かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF 4.5の新機能「複数スレッドからのコレクションの操作」

前に試してダメだ〜!と思ってた奴ですが、私のやり方がダメだったみたいです。

BindingOperationsクラスのEnableCollectionSynchronizationメソッドをコレクションに対して呼んでやる必要があったみたいです。ということでコードを書き直してお試し。

プロジェクトを作って、UIスレッド以外からObservableCollectionに要素を追加する処理を持ったViewModelクラスを作成しました。因みにMVVM Light Toolkitを参照に追加してます。

public class MainWindowViewModel
{
    public ObservableCollection<DateTimeHolder> Items { get; private set; }
    public RelayCommand StartAddItemCommand { get; private set; }
    public RelayCommand StopAddItemCommand { get; private set; }

    public MainWindowViewModel()
    {
        this.Items = new ObservableCollection<DateTimeHolder>();
        // Itemsに対して複数スレッドから要素の変更を行っても大丈夫なようにする
        BindingOperations.EnableCollectionSynchronization(this.Items, new object());

        bool isStop = false;
        this.StartAddItemCommand = new RelayCommand(() =>
        {
            // 別スレッドからコレクションにアイテムを追加する
            Task.Run(async () =>
            {
                while (!isStop)
                {
                    this.Items.Add(new DateTimeHolder());
                    await Task.Delay(1000);
                }
                isStop = false;
            });
        });

        this.StopAddItemCommand = new RelayCommand(() =>
        {
            // 追加を辞める
            isStop = true;
        });
    }


}

// 日時を保持するクラス
public class DateTimeHolder
{
    private DateTime value = DateTime.Now;
    public override string ToString()
    {
        return this.value.ToString("yyyy/MM/dd HH:mm:ss");
    }
}

そして、MainWindowで適当にListBoxやButtonとバインドしてやります。

<Window x:Class="ThreadSafeCollectionBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:ThreadSafeCollectionBindingTest"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Content="Start" MinWidth="75" Margin="2.5" Command="{Binding StartAddItemCommand, Mode=OneWay}" />
            <Button Content="Stop" MinWidth="75" Margin="2.5" Command="{Binding StopAddItemCommand, Mode=OneWay}" />
        </StackPanel>
        <ListBox x:Name="listBox" Grid.Row="1" ItemsSource="{Binding Items}"/>

    </Grid>
</Window>

実行してStartボタンを押すと、UIスレッド以外からコレクションに要素を追加してるのに例外が出ずに表示が更新されてることが確認できます。これで、DispatcherCollectionみたいなものを実装しなくて済みますね。