WPFのバインディングで複数スレッドからコレクションを操作する方法

この記事は公開から3年以上経過しています。

WPFのリストボックスなどにバインディングされているコレクションを複数スレッドから操作する方法。

問題

ItemsCollectionにバインディングされているコレクションに対してUIスレッド以外のスレッドから要素の追加や削除を行うと、画面が更新されないなどプログラムが正常に動作しない。
(バインドするコレクションがObservableCollectionの場合はこの型の CollectionView は、Dispatcher スレッドとは異なるスレッドからその SourceCollection への変更をサポートしません。というエラーが発生する。)

対応

アプリケーション側でコレクションの同期(lock)を行い、WPF側でも同じコレクションとロックオブジェクトを利用できるようにBindingOperations.EnableCollectionSynchronization()を呼び出す。
(.NET Framework 4.5以上)

サンプルソースコード(C#)

MainWindow.xaml

<Window
    x:Class="WPFBindingTest210403.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:local="clr-namespace:WPFBindingTest210403"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <!-- ListBoxにViewModelのItemsプロパティをバインディング -->
        <ListBox x:Name="listBox" ItemsSource="{Binding Items, Mode=OneWay}" />
    </Grid>
</Window>

MainWindow.cs

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

namespace WPFBindingTest210403
{
    public partial class MainWindow : Window
    {
        // ロックオブジェクト
        private readonly object _listBoxLock = new object();

        // ViewModel
        private readonly Vm _vm = new Vm();

        public MainWindow()
        {
            InitializeComponent();

            // ViewModel
            this.DataContext = _vm;

            // WPF側にコレクションとロックオブジェクトを通知
            BindingOperations.EnableCollectionSynchronization(_vm.Items, _listBoxLock);

            // 別スレッドからのコレクション操作
            Task.Run(() =>
            {
                for (var i = 0; i < 10; ++i)
                {
                    lock (_listBoxLock)
                        _vm.Items.Add($"{i}");
                    // 登録動作を視覚的に分かりやすくするためのウエイト
                    Thread.Sleep(500);
                }
            });
        }

        // ViewModel
        private sealed class Vm
        {
            public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();
        }
    }
}

参考ウェブサイトなど

  • Microsoft Docs
    BindingOperations.EnableCollectionSynchronization Method

以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする