WPFで使いまわしができる「ListBox.SelectedItemsを取得するメソッド」

（2020/09/22追記）
「ジェネリックメソッド」を用いることで、 ```List<T>``` の ```<T>``` に当たる部分を呼び出し元で指定できることが分かりました。リストボックスの中身を取得するメソッドくん②、最終的なコード全体②として記載しました。 @MinoruSanou さん、ありがとうございます。

（2020/09/26追記）
長々と記事を書きましたが、 @TN8001 さんの [こちらのコメント][] に、現状僕が知る中で一番簡単な ListBox.SelectedItem を取得するコードを掲載頂きました。TN8001 さんありがとうございます。
[こちらのコメント]:https://qiita.com/Michio029/items/fd597470cc62cfa79ed7#comment-e4c149eb4e00ada84c49

#背景
以前 [こちら][1] でListBox.SelectedItemsの中身を取り出す方法を書きました。しかし ListBox が複数になるとコード量が多く邪魔だったので、中身を取得する専用のメソッドくんを考えました。

[1]:https://qiita.com/Michio029/items/3b531acd46bb1f81f7d7 "WPFのListBox.SelectedItems(複数選択)を取り出すのが意外と大変だった"

#準備
WPFでリストを複数（今回は二つ）作る。
c#でリスト項目をセット

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301115/76cb0c48-5b81-d60b-a10f-62a6d1921605.png)

```xml:MainWindow.xaml
<Window>
    <Grid>
        <!-- 上下に2分割 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- 上半分 -->
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>

            <Label Grid.Row="0" Content="リストボックス1"/>
            <ListBox Grid.Row="1" Name="ExampleList1" 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="2" Name="Btn1" Content="ボタン1" Margin="50,0,50,0" Click="Btn1_Click"/>
        </Grid>

        <!-- 下半分 -->
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>

            <Label Grid.Row="0" Content="リストボックス2" Grid.ColumnSpan="2"/>
            <ListBox Grid.Row="1" Name="ExampleList2" Margin="10" SelectionMode="Extended" ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.ColumnSpan="2">
                <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="2" Name="Btn2" Content="ボタン2" Margin="50,0,50,0" Click="Btn2_Click" Grid.ColumnSpan="2"/>
        </Grid>
    </Grid>
</Window>
```

```c#:MainWindow.xaml.cs
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
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 });
        ExampleList1.ItemsSource = list;
        ExampleList2.ItemsSource = list;
    }

    private void Btn1_Click(object sender, RoutedEventArgs e)
    {
        //リストボックス1の選択アイテムを取り出す
    }

    private void Btn2_Click(object sender, RoutedEventArgs e)
    {
        //リストボックス2の選択アイテムを取り出す
    }
}
class ExampleClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte Age { get; set; }
}
```

#リストボックスの中身を取得するメソッドくん
今回の本題です。このメソッドを、リスト選択アイテムを知りたい場所で呼び出します。

```c#:MainWindow.xaml.cs
    //! SelectedItemsの中身を取り出すメソッド
    private List<ExampleClass> GetSelectedItems(ListBox listBox)
    {
        List<ExampleClass> selItems = new List<ExampleClass>();
        foreach (var oneItemLine in listBox.SelectedItems)
        {
            ExampleClass item = oneItemLine as ExampleClass;
            selItems.Add(item);
        }
        return selItems;
    }
```

<br>
各ボタンから呼び出します。
対象のコントロールを探す方法は [こちら][2] を参考にしました。
[2]:https://qiita.com/7of9/items/2dd05738a66a71bb3c11 "Visual Studio | WPF > 名前からコントロールを見つける > FindName()"

```c#:MainWindow.xaml.cs
    //! リストボックス1の選択中アイテムを表示する
    private void Btn1_Click(object sender, RoutedEventArgs e)
    {
        // 操作するコントロール
        string controlName = "ExampleList1";

        // リストボックスを探す
        object controlObj = FindName(controlName);
        ListBox listBox = (ListBox)controlObj;

        // 選択項目が0 => メソッドを出る
        if (listBox.SelectedItems.Count == 0)
            return;

        // 選択中のアイテムを取得する
        List<ExampleClass> selItems = GetSelectedItems(listBox);

        // メッセージボックスに表示する内容
        string message = "";
        foreach (var line in selItems)
        {
            message = message + string.Format("\r\nId：「{0}」　Nmae：「{1}」　Age：「{2}」", line.Id, line.Name, line.Age);
        }
        message = string.Format($"{controlName} で選択中の項目は\r\n{message}\r\n\r\nです");

        MessageBox.Show(message);
    }

    //! リストボックス2の選択中アイテムを表示する
    private void Btn2_Click(object sender, RoutedEventArgs e)
    {
        // リスト1と大体同じ。ContorolNameを変えるのみ。
    }
```

ボタン1 を押すと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301115/51f3a205-5aa6-8ea4-ac13-4b48653bd725.png)
選択中の項目が取り出せました。ボタン2でも同じです。
でもボタン1と2のコードもだいたい同じだからまとめたいですね。まとめた最終的なコードを最後に載せておきます。

#最終的なコード全体
ExampleClassは変わっていないので省略します。

```c#:MainWindow.xaml.cs
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
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 });
        ExampleList1.ItemsSource = list;
        ExampleList2.ItemsSource = list;
    }

    //! リストボックス1の選択中アイテムを表示する
    private void Btn1_Click(object sender, RoutedEventArgs e)
    {
        // 操作するコントロール
        string controlName = "ExampleList1";

        ItemShow(controlName);
    }

    //! リストボックス2の選択中アイテムを表示する
    private void Btn2_Click(object sender, RoutedEventArgs e)
    {
        // 操作するコントロール
        string controlName = "ExampleList2";

        ItemShow(controlName);
    }

    // SelectedItemsの中身を取り出す～メッセージ表示メソッド
    private void ItemShow(string controlName)
    {
        // リストボックスを探す
        object controlObj = FindName(controlName);
        ListBox listBox = (ListBox)controlObj;

        // 選択項目が0 => メソッドを出る
        if (listBox.SelectedItems.Count == 0)
            return;

        // 選択中のアイテムを取得する
        List<ExampleClass> selItems = GetSelectedItems(listBox);

        // メッセージボックスに表示する内容
        string message = "";
        foreach (var line in selItems)
        {
            message = message + string.Format("\r\nId：「{0}」　Nmae：「{1}」　Age：「{2}」", line.Id, line.Name, line.Age);
        }
        message = string.Format($"{controlName} で選択中の項目は\r\n{message}\r\n\r\nです");

        MessageBox.Show(message);
    }

    //! SelectedItemsの中身を取り出すメソッド
    private List<ExampleClass> GetSelectedItems(ListBox listBox)
    {
        List<ExampleClass> selItems = new List<ExampleClass>();
        foreach (var oneItemLine in listBox.SelectedItems)
        {
            ExampleClass item = oneItemLine as ExampleClass;
            selItems.Add(item);
        }
        return selItems;
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301115/237934e9-6a75-aa55-8e94-c4131a07f57f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301115/cd49f3b7-8968-72c5-1b7f-0a98ee3e80c7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301115/b449487f-75de-202f-fcad-b74484c6796f.png)
アイテムはクリックした順で取得します。

#リストボックスの中身を取得するメソッドくん②
以下 2020/09/22 の追記です。
ジェネリックメソッドを用いることで、T を呼び出し元で指定することができます。これにより使いまわし性が向上します。戻り値 ```selItems``` は ```List<T>``` 型です。

```c#:
public List<T> GetSelectedItems<T>(ListBox listBox) where T : class
{
    var selItems = new List<T>();
    foreach (var oneItemLine in listBox.SelectedItems)
    {
        var item = oneItemLine as T;
        selItems.Add(item);
    }
    return selItems;
}
```

<br>
これをこのまま```MainWindow.xaml.cs```内に記述してもいいですが、せっかく使いまわしの自由度が増したので、独立したクラスにしたいと思います。

```c#:GetSelectedItems.cs
class GetSelectedItems<T> where T : class
{
    ListBox listBox;

    public GetSelectedItems(ListBox listBox)
    {
        this.listBox = listBox;
    }

    //! SelectedItemsの中身を取り出すメソッド
    public List<T> Get()
    {
        var selItems = new List<T>();
        foreach (var oneItemLine in listBox.SelectedItems)
        {
            var item = oneItemLine as T;
            selItems.Add(item);
        }
        return selItems;
    }
}
```

<br>
呼び出し元で以下の記述をすることで、SelectedItemsの中身を取り出せます。```var selItems``` は ```List<T>``` 型で、選択中のリストボックスアイテムが入ります。

```c#
    // リストボックスを探す
    object controlObj = FindName(controlName);
    ListBox listBox = (ListBox)controlObj;

    // 選択中のアイテムを取得する
    GetSelectedItems<ExampleClass> g = new GetSelectedItems<ExampleClass>(listBox);
    var selItems = g.Get();
```


#最終的なコード全体②
内容が増えたのでc#の記述全体を書きます。

```c#:MainWindow.xaml.cs
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
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 });
        ExampleList1.ItemsSource = list;
        ExampleList2.ItemsSource = list;
    }

    //! リストボックス1の選択中アイテムを表示する
    private void Btn1_Click(object sender, RoutedEventArgs e)
    {
        // 操作するコントロール
        string controlName = "ExampleList1";

        ItemShow(controlName);
    }

    //! リストボックス2の選択中アイテムを表示する
    private void Btn2_Click(object sender, RoutedEventArgs e)
    {
        // 操作するコントロール
        string controlName = "ExampleList2";

        ItemShow(controlName);
    }

    // SelectedItemsの中身を取り出す～メッセージボックス表示メソッド
    private void ItemShow(string controlName)
    {
        // リストボックスを探す
        object controlObj = FindName(controlName);
        ListBox listBox = (ListBox)controlObj;

        // 選択項目が0 => メソッドを出る
        if (listBox.SelectedItems.Count == 0)
            return;

        // 選択中のアイテムを取得する
        GetSelectedItems<ExampleClass> g = new GetSelectedItems<ExampleClass>(listBox);
        var selItems = g.Get();

        // メッセージボックスに表示する内容
        string message = "";
        foreach (var line in selItems)
        {
            message = message + string.Format("\r\nId：「{0}」　Nmae：「{1}」　Age：「{2}」", line.Id, line.Name, line.Age);
        }
        message = string.Format($"{controlName} で選択中の項目は\r\n{message}\r\n\r\nです");

        MessageBox.Show(message);
    }
}
```

```c#:ExampleClass.cs
class ExampleClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte Age { get; set; }
}
```

```c#:GetSelectedItems.cs
class GetSelectedItems<T> where T : class
{
    ListBox listBox;

    public GetSelectedItems(ListBox listBox)
    {
        this.listBox = listBox;
    }

    //! SelectedItemsの中身を取り出すメソッド
    public List<T> Get()
    {
        var selItems = new List<T>();
        foreach (var oneItemLine in listBox.SelectedItems)
        {
            var item = oneItemLine as T;
            selItems.Add(item);
        }
        return selItems;
    }
}
```