在数据触发器中没有一致地调用Setter
本文关键字:调用 Setter 数据 触发器 | 更新日期: 2023-09-27 18:05:29
注:改写为MCVE
我有一个这样的UI布局,其目的是隐藏ListView
,如果它是空的或显示它,并选择第一项,如果它不是空的
<Window x:Class="BrokenSelection.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:BrokenSelection"
mc:Ignorable="d"
SizeToContent="WidthAndHeight"
Title="MainWindow">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<ListView x:Name="MyListView" ItemsSource="{Binding Items}">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
<Button Content="Set" Click="Button_Set"/>
<Button Content="Clear" Click="Button_Clear"/>
<Button Content="Step" Click="Button_Step"/>
</StackPanel>
</Window>
我有一个这样实现的VM
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<string> items = new List<string>();
public List<string> Items
{
get
{
return items;
}
set
{
items = value;
PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs("Items"));
}
}
}
后面有这样的代码
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Set(object sender, RoutedEventArgs e)
{
var l = new List<string>();
l.Add("Item1");
l.Add("Item2");
l.Add("Item3");
l.Add("Item4");
l.Add("Item5");
var vm = DataContext as ViewModel;
vm.Items = l;
}
private void Button_Clear(object sender, RoutedEventArgs e)
{
var vm = DataContext as ViewModel;
vm.Items = new List<string>();
}
private void Button_Step(object sender, RoutedEventArgs e)
{
MyListView.SelectedIndex++;
}
}
我的问题是SelectedIndex
的Setter
成功,只要我点击"设置","清除","设置","清除"等…
然而,如果我点击"set","step","clear","set",SelectedIndex
在第二个"set"上最终是-1,而不是0。当列表消失并正确重新出现时,另一个Setter
执行。为什么手动改变SelectedIndex
打破setter为它在我的触发器?
这对我来说很好。请参阅下面我的最小化、完整和可验证代码示例:
class ViewModel : INotifyPropertyChanged
{
private bool _hasItems;
public bool HasItems
{
get { return _hasItems; }
set { _UpdateValue(ref _hasItems, value); }
}
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
private void _UpdateValue<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!object.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.DynamicInvoke(this, new PropertyChangedEventArgs(propertyName));
if (propertyName == nameof(HasItems))
{
if (HasItems)
{
Items.Add("test item");
}
else
{
Items.Clear();
}
}
}
}
}
<Window x:Class="TestSO39298712SelectedIndexSetter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="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:l="clr-namespace:TestSO39298712SelectedIndexSetter"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<StackPanel>
<CheckBox IsChecked="{Binding HasItems}" Content="HasItems"/>
<TextBlock Text="{Binding SelectedIndex, ElementName=listBox1, StringFormat=SelectedIndex: {0}}"/>
<ListView x:Name="listBox1" ItemsSource="{Binding Items}">
<ListView.Style>
<p:Style TargetType="ListView">
<Setter Property="Visibility" Value="Collapsed"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</ListView.Style>
</ListView>
</StackPanel>
</Window>
我将您提供的代码直接复制到这个示例中。我所做的就是添加足够的其他代码,这样我就有了一个工作示例,可以演示SelectedIndex
属性是否实际上发生了变化。
你可以回顾一下上面的代码,看看你是否能发现你的代码有什么不同。如果你仍然无法找出你犯了什么错误,请修复你的问题,使它包括一个好的MCVE,像上面一样,但可靠地再现问题。
提前到Peter的回答,他没有分配新的ObservableCollection
实例每次集合变化,他只是添加/删除项目,这可能是使他的例子工作(通常这是正确的方式使用ObservableCollection
,更改集合本身,而不是实例化一个新的并分配它)。
根据您在问题中提供的内容,我怀疑您在SelectedIndex
属性中看到的-1值是由空ObservableCollection
的分配引起的。如你所述:
在每次赋值给新集合1之间或更多项,我首先将它赋值给一个空集合(不是null)
我不能100%确定我的答案,除非你能像Peter提到的那样在一个好的MCVE中提供你的代码。
已更新
我可以重现问题,如果我加载集合,滚动,清除,然后再次加载,触发器不设置SelectedIndex
MainWindow.xaml
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="525">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Vertical">
<Button Click="Load_Button_Click">Load</Button>
<Button Click="Clear_Button_Click">Clear</Button>
<Button Click="Scroll_Button_Click">Scroll</Button>
</StackPanel>
<ListView DockPanel.Dock="Bottom" Name="SearchResultListView" ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="60" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Email" Width="140" DisplayMemberBinding="{Binding Email}" />
</GridView>
</ListView.View>
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
private void Clear_Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<User> items = new ObservableCollection<User>();
SearchResultListView.ItemsSource = items;
}
private void Load_Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<User> items = new ObservableCollection<User>();
items.Add(new User() { Name = "Smith", Age = 18, Email = "smith@test.com" });
items.Add(new User() { Name = "Joe", Age = 19, Email = "joe@test.com" });
items.Add(new User() { Name = "Walter", Age = 17, Email = "walter@test.com" });
items.Add(new User() { Name = "Tim1", Age = 44, Email = "tim1@test.com" });
items.Add(new User() { Name = "Tim2", Age = 44, Email = "tim2@test.com" });
items.Add(new User() { Name = "Tim3", Age = 44, Email = "tim3@test.com" });
items.Add(new User() { Name = "Tim4", Age = 44, Email = "tim4@test.com" });
items.Add(new User() { Name = "Tim5", Age = 44, Email = "tim5@test.com" });
items.Add(new User() { Name = "Tim6", Age = 44, Email = "tim6@test.com" });
items.Add(new User() { Name = "Tim7", Age = 44, Email = "tim7@test.com" });
items.Add(new User() { Name = "Tim8", Age = 44, Email = "tim8@test.com" });
items.Add(new User() { Name = "Tim9", Age = 44, Email = "tim9@test.com" });
items.Add(new User() { Name = "Tim10", Age = 44, Email = "tim10@test.com" });
items.Add(new User() { Name = "Tim11", Age = 44, Email = "tim11@test.com" });
items.Add(new User() { Name = "Tim12", Age = 44, Email = "tim12@test.com" });
SearchResultListView.ItemsSource = items;
}
private void Scroll_Button_Click(object sender, RoutedEventArgs e)
{
if (SearchResultListView.SelectedIndex < this.SearchResultListView.Items.Count - 1)
{
SearchResultListView.SelectedIndex++;
SearchResultListView.ScrollIntoView(this.SearchResultListView.SelectedItem);
}
else
{
SearchResultListView.SelectedIndex = 0;
SearchResultListView.ScrollIntoView(this.SearchResultListView.SelectedItem);
}
}
}
我终于找到了这个奇怪问题的解决办法。如果您设置了IsSynchronizedWithCurrentItem="True"
,那么所选内容不会神秘地设置回-1。