RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

VB(.net) Windows10 WinFormsのDataGridViewのカスタム

本記事では、下記の方法をまとめて記載いたします。(挙動はWindows10にて確認済の内容となります)

①DataGridViewのヘッダー背景色変更

②DataGridViewのヘッダー文字色変更

③DataGridViewのヘッダー・項目それぞれのフォント変更

④DataGridViewのヘッダー・項目それぞれの文字の寄せ方変更

変更前のコードと画面は以下の通り。

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        DataGridView1.Columns.Add("col1", "漢字1")
        DataGridView1.Columns.Add("col2", "漢字2")
        DataGridView1.Rows.Add({"漢字3", 3})
        DataGridView1.Rows.Add({"漢字4", 4})

    End Sub
End Class

f:id:ys-soniclab:20181231204739p:plain
変更前

変更用コードと変更後画面は以下の通り。

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        DataGridView1.Columns.Add("col1", "漢字1")
        DataGridView1.Columns.Add("col2", "漢字2")
        DataGridView1.Rows.Add({"漢字3", 3})
        DataGridView1.Rows.Add({"漢字4", 4})

        '下記設定を行わないと、①と②の設定が反映されない
        DataGridView1.EnableHeadersVisualStyles = False

        '①DataGridViewのヘッダー背景色変更
        DataGridView1.Columns().Item(0).HeaderCell.Style.BackColor = Color.Red
        '②DataGridViewのヘッダー文字色変更
        DataGridView1.Columns().Item(1).HeaderCell.Style.ForeColor = Color.Green

        '③DataGridViewのヘッダー・項目それぞれのフォント変更
        'ヘッダーのフォント変更
        DataGridView1.Columns().Item(0).HeaderCell.Style.Font = New Font("MS 明朝", 8)
        DataGridView1.Columns().Item(1).HeaderCell.Style.Font = New Font("Meiryo UI", 15)
        '項目のフォント変更
        DataGridView1.Columns().Item(0).DefaultCellStyle.Font = New Font("MS ゴシック", 15)
        DataGridView1.Columns().Item(1).DefaultCellStyle.Font = New Font("MS P ゴシック", 8)

        '④DataGridViewのヘッダー・項目それぞれの文字の寄せ方変更
        'ヘッダーの寄せ方変更
        DataGridView1.Columns().Item(0).HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopLeft
        DataGridView1.Columns().Item(1).HeaderCell.Style.Alignment = DataGridViewContentAlignment.BottomLeft

        '項目の寄せ方変更
        DataGridView1.Columns().Item(0).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
        DataGridView1.Columns().Item(1).DefaultCellStyle.Alignment = DataGridViewContentAlignment.BottomRight

        'ヘッダーの高さ変更
        '下記のプロパティ変更を行わないと、ColumnHeadersHeightが正しく反映されない
        DataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing
        DataGridView1.ColumnHeadersHeight = 100

    End Sub
End Class

f:id:ys-soniclab:20190103184425p:plain
変更後

VBでASP.net WebAPIを作る

ASP.net+VBでWebApiを作りたい。WebApiは呼ばれたらバッチを実行し、バッチ処理が完了した後にレスポンスを返してほしい。レスポンスは何でもよい。」とリクエストを受けたので、作ってみました。

WebApiの作り方は、下記の記事内容を引用させて頂きます。

blog.okazuki.jp

本記事は、ほとんど上記記事のコードをVBでやってみただけの記事になります。

プロジェクトを作る

空のASP.netプロジェクトを作成します。

f:id:ys-soniclab:20170204210936p:plain

NuGetで「Microsoft ASP.NET Web API 2 Web Host」を追加

f:id:ys-soniclab:20170204211017p:plain

本記事ではv5.2.3を利用しました。

Global.asaxの追加

プロジェクトを右クリックし、「追加」→「新しい項目を追加」を選択し、ダイアログで「Web」→「グローバル アプリケーション クラス」を選択。

f:id:ys-soniclab:20170204211129p:plain

作成された「Global.asax」を開き、下記のようにコードを追加します。

Imports System.Web.SessionState
'追加ここから
Imports System.Web.Http
'追加ここまで

Public Class Global_asax
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' アプリケーションの起動時に呼び出されます
        '追加ここから
        GlobalConfiguration.Configure(
            Sub(config)
                config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}",
                                           New With {.id = RouteParameter.Optional})
            End Sub)
        '追加ここまで
    End SubEnd Class

ControllerフォルダとControllerクラスの追加

プロジェクトを右クリックし、「追加」→「新しいフォルダー」でフォルダを追加、名前を「Controller」に変更します。 「Controller」フォルダを右クリックし、「追加」→「クラス」でクラスを追加する、名前は「ExecBatchController」とします。

f:id:ys-soniclab:20170204212746p:plain

メソッドの実装

ExecBatchControllerを下記のように書き換えます。 なお、C#ではGetのWebAPIを作成する場合「Get」とするが、VBでは「Get」が予約語であるため、「GetValues」とします。

Imports System.Web.Http

Public Class ExecBatchController
    Inherits ApiController

    Public Function GetValues() As String

        'バッチを実行(test.batはtimeout 20を実行する)
        Dim p As System.Diagnostics.Process =
            System.Diagnostics.Process.Start("C:\\test\\test.bat")
        p.WaitForExit()
        '適当な値を返す
        Return "end process"
    End Function

End Class

実行

実行すると、ブラウザにはエラーが表示されるが、WebAPIは起動しています。

f:id:ys-soniclab:20170204214346p:plain

ブラウザのアドレスバーに「http://localhost:(ポートNo)/api/ExecBatch」と入力すると、バッチが実行されます。

f:id:ys-soniclab:20170204214326p:plain

バッチ終了後、"end process"と文字列が返されます。

f:id:ys-soniclab:20170204214747p:plain

C#で書いたラムダ式を用いたコードをVBで書き直してみる

.netのVBを使い慣れない私が、今度はラムダ式を使ったテストコード(コンソールアプリケーション)をC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //文字列を返すだけのFunc
            Func<string> func = () => "returnvalue";
            //数値を3倍にして文字列で返すFunc
            Func<int, string> func2 = (i) =>
            {
                i = i * 3;
                return i.ToString();
            };
            //引数をコンソールに表示するだけのAction
            Action<string,string> action = (str1,str2) => 
            {
                Console.WriteLine(str1);
                Console.WriteLine(str2);
            };
            //Actionを実行
            action(func(), func2(2));

            //下記コードはコンソール画面を表示しておくために追加
            Console.ReadLine();
        }
    }
}

上記処理をVB(Visual Studio 2015)で書き直してみます。

Module Module1
    Sub Main()
        '文字列を返すだけのFunc
        Dim func As Func(Of String) = Function() "returnstring"
        '数値を3倍にして文字列で返すFunc
        Dim func2 As Func(Of String, Integer) =
            Function(i)
                i = i * 3
                Return i.ToString()
            End Function
        '引数をコンソールに表示するだけのAction
        Dim action As Action(Of String, String) =
        Sub(str1, str2)
            Console.WriteLine(str1)
            Console.WriteLine(str2)
        End Sub
        'Actionを実行
        action(func(), func2(2))

        '下記コードはコンソール画面を表示しておくために追加
        Console.ReadLine()
    End Sub
End Module

処理結果はどちらも

returnstring

6

となります。

LinqGUIのイベントにと使い道は非常に多いので、VBでのラムダ式の書き方は押さえておくと便利そうと感じました。 とりあえず書き溜めて、そのうちQiitaとかで纏めようかと思います。

C#で書いたイテレーターを用いたコードをVBで書き直してみる

.netのVBを使い慣れない私が、今度はイテレーターを使ったテストコード(コンソールアプリケーション)をC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (string message in TestList())
            {
                Console.WriteLine(message);
            }
            //下記コードはコンソール画面を表示しておくために追加
            Console.ReadLine();
        }

        static IEnumerable<string> TestList()
        {
            yield return "I";
            yield return "have";
            yield return "a";
            yield return "pen";
        }
    }
}

上記処理をVB(Visual Studio 2015)で書き直してみます。

Module Module1

    Sub Main()
        For Each message In TestList()
            Console.WriteLine(message)
        Next
        '下記コードはコンソール画面を表示しておくために追加
        Console.ReadLine()
    End Sub

    Iterator Function TestList() As IEnumerable(Of String)
        Yield "I"
        Yield "have"
        Yield "a"
        Yield "pen"
    End Function

End Module

イテレーター自体はあまり多用しないので微妙ですが、ジェネリックの書き方の違いが見やすいかなと感じました。 とりあえず書き溜めて、そのうちQiitaとかで纏めようかと思います。

C#で書いたカスタム属性をVB(.net)でも書いてみる

VBを使い慣れない私が、カスタム属性クラスを適用するテストコードをC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

    //文字列を保持するだけの属性
    public class TestAttribute : Attribute
    {
        private string val;
        //属性のコンストラクタ
        public TestAttribute(string testValue)
        {
            val = testValue;
        }
        //コンストラクタで入れた文字列を返すプロパティ
        public string Value
        {
            get { return val; }
        }
    }
    //Test属性を適用したクラス
    [Test("TestValue")]
    public class TestClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            //TestClassに指定されたTestAttributeを取得
            var att = (TestAttribute)Attribute.GetCustomAttribute(typeof(TestClass), typeof(TestAttribute));
            //TestAttributeのValueを取得
            Console.WriteLine(att.Value);
        }
    }

上記処理をVB(Visual Studio 2015)で書き直してみます。

'文字列を保持するだけの属性
Public Class TestAttribute
    Inherits System.Attribute
    Private val As String
    '属性のコンストラクタ
    Public Sub New(ByVal testValue As String)
        val = testValue
    End Sub
    'コンストラクタで入れた文字列を返すプロパティ
    Public ReadOnly Property Value() As String
        Get
            Return val
        End Get
    End Property
End Class

'Test属性を適用したクラス
<Test("TestValue")>
Public Class TestClass
End Class

Module Module1

    Sub Main()
        'TestClassに指定されたTestAttributeを取得
        Dim att As TestAttribute = Attribute.GetCustomAttribute(GetType(TestClass), GetType(TestAttribute))
        'TestAttributeのValueを取得
        Console.WriteLine(att.Value)
    End Sub

End Module

書いてみた結果、継承、コンストラクタ、プロパティ定義やら、C#VBでの書き方の違いを見比べられて少し良いなと思いました。

C#ユーザーがVBの遅延バインディングに驚いた話など

色々あって、Visual Studio 2008でVBをやっています。

触ってみた結果、多数のスタティックメソッドや「改行にアンダーバー必須」といった文法関連など色々なC#との使い勝手の違いを感じましたが、一番違いに驚いたのは下記コードの動作です。

    Dim testobj As Object
    'オブジェクト型の変数に日付型を代入
    testobj = DateTime.Now
    'Objectクラスが持っていない「Month」プロパティを、遅延バインディングで使える
    Console.WriteLine(testobj.Month & "月")

思い起こせば、VB6の頃は当たり前のように遅延バインディングを使ったコードを書いていましたが、今見るとtestobjにMonthプロパティがないクラスのインスタンスを入れると実行時エラーが出せてしまう事から、少々おっかない印象を受けてしまいます。

なお上記のコードをC#で動くように書くと、下記のようになります。

    object testobj;
    testobj = DateTime.Now;
    //TypeクラスのGetPropertyメソッドでプロパティ情報を取得し、オブジェクトのプロパティ値取得を行う
    Console.WriteLine(testobj.GetType().GetProperty("Month").GetValue(testobj).ToString() + "月");

リフレクションを用いてプロパティ情報を取得した後、プロパティの実際の値を取得する流れになります。

少々冗長な気もしますが、リフレクションを使うと下記のように、変数testobjのプロパティの有無をチェックする事が容易です。

    object testobj;
    testobj = DateTime.Now;
    //Monthプロパティを取得
    System.Reflection.PropertyInfo prop = testobj.GetType().GetProperty("Month");
    if (prop != null)
    {//testobjに入ったオブジェクトに、Monthプロパティが存在する場合にのみ処理が実行される
        Console.WriteLine(prop.GetValue(testobj).ToString() + "月");
    }

プロパティの有無をチェックすることで、実行時エラーを防げます。

もちろんVBでも、リフレクションを用いてプロパティの有無をチェックできます。

    Dim testobj As Object
    testobj = DateTime.Now
    'リフレクションでMonthプロパティの情報を取得
    Dim prop As System.Reflection.PropertyInfo = testobj.GetType().GetProperty("Month")
    If (prop <> Nothing) Then
        'testobjにMonthプロパティが存在する場合のみ、処理が実行される
        Console.WriteLine(testobj.Month & "月")
    End If

コードは長くなりますが、遅延バインディングの対象となるオブジェクト型の変数に何が入ってくるかわからない時にはぜひこのチェックは行ったほうが良さそうです。

遅延バインディングを止めて、きちんと型チェックをしてから実行したい場合は下記のような感じで。

Option Strict OnDim testobj As Object
    testobj = DateTime.Now
    'リフレクションでMonthプロパティの情報を取得
    Dim prop As System.Reflection.PropertyInfo = testobj.GetType().GetProperty("Month")
    If (prop <> Nothing) Then
        'testobjにMonthプロパティが存在する場合のみ、処理が実行される
        Console.WriteLine(prop.GetValue(testobj).ToString() & "月")
    End If

なお、C#4.0からはdynamic型が使えるため、C#でも遅延バインディング的な書き方が可能です。

    dynamic testobj;
    testobj = DateTime.Now;
    Console.WriteLine(testobj.Month + "月");

VBの遅延バインディングと同じ理由で、testobjの内容に変更が多い場合はリフレクションのほうがお勧めです。

WPF:プロパティ値の継承(包含継承)が可能な添付プロパティ

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <GroupBox Header="test" Foreground="Red">
        <StackPanel Orientation="Vertical">
            <TextBlock Text="test" />
            <TextBox Text="test"/>
            <Button Content="test" />
        </StackPanel>
    </GroupBox>
</Window>

上記のようなxamlを実行した場合、GroupBoxのForegroundプロパティの値を子のアイテムが引き継ぐことがあります。 この動きを、プロパティの値の継承と呼びます。(クラスの派生による継承とは異なることから、「包含継承」と呼ぶことがあるようです。)

上記の例では、TextBlockはGroupBoxのForegroundを引き継ぎますが、TextBoxやButtonのForegroundは引き継がれないようです。

f:id:ys-soniclab:20160910215003p:plain

そこで、プロパティ値の継承が可能な添付プロパティを作成し、そのプロパティが設定されたコントロールのForegruondを強制的に書き換えるようにしてみます。

public class Att
{
    public static Brush GetForeground(DependencyObject obj)
    {
        return (Brush)obj.GetValue(ForegroundProperty);
    }

    public static void SetForeground(DependencyObject obj, Brush value)
    {
        obj.SetValue(ForegroundProperty, value);
    }

    public static readonly DependencyProperty ForegroundProperty =
        DependencyProperty.RegisterAttached("Foreground", typeof(Brush), typeof(Att), new FrameworkPropertyMetadata(null, OnForegroundChanged) { Inherits = true });

    private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = d as Control;
        if (ctl == null) return;
        ctl.Foreground = e.NewValue as Brush;
    }
}
<Window …>
    <GroupBox Header="test" local:Att.Foreground="Red">
        <StackPanel Orientation="Vertical">
            <TextBlock Text="test" />
            <TextBox Text="test"/>
            <Button Content="test" />
        </StackPanel>
    </GroupBox>
</Window>

上記の添付プロパティを付けると、Foregroundが強制的に変更されます。

f:id:ys-soniclab:20160910215020p:plain