20200916のC#に関する記事は3件です。

WPFのListBox.SelectedItems(複数選択)を取り出すのが意外と大変だった

(2020/09/17追記)
私が掲載した内容をよりスマートにして頂いた内容が、コメントにあります。私の後学のために本記事の内容はそのままにしますが、是非コメントにも目を通してみて下さい。soiさんご指摘、解説ありがとうございます。

(2020/09/18追記)
リストボックスが複数ある場合に、コード量をもっと少なくする方法を思いついたので、こちら に書きました。

背景

WPF に用意されている ListBox の SelectedItem は簡単に取り出せるのですが SelectedItem"s" を取り出すのに少し苦労したのでその備忘録です。

SelectedItem(単体選択)の取り出し方は 別記事 に書きました。

準備

ListBoxとその中身を用意します
image.png

MainWindow.xaml
<Window><!-- Windowの細かいのは省略 -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>

        <ListBox Grid.Row="0" Name="ExampleList" Margin="10" SelectionMode="Extended" ScrollViewer.VerticalScrollBarVisibility="Auto">
            <!-- SelectionMode="Extended" :ctrl,shift+クリックで複数選択可能 -->
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Id, StringFormat=IDは{0} :}"/>
                        <TextBlock Text="{Binding Name, StringFormat= Nameは{0} :}"/>
                        <TextBlock Text="{Binding Age, StringFormat= Ageは{0}}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Grid.Row="1" Name="Btn" Content="ボタン" Margin="100,10,100,10" Click="Btn_Click"/>
    </Grid>
</Window>

<TextBlock Text="{Binding Id, StringFormat=IDは{0} :}"/>
この辺の文字列のフォーマットの書き方についてはこちらで詳しく解説してくれています。
https://qiita.com/koara-local/items/815eb5146b3ddc48a8c3

MainWindow.xaml.cs
public partial class MainWindow : Window
{
    List<ExampleClass> list = new List<ExampleClass>();
    public MainWindow()
    {
        InitializeComponent();

        list.Add(new ExampleClass() { Id = 0, Name = "aaa", Age = 10 });
        list.Add(new ExampleClass() { Id = 1, Name = "bbb", Age = 20 });
        list.Add(new ExampleClass() { Id = 2, Name = "ccc", Age = 30 });
        list.Add(new ExampleClass() { Id = 3, Name = "ddd", Age = 40 });
        list.Add(new ExampleClass() { Id = 4, Name = "eee", Age = 50 });
        ExampleList.ItemsSource = list;
    }

    private void Btn_Click(object sender, RoutedEventArgs e)
    {

    }
}
class ExampleClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte Age { get; set; }
}

選択状態の中身を見てみる

複数選択時の中身がどうなっているのか知りたいので、ボタンを押すところでブレークポイントを設定してみます。
image.png
ボタンを押してブレークします。
image.png
どうやら SelectedItems 自体は配列になっていて、その中身が SelectedItem と同じっぽいです。
Count は 0 からではなく 1 から始まるみたいですね。3つ選択中なので3。

これを見ると foreach の中で SelectedItem(単体選択) を取り出すときと同じことをしたら取り出せる気がします。

SelectedItems(複数選択)を取り出す

ボタンに動作を追記します

    private void Btn_Click(object sender, RoutedEventArgs e)
    {
        // 選択項目が0 => メソッドを出る
        if (ExampleList.SelectedItems.Count == 0)
            return;

        // 空の配列を宣言
        int[] _id = new int[0];
        string[] _name = new string[0];
        byte[] _age = new byte[0];

        int i = 0;

        // SelectedItemsの中身を取り出す
        foreach (var oneItemLine in ExampleList.SelectedItems)
        {
            // 配列の箱を一つ増やす
            int reLength = _id.Length + 1;
            Array.Resize(ref _id, reLength);
            Array.Resize(ref _name, reLength);
            Array.Resize(ref _age, reLength);

            // ExampleClassとして取り出す
            ExampleClass item = oneItemLine as ExampleClass;
            _id[i] = item.Id;
            _name[i] = item.Name;
            _age[i] = item.Age;
            i++;
        }

        // 三つ選択がある(SelectedItems.Count=3)としたら、3回ループして欲しい(n=0,1,2)ので n < SelectedItems.count
        for (int n = 0; n < ExampleList.SelectedItems.Count; n++)
        {
            Console.WriteLine("Id:「{0}」 Name:「{1}」 Age:「{2}」", _id[n], _name[n], _age[n]);
        }
    }



実行してボタンを押してみます

Id:「0」 Name:「aaa」 Age:「10」
Id:「1」 Name:「bbb」 Age:「20」
Id:「2」 Name:「ccc」 Age:「30」

取れました。

どうやら項目は選択した順に入っていくようです。

Id:「4」 Name:「eee」 Age:「50」
Id:「2」 Name:「ccc」 Age:「30」
Id:「0」 Name:「aaa」 Age:「10」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WPFで中身がClassアイテムのListBox.SelectedItemを取得する

既出かもしれませんが備忘録として。

SelectedItems(複数選択)の取り出し方も書きました
https://qiita.com/Michio029/items/3b531acd46bb1f81f7d7

準備

リストと中身を用意します
image.png

MainWindow.xaml
<Window><!-- Windowの細かいのは省略 -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>

        <ListBox Grid.Row="0" Name="ExampleList" Margin="10" SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Auto">
            <!-- SelectionMode="Single" :リストから一つしか選択できない -->
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Id, StringFormat=IDは{0} :}"/>
                        <TextBlock Text="{Binding Name, StringFormat= Nameは{0} :}"/>
                        <TextBlock Text="{Binding Age, StringFormat= Ageは{0}}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Grid.Row="1" Name="Btn" Content="ボタン" Margin="100,10,100,10" Click="Btn_Click"/>
    </Grid>
</Window>

<TextBlock Text="{Binding Id, StringFormat=IDは{0} :}"/>
この辺の文字列のフォーマットの書き方についてはこちらで詳しく解説してくれています。
https://qiita.com/koara-local/items/815eb5146b3ddc48a8c3

MainWindow.xaml.cs
public partial class MainWindow : Window
{
    List<ExampleClass> list = new List<ExampleClass>();
    public MainWindow()
    {
        InitializeComponent();

        list.Add(new ExampleClass() { Id = 0, Name = "aaa", Age = 10 });
        list.Add(new ExampleClass() { Id = 1, Name = "bbb", Age = 20 });
        list.Add(new ExampleClass() { Id = 2, Name = "ccc", Age = 30 });
        list.Add(new ExampleClass() { Id = 3, Name = "ddd", Age = 40 });
        list.Add(new ExampleClass() { Id = 4, Name = "eee", Age = 50 });
        ExampleList.ItemsSource = list;
    }

    private void Btn_Click(object sender, RoutedEventArgs e)
    {

    }
}
class ExampleClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte Age { get; set; }
}

SelectedItemを取り出す

ボタンの処理を追記します。

    private void Btn_Click(object sender, RoutedEventArgs e)
    {
        // 選択項目が0 => メソッドを出る
        if (ExampleList.SelectedItems.Count == 0)
            return;

        // ExampleClassとして取り出す
        ExampleClass selitem = ExampleList.SelectedItem as ExampleClass;

        // 中身の確認
        Console.WriteLine("選択中の項目 Id:{0} Name:{1} Age{2}", selitem.Id, selitem.Name, selitem.Age);
    }



ボタンを押してみます
image.png

実行結果

選択中の項目 Id:0 Name:aaa Age10

取り出せました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[VB.NET] 暗黙のFormオブジェクト~My.Forms

My.Formsの謎に迫る

まず、VB.NET の WinForms プロジェクトを作成し、下記のようなコードを記述します。
(Form1 には、Button1 と Button2 の2つのボタンがあり、デザイナで Click イベントハンドラを設定しています)

Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Test.HelloWorldA(Me)
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Test.HelloWorldB()
    End Sub

End Class

Public Class Test
    Public Shared Sub HelloWorldA(frm As Form1)
        frm.Text = "Hello World! A"
    End Sub

    Public Shared Sub HelloWorldB()
        Form1.Text = "Hello World! B"
    End Sub

End Class

HelloWorldB メソッドでは Form1 のインスタンスを渡しておらず、何故かインスタンスが作成されていないForm1.Text プロパティに対して直接アクセスしてるように見えます。

一見コンパイルエラーになるように見えますが、奇妙な事に実際はこのコードはコンパイルエラーにもならないし、Button1 をクリックすると "Hello World! A"、Button2 をクリックすると "Hello World! B" にフォームのタイトルが切り替わります。

VisualStudioで、HelloWorldB の Form1 にマウスカーソルを当てると、下記のように表示されます。
VisualStudio表示

ちなみに、ILSpy でC#にデコンパイルすると、下記のようになります。
decomp.jpg

WindowsApp1.My という名前空間と、その中に、My〇〇 というクラスが作成され、
MyProject クラスの中に Forms という静的プロパティがあり、その中に Form1 プロパティがあります。
C#の HelloWorldB メソッドのコードを見ると、

MyProject.Forms.Form1.Text = "Hello World! B";

となっています。VB.NET が勝手に My.MyProject.Forms にフォームのインスタンスを作成していて、
[フォームクラス名.プロパティ]
と記述すると、My.MyProject.Forms を省略し、そこにアクセスしたものと解釈してくれる訳ですね。まったく余計な事しやがって。

関連ドキュメントを漁ったら、下記のものが見つかりました。他にも色々な My オブジェクトがあるようですね。英語苦手な人は Google 翻訳通してください。
My.Forms Object
My.Internals: Examining the Visual Basic My Feature
Objects implicitly instantiated in vb.net?

My.Formsの問題点

VB.NET はデフォルトのプロジェクト設定だと Form から直接起動され、この場合は My.Forms のインスタンスが使用されますが、これをC#のように Main メソッドを作成し、プロジェクト設定のスタートアップオブジェクトを変更して、Main メソッドから起動してフォームを作成したとします。

Public Class Program

    <STAThread>
    Shared Sub Main()
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.Run(New Form1)
    End Sub

End Class

これで Form1 の Button2 をクリックした場合どうなるかというと、フォームのタイトルは "Hello World! B" に設定されません。
Main メソッドで作成した Form1 と、MyProject.Forms.Form1 の Form1 のインスタンスは別物であり、現在画面に表示されているのは Main メソッドで作成した Form1 です。MyProject.Forms.Form1 の Text プロパティを更新した所で、当然フォームのタイトルは変わらないという訳ですね。

.NET開発の主流は C# で、VB.NET は開発も止まるみたいですし今後無くなっていくとは思いますが、既存の VB.NET のコードを C# に移植する際、My.Forms のような暗黙のオブジェクトはC#には無い要素なので、もし参照している箇所があったりするとハマりどころになる場合があるので注意が必要です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む