rksoftware

Visual Studio とか C# とかが好きです

ListBox などの選択肢を増減する

WPF で ListBox など (ItemsControl、ComboBox など) は DataSource に List などを設定することで選択肢を表示できます。
選択肢を表示するだけであれば、List や配列などを設定すれば OK です。
しかし、選択肢を状況によって増減したいとなると注意点が必要です。

■ 選択肢を増減するコード

次のコードでは、画面を表示し 3 秒後に "B" を削除しています。

・XAML

<Window x:Class="WpfApp.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <ListBox x:Name="listA" ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox x:Name="listB" ItemsSource="{Binding}" Grid.Row="1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox x:Name="listC" ItemsSource="{Binding}" Grid.Row="2">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Value}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

・コードビハインド

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var list = new List<string> { "A", "B", "C" };
            var observable = new ObservableCollection<string> { "A", "B", "C" };
            var dt = new DataTable();
            dt.Columns.Add("Value");
            dt.Rows.Add("A"); dt.Rows.Add("B"); dt.Rows.Add("C");

            listA.DataContext = list;
            listB.DataContext = observable;
            listC.DataContext = dt;

            Task.Run(async () =>
            {
                await Task.Delay(3000);
                this.Dispatcher.Invoke(() =>
                {
                    list.RemoveAt(1);
                    observable.RemoveAt(1);
                    dt.Rows.RemoveAt(1);
                });
            });
        }
    }
}

実行結果です。
f:id:rksoftware:20180913004241j:plain
一つ目の ListBox では、"B" が削除されていません。データの増減を View に反映するには DataContext には List などではなく ObservableCollection のオブジェクトをセットする必要があります。
もしくは DataTable です。

■ 要素を削除する

要素を追加する場合は追加する要素の型が必要になるためどうしようもありませんが、要素を index 指定で削除する場合は ObservableCollection と DataTable でインタフェースをそろえた処理が行えそうです。
拡張メソッドを作ってみます。

using System.Data;
using System.Windows.Controls;

namespace WpfApp
{
    public static class ItemsControlExtensions
    {
        public static void RemoveDataAt(this ItemsControl control, int index)
        {
            var data = control.DataContext;
            if (data == null) return;
            if(data is DataTable dt) { dt.Rows.RemoveAt(index); return; }
            data.GetType().GetMethod("RemoveAt")?.Invoke(data, new object[] { index });
        }
    }
}

・コードビハインド

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var list = new List<string> { "A", "B", "C" };
            var observable = new ObservableCollection<string> { "A", "B", "C" };
            var dt = new DataTable();
            dt.Columns.Add("Value");
            dt.Rows.Add("A"); dt.Rows.Add("B"); dt.Rows.Add("C");

            listA.DataContext = list;
            listB.DataContext = observable;
            listC.DataContext = dt;

            Task.Run(async () =>
            {
                await Task.Delay(3000);
                this.Dispatcher.Invoke(() =>
                {
                    listA.RemoveDataAt(1);
                    listB.RemoveDataAt(1);
                    listC.RemoveDataAt(1);
                });
            });
        }
    }
}

DataSource の中身を意識せずに index 指定で選択肢を削除することができました。