当试图在视图之间共享项时,SelectedItem不能正确更新
本文关键字:SelectedItem 不能 更新 视图 共享 之间 | 更新日期: 2023-09-27 18:19:24
我有一个场景,可以用不同的方式查看相同的项目集合。也就是说,我们对相同的数据有多个视觉表示。为了保持应用程序在视觉上的整洁,您一次只能查看这些视图中的一个。我遇到的问题是,如果您在查看视图#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>
控件也有一个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
。这允许您在具有不同数据视图的窗口中将两个属性相互绑定。在第二个控件中,我有另一个ObservableCollection
对象属性,但它没有在控件的构造函数中初始化。我所做的是在控件的Loaded
事件处理程序中,我将ObservableCollection
属性的值设置为CollectionViewSource
对象的Source属性的值。即:
if ( DataCollection == null && DataView != null ) {
DataCollection = (ObservableCollection<DataType>) DataView.Source;
DataGrid.ItemsSource = DataView.View;
}
之后,两个控件共享相同的ObservableCollection
和CollectionViewSource
。这是CollectionViewSource
,保持两个控件的选择项目同步。
显然,您可以在任意多个视图中共享CollectionViewSource
对象。一个控件必须声明对象,其他控件必须共享。
使用WPF时,建议将IEnumerable
s替换为ObservableCollection
或ICollectionView
。您可以在MSDN上找到关于这些集合的更多细节,但我的建议是将一个ListBox的SelectedItem
绑定到XAML中的另一个(通过SelectedItem = {Binding ElementName='yourList', Path='SelectedItem'}
),以便当一个更改时,另一个将响应(您应该对两个列表都这样做)。我自己从来没有尝试过这种循环绑定,但我认为它在你的情况下会很好。