3

プロジェクトをWindowsフォームからWPF形式に変換しています。現在、すべてのデータを要素にバインドしています。私は今、次のように問題を提起しObservableCollectionます:

このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからのSourceCollectionへの変更をサポートしていません。

コードをスレッドセーフにするにはどうすればよいですか?または、ディスパッチャスレッドへの変更をどのようにガイドしますか?私はそれについていくつかの投稿を見ましたが、私はそれを自分のプロジェクトに適用する方法について混乱しています。多分誰かが私のためにこれにいくつかの光を当てることができますか?

これは私のコードですObservableList.cs

public class ObservableList<T> : ObservableCollection<T>
{
    #region Private members

    bool isInAddRange = false;

    #endregion Private members

    #region Public methods

    /// <summary>
    /// Creates a new empty ObservableList of the provided type. 
    /// </summary>
    public ObservableList()
    {

    }

    /// <summary>
    /// Handles the event when a collection has changed.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // intercept this when it gets called inside the AddRange method.
        if (!isInAddRange)
            base.OnCollectionChanged(e);
    }

    /// <summary>
    /// Adds a collection of items to the ObservableList.
    /// </summary>
    /// <param name="items"></param>
    public void AddRange(IEnumerable<T> items)
    {
        isInAddRange = true;
        foreach (T item in items)
        {
            Add(item);
        }

        isInAddRange = false;

        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
        base.OnCollectionChanged(e);

    }

    #endregion Public methods
}

}

編集:ywmによって与えられた答えの後、私はAddRangeクラスを次のように変更しました:

public void AddRange(IEnumerable<T> items)
{
    isInAddRange = true;
    foreach (T item in items)
    {
        if (item != null)
        {
            Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
                {
                    Add(item);
                }));
        }
    }

    isInAddRange = false;

    var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
    base.OnCollectionChanged(e);

}

これで、すべてのObservableListはnullです。

4

3 に答える 3

4

にアイテムを追加するときはObservableCollection、UIディスパッチャースレッドを呼び出す必要があります。

これは次のように行われます。

  Dispatcher.CurrentDispatcher.Invoke(() =>
  {
        foreach (var myModel in itemsToAdd)
        {
                    Images.Add(mymodel);                   
        } 
  });

次に、それを使用するクラスで、

    public ObservableList<String> Strings { get; set; }

    public MyViewModel()
    {
        this.Strings = new ObservableList<string>();

        this.Strings.AddRange(new[] { "1", "2", "3", "4" });
    }
于 2013-02-22T12:55:11.160 に答える
0

UIスレッドでAddメソッドを呼び出している間も、呼び出し元のスレッドのAddRangeメソッドでイベントを発生させています。したがって、変更を加える前と同じ問題が発生します。

これを試して:

public void AddRange(IEnumerable<T> items)
{
    isInAddRange = true;
    foreach (T item in items)
    {
        if (item != null)
        {
            Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
                {
                    Add(item);
                }));
        }
    }

    isInAddRange = false;

    var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
    Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
    {
        base.OnCollectionChanged(e);
    });

}
于 2013-02-22T16:43:30.183 に答える
0

私はここできれいな解決策を見つけました。

ここでおそらく発生した問題は、変更時にUIスレッドを呼び出す必要があるということではなく、コレクションを作成したスレッドです。そして、それは必ずしもUIスレッドである必要はありません!

そこで、コードを次のように変更しました。

public class ObservableList<T> : ObservableCollection<T>
{
    #region Private members

    bool isInAddRange = false;
    private readonly Dispatcher _currentDispatcher;

    #endregion Private members

    #region Public methods

    /// <summary>
    /// Creates a new empty ObservableList of the provided type. 
    /// </summary>
    public ObservableList()
    {
        //Assign the current Dispatcher (owner of the collection) 
        _currentDispatcher = Dispatcher.CurrentDispatcher;
    }

    /// <summary>
    /// Executes this action in the right thread
    /// </summary>
    ///<param name="action">The action which should be executed</param>
    private void DoDispatchedAction(Action action)
    {
        if (_currentDispatcher.CheckAccess())
            action.Invoke();
        else
            _currentDispatcher.Invoke(DispatcherPriority.DataBind, action);
    }

    /// <summary>
    /// Handles the event when a collection has changed.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // intercept this when it gets called inside the AddRange method.
        if (!isInAddRange)
        {
            DoDispatchedAction(() => base.OnCollectionChanged(e));
        }
    }

    /// <summary>
    /// Adds a collection of items to the ObservableList.
    /// </summary>
    /// <param name="items"></param>
    public void AddRange(IEnumerable<T> items)
    {
        isInAddRange = true;
        foreach (T item in items)
        {
            if (item != null)
                {
                    Add(item);
            }
        }       

        isInAddRange = false;

        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
        DoDispatchedAction(() => base.OnCollectionChanged(e));

    }

    #endregion Public methods
}

AddRange()私はまだメソッド自体をテストしていないことを認めます。リンク先のサイトにあるサンプルは次のとおりです。

/// <summary>
/// Inserts a item at the specified index
/// </summary>
///<param name="index">The index where the item should be inserted</param>
///<param name="item">The item which should be inserted</param>
protected override void InsertItem(int index, T item)
{
    DoDispatchedAction(() => base.InsertItem(index, item));
}

私を助けてくれてありがとう!

于 2013-02-25T13:07:32.633 に答える