尝试在视图之间共享项目时,SelectedItem无法正确更新

时间:2015-10-27 16:19:16

标签: c# wpf

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

我的再现步骤:

  • 在视图#1上选择项目#1。
  • 切换到视图#2 - 此时选择了项目#1
  • 向下滚动到“Item#200”并选择它
  • 切换回View#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;
        }
    }
  }
}

视图模型:

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));
    }
  }
}

2 个答案:

答案 0 :(得分:2)

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

我有一个控件在Xaml中定义CollectionViewSource资源:

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

该控件还有DependencyProperty CollectionViewSource,允许它与其他控件绑定数据:

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。这允许您在具有不同数据视图的窗口中将两个proeprties绑定到彼此。在我的第二个控件中,我有另一个ObservableCollection对象属性,但它没有在控件的构造函数中初始化。我所做的是在控件的Loaded事件处理程序中,我将ObservableCollection属性的值设置为CollectionViewSource对象的Source属性的值。那就是:

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

在此之后,两个控件共享相同的ObservableCollection和相同的CollectionViewSourceCollectionViewSource使两个控件的所选项保持同步。

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

答案 1 :(得分:0)

使用WPF时,建议您为IEnumerableObservableCollection删除ICollectionView。您可以在MSDN上找到有关这些集合的更多详细信息,但我的建议是将一个ListBox的SelectedItem绑定到XAML中的另一个(SelectedItem = {Binding ElementName='yourList', Path='SelectedItem'}),这样当一个更改时,另一个将响应(你应该为这两个列表做这个)。我自己从未尝试过这种循环绑定,但我认为在你的情况下它会正常工作。