尝试在视图之间共享项目时,选定项未正确更新



我有一个场景,可以用不同的方式查看相同的项目集合。也就是说,我们对相同的数据有多个视觉表示。为了保持应用程序在视觉上的整洁,您一次只能查看这些视图中的一个。我遇到的问题是,如果您在查看视图#1时更改所选项目,那么当您切换到视图#2时,所选项目不会正确更新。

我的复制步骤:

  • 在视图#1上选择项目#1。
  • 切换到视图#2 -在这一点上,项目#1被选中
  • 向下滚动到"Item #200"并选择它
  • 切换回视图#1
  • 项目#1仍然会高亮显示,如果你向下滚动到项目#200,它也会高亮显示

似乎当列表框被折叠时,选择的变化没有被拾取。我错过了什么?是否期望PropertyChanged事件不会更新UI元素,如果它们不可见?

下面是我的代码的一个非常简化的版本。基本上,我有一个共享数组,它被绑定到两个不同的ListBox控件。

XAML:

<Window x:Class="SharedListBindingExample.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"
    xmlns:local="clr-namespace:SharedListBindingExample"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0" x:Name="listBox1" ItemsSource="{Binding List1}">
        <ListBox.Resources>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
            </Style>
            <DataTemplate DataType="{x:Type local:SharedListItem}">
                <Grid HorizontalAlignment="Stretch">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Button Background="Red" />
                    <Label Grid.Row="1" Content="{Binding Name}" />
                </Grid>
            </DataTemplate>
        </ListBox.Resources>

    </ListBox>
    <ListBox Grid.Row="0" x:Name="listBox2" ItemsSource="{Binding List2}" Background="AliceBlue" Visibility="Collapsed">
        <ListBox.Resources>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
            </Style>
            <DataTemplate DataType="{x:Type local:SharedListItem}">
                <Label Content="{Binding Name}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"/>
            </DataTemplate>
        </ListBox.Resources>
    </ListBox>
    <Button Grid.Row="1" Click="Button_Click">Toggle View</Button>
</Grid>
</Window>

代码:

using System.Windows;
namespace SharedListBindingExample
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
        InitializeComponent();
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (listBox1.Visibility == Visibility.Collapsed)
        {
            listBox1.Visibility = Visibility.Visible;
            listBox2.Visibility = Visibility.Collapsed;
        }
        else
        {
            listBox2.Visibility = Visibility.Visible;
            listBox1.Visibility = Visibility.Collapsed;
        }
    }
  }
}

ViewModel:

using System.Collections.Generic;
namespace SharedListBindingExample
{
  public class TwoPropertiesForSameListViewModel
  {
    private readonly List<SharedListItem> _sharedList;
    public TwoPropertiesForSameListViewModel()
    {
        _sharedList = new List<SharedListItem>();
        for (int i = 0; i < 300; i++)
        {
            _sharedList.Add(new SharedListItem($"Item #{i}"));
        }
    }
    public IEnumerable<SharedListItem> List1
    {
        get
        {
            return _sharedList;
        }
    }
    public IEnumerable<SharedListItem> List2
    {
        get
        {
            return _sharedList;
        }
    }
  }
}

SharedListItem:

using System.ComponentModel;
namespace SharedListBindingExample
{
  public class SharedListItem : INotifyPropertyChanged
  {
    private bool _isSelected;
    public SharedListItem(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                OnPropertyChanged("IsSelected");
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

我认为您需要在两个不同的视图之间使用CollectionViewSource对象来保持所选项的同步。我在自己的应用程序中也做了类似的事情。

我有一个控件,它定义了一个CollectionViewSource资源在Xaml:

<UserControl.Resources>
    <CollectionViewSource x:Key="DataView" />
</UserControlResources>

控件也有一个DependencyPropertyCollectionViewSource,允许它是数据绑定到其他控件:

public static readonly DataViewProperty =
    DependencyProperty.Register( "DataView", typeof( CollectionViewSource ), typeof( YourControlType ),  new PropertyMetadata( null ) );
public CollectionViewSource DataView {
    get { return (CollectionViewSource) GetProperty( DataViewProperty); }
    set { SetProperty( DataViewProperty, value );
}

然后在组件构造函数中,在调用InitializeComponent之后,必须执行如下代码:

public MyUserControl() {
    InitializeComponent();
    DataView = FindResource( "DataView" ) as CollectionViewSource;
    DataView.Source = YourObservableCollection;
}

在你想共享这个对象的另一个视图中,你创建一个新的CollectionViewSource DependencyProperty。这允许您在具有不同数据视图的窗口中将两个属性相互绑定。在第二个控件中,我有另一个ObservableCollection对象属性,但它没有在控件的构造函数中初始化。我所做的是在控件的Loaded事件处理程序中,我将ObservableCollection属性的值设置为CollectionViewSource对象的Source属性的值。即:

if ( DataCollection == null && DataView != null ) {
    DataCollection = (ObservableCollection<DataType>) DataView.Source;
    DataGrid.ItemsSource = DataView.View;
}

之后,两个控件共享相同的ObservableCollectionCollectionViewSource。这是CollectionViewSource,保持两个控件的选择项目同步。

显然,您可以在任意多个视图中共享CollectionViewSource对象。一个控件必须声明对象,其他控件必须共享。

使用WPF时,建议将IEnumerable s替换为ObservableCollectionICollectionView。您可以在MSDN上找到关于这些集合的更多细节,但我的建议是将一个ListBox的SelectedItem绑定到XAML中的另一个(通过SelectedItem = {Binding ElementName='yourList', Path='SelectedItem'}),以便当一个更改时,另一个将响应(您应该对两个列表都这样做)。我自己从来没有尝试过这种循环绑定,但我认为它在你的情况下会很好。

最新更新