22

私はいくつかのデータの 2 つのビューを持っています: リスト ビュー (ListBox今は、しかし、私は に切り替えるつもりListViewでした) と地図上の派手なグラフィカル表現です。どちらのビューでも、ユーザーがオブジェクトをクリックすると、両方のビューでオブジェクトが選択されます。複数選択も可能であるため、各ViewModelインスタンスには独自のIsSelectedプロパティがあります。

現在、私は にバインドListBoxItem.IsSelectedしていますが、これは が仮想化ViewModel.IsSelectedされていない場合にのみ正しく機能します(こちらを参照)。残念ながら、仮想化を無効にするとパフォーマンスが低下し、アプリが非常に遅くなりました。ListBox

そのため、仮想化を再度有効にする必要があります。ViewModel.IsSelectedオフスクリーン アイテムのプロパティを維持するために、選択状態を から に伝達するために (おそらく) 使用できるイベントがあることにListBox気付きました。ListViewSelectionChangedListBox/ListViewViewModel

私の質問は、選択状態を逆方向に伝播するにはどうすればよいですか? のSelectedItemsプロパティListBox/ListViewは読み取り専用です。ユーザーがグラフィカル表現のアイテムをクリックしたとしますが、それはリストの画面外にあるとします。設定ViewModel.IsSelectedしただけListBox/ListViewでは、新しい選択が認識されず、その結果、ユーザーがリスト内の別の項目をクリックしても、その項目の選択を解除できません。ListBox.ScrollIntoViewから呼び出すことはできますViewModelが、いくつか問題があります。

  • 私の UI では、2 つのアイテムがグラフィック上で同じ場所にある場合、実際には 1 回のクリックで 2 つのアイテムを選択できますが、ListBox/ListView.
  • ViewModel の分離が壊れます (私の ViewModel は WPF をまったく認識していないので、そのままにしておきたいと思います)。

親愛なる WPF の専門家の皆さん、何か考えはありますか?

編集: Infragistics コントロールに切り替えて、醜くてかなり遅いソリューションを使用することになりました。要するに、もう答えは必要ありません。

4

1 に答える 1

29

ViewModelのコレクションと同期するビヘイビアを作成できます。ListBox.SelectedItems

public class MultiSelectionBehavior : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        if (SelectedItems != null)
        {
            AssociatedObject.SelectedItems.Clear();
            foreach (var item in SelectedItems)
            {
                AssociatedObject.SelectedItems.Add(item);
            }
        }
    }

    public IList SelectedItems
    {
        get { return (IList)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));

    private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var behavior = o as MultiSelectionBehavior;
        if (behavior == null)
            return;

        var oldValue = e.OldValue as INotifyCollectionChanged;
        var newValue = e.NewValue as INotifyCollectionChanged;

        if (oldValue != null)
        {
            oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
            behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged;
        }
        if (newValue != null)
        {
            behavior.AssociatedObject.SelectedItems.Clear();
            foreach (var item in (IEnumerable)newValue)
            {
                behavior.AssociatedObject.SelectedItems.Add(item);
            }

            behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged;
            newValue.CollectionChanged += behavior.SourceCollectionChanged;
        }
    }

    private bool _isUpdatingTarget;
    private bool _isUpdatingSource;

    void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (_isUpdatingSource)
            return;

        try
        {
            _isUpdatingTarget = true;

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    AssociatedObject.SelectedItems.Remove(item);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    AssociatedObject.SelectedItems.Add(item);
                }
            }

            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                AssociatedObject.SelectedItems.Clear();
            }
        }
        finally
        {
            _isUpdatingTarget = false;
        }
    }

    private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (_isUpdatingTarget)
            return;

        var selectedItems = this.SelectedItems;
        if (selectedItems == null)
            return;

        try
        {
            _isUpdatingSource = true;

            foreach (var item in e.RemovedItems)
            {
                selectedItems.Remove(item);
            }

            foreach (var item in e.AddedItems)
            {
                selectedItems.Add(item);
            }
        }
        finally
        {
            _isUpdatingSource = false;
        }
    }

}

この動作は、次のように使用できます。

        <ListBox ItemsSource="{Binding Items}"
                 DisplayMemberPath="Name"
                 SelectionMode="Extended">
            <i:Interaction.Behaviors>
                <local:MultiSelectionBehavior SelectedItems="{Binding SelectedItems}" />
            </i:Interaction.Behaviors>
        </ListBox>

SelectedItemsViewModelのコレクションを初期化する必要があることに注意してください。動作によってコレクションが設定されることはなく、コンテンツのみが変更されます)

于 2011-11-11T02:17:36.800 に答える