通过一次更改将按钮 CommandParameter 绑定到 DataGrid SelectedItem
Binding Button CommandParameter to DataGrid SelectedItem off by one change
我有一个带有 DataGrid 的 (WPF) Catel DataWindow,其中 SelectedItem 属性 绑定到 VM 属性,并且缩进了两个按钮以启动不同的 Catel所选数据网格项上的 TaskCommands。
请注意,CommandParameters 以不同的方式绑定到看起来 - 但不是 - 相同的值:
<catel:DataWindow x:Class="CatelWPFApplication1.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:CatelWPFApplication1.ViewModels"
mc:Ignorable="d"
ShowInTaskbar="True"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
WindowStartupLocation="Manual"
d:DataContext="{d:DesignInstance Type=viewModels:MainWindowViewModel, IsDesignTimeCreatable=True}"
>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="0,6">
<Button Command="{Binding ViaVmCommand}"
CommandParameter="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Content="Binding via VM" />
<Button Command="{Binding ViaElementNameCommand}"
CommandParameter="{Binding SelectedItem, ElementName=PersonDataGrid, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Content="Binding via ElementName"
x:Name="ViaElementButton"/>
</StackPanel>
<DataGrid x:Name="PersonDataGrid"
ItemsSource="{Binding PersonList}"
SelectedItem="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
AutoGenerateColumns="True" />
</StackPanel>
</catel:DataWindow>
为了完整起见,VM 代码如下。
首次显示 Window 时,未选择任何数据网格行,并且正如预期的那样,两个按钮都被禁用。如果第一个数据网格行被选中(通过单击鼠标),只有绑定到 VM 的 SelectedPerson 属性 的一个按钮被启用,而令我惊讶的是另一个按钮保持禁用状态。选择不同的项目时,两个按钮都会启用,而在 Crtl-Clicking 取消选择所选行时,VM 绑定按钮会被禁用,而通过 ElementName 机制绑定的按钮不会。
使用调试器断点,我证明了两个 CanExecute 函数在 window 初始化和每个项目选择更改时都会被调用。然而,通过 ElementName 引用绑定 Button 的参数却落后了一次点击。
如果我在其中一个命令中更改 VM SelectedPerson 属性,两个按钮都会按预期更新,它们的 CanExecute 处理程序会获得正确的项目值。
我发现绑定到 VM 属性 不错,因为它在业务逻辑的其他地方很有用,但我喜欢了解为什么这两种方法的行为如此不同。
'ElementName'绑定是怎么回事,为什么一键后退?
最后,这是虚拟机
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Catel.Data;
using Catel.MVVM;
using CatelWPFApplication1.Models;
namespace CatelWPFApplication1.ViewModels
{
/// <summary>
/// MainWindow view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{
#region Fields
#endregion
#region Constructors
public MainWindowViewModel()
{
ViaVmCommand = new TaskCommand<Person>(OnViaVmCommandExecute, OnViaVmCommandCanExecute);
ViaElementNameCommand = new TaskCommand<Person>(OnViaElementNameCommandExecute, OnViaElementNameCommandCanExecute);
PersonList = new List<Person>();
PersonList.Add(new Person(){FirstName = "Albert", LastName = "Abraham"});
PersonList.Add(new Person(){FirstName = "Betty", LastName = "Baboa"});
PersonList.Add(new Person(){FirstName = "Cherry", LastName="Cesar"});
}
#endregion
#region Properties
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "View model title"; } }
public List<Person> PersonList { get; }
// classic Catel property, avoiding any magic with Fody weavers
public Person SelectedPerson
{
get { return GetValue<Person>(SelectedPersonProperty); }
set
{
SetValue(SelectedPersonProperty, value);
}
}
public static readonly PropertyData SelectedPersonProperty = RegisterProperty(nameof(SelectedPerson), typeof(Person), null);
#endregion
#region Commands
public TaskCommand<Person> ViaElementNameCommand { get; }
private bool OnViaElementNameCommandCanExecute(Person person)
{
return person is not null;
}
private async Task OnViaElementNameCommandExecute(Person person)
{
SelectedPerson = null;
}
public TaskCommand<Person> ViaVmCommand { get; }
private bool OnViaVmCommandCanExecute(Person person)
{
return person is not null;
}
private async Task OnViaVmCommandExecute(Person person)
{
SelectedPerson = PersonList.FirstOrDefault();
}
#endregion
}
}
我认为这是由命令得到(重新)评估的时刻引起的。
您可能将 InvalidateCommandsOnPropertyChange
属性 设置为 true
(默认值)。有关详细信息,请参阅 https://github.com/catel/catel/blob/develop/src/Catel.MVVM/MVVM/ViewModels/ViewModelBase.cs#L1016
在这个阶段,绑定可能还没有机会更新自己,因此发送以前的版本。
此问题的解决方法是使用 VM 内的调度程序服务自行重新评估命令:
在构造函数中:
private readonly IDispatcherService _dispatcherService;
public MainWindowViewModel(IDispatcherService dispatcherService)
{
Argument.IsNotNull(() => dispatcherService);
_dispatcherService = dispatcherService;
}
覆盖 OnPropertyChanged:
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
// Don't alter current behavior by calling base
base.OnPropertyChanged(e);
// Dispatcher so the bindings get a chance to update
_dispatcherService.BeginInvoke(await () =>
{
await Task.Delay(10);
ViewModelCommandManager.InvalidateCommands();
});
}
免责声明:这是在没有编辑器的情况下编写的代码,但希望它能让您了解它应该做什么
我有一个带有 DataGrid 的 (WPF) Catel DataWindow,其中 SelectedItem 属性 绑定到 VM 属性,并且缩进了两个按钮以启动不同的 Catel所选数据网格项上的 TaskCommands。
请注意,CommandParameters 以不同的方式绑定到看起来 - 但不是 - 相同的值:
<catel:DataWindow x:Class="CatelWPFApplication1.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:CatelWPFApplication1.ViewModels"
mc:Ignorable="d"
ShowInTaskbar="True"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
WindowStartupLocation="Manual"
d:DataContext="{d:DesignInstance Type=viewModels:MainWindowViewModel, IsDesignTimeCreatable=True}"
>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="0,6">
<Button Command="{Binding ViaVmCommand}"
CommandParameter="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Content="Binding via VM" />
<Button Command="{Binding ViaElementNameCommand}"
CommandParameter="{Binding SelectedItem, ElementName=PersonDataGrid, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Content="Binding via ElementName"
x:Name="ViaElementButton"/>
</StackPanel>
<DataGrid x:Name="PersonDataGrid"
ItemsSource="{Binding PersonList}"
SelectedItem="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
AutoGenerateColumns="True" />
</StackPanel>
</catel:DataWindow>
为了完整起见,VM 代码如下。
首次显示 Window 时,未选择任何数据网格行,并且正如预期的那样,两个按钮都被禁用。如果第一个数据网格行被选中(通过单击鼠标),只有绑定到 VM 的 SelectedPerson 属性 的一个按钮被启用,而令我惊讶的是另一个按钮保持禁用状态。选择不同的项目时,两个按钮都会启用,而在 Crtl-Clicking 取消选择所选行时,VM 绑定按钮会被禁用,而通过 ElementName 机制绑定的按钮不会。
使用调试器断点,我证明了两个 CanExecute 函数在 window 初始化和每个项目选择更改时都会被调用。然而,通过 ElementName 引用绑定 Button 的参数却落后了一次点击。
如果我在其中一个命令中更改 VM SelectedPerson 属性,两个按钮都会按预期更新,它们的 CanExecute 处理程序会获得正确的项目值。
我发现绑定到 VM 属性 不错,因为它在业务逻辑的其他地方很有用,但我喜欢了解为什么这两种方法的行为如此不同。
'ElementName'绑定是怎么回事,为什么一键后退?
最后,这是虚拟机
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Catel.Data;
using Catel.MVVM;
using CatelWPFApplication1.Models;
namespace CatelWPFApplication1.ViewModels
{
/// <summary>
/// MainWindow view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{
#region Fields
#endregion
#region Constructors
public MainWindowViewModel()
{
ViaVmCommand = new TaskCommand<Person>(OnViaVmCommandExecute, OnViaVmCommandCanExecute);
ViaElementNameCommand = new TaskCommand<Person>(OnViaElementNameCommandExecute, OnViaElementNameCommandCanExecute);
PersonList = new List<Person>();
PersonList.Add(new Person(){FirstName = "Albert", LastName = "Abraham"});
PersonList.Add(new Person(){FirstName = "Betty", LastName = "Baboa"});
PersonList.Add(new Person(){FirstName = "Cherry", LastName="Cesar"});
}
#endregion
#region Properties
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "View model title"; } }
public List<Person> PersonList { get; }
// classic Catel property, avoiding any magic with Fody weavers
public Person SelectedPerson
{
get { return GetValue<Person>(SelectedPersonProperty); }
set
{
SetValue(SelectedPersonProperty, value);
}
}
public static readonly PropertyData SelectedPersonProperty = RegisterProperty(nameof(SelectedPerson), typeof(Person), null);
#endregion
#region Commands
public TaskCommand<Person> ViaElementNameCommand { get; }
private bool OnViaElementNameCommandCanExecute(Person person)
{
return person is not null;
}
private async Task OnViaElementNameCommandExecute(Person person)
{
SelectedPerson = null;
}
public TaskCommand<Person> ViaVmCommand { get; }
private bool OnViaVmCommandCanExecute(Person person)
{
return person is not null;
}
private async Task OnViaVmCommandExecute(Person person)
{
SelectedPerson = PersonList.FirstOrDefault();
}
#endregion
}
}
我认为这是由命令得到(重新)评估的时刻引起的。
您可能将 InvalidateCommandsOnPropertyChange
属性 设置为 true
(默认值)。有关详细信息,请参阅 https://github.com/catel/catel/blob/develop/src/Catel.MVVM/MVVM/ViewModels/ViewModelBase.cs#L1016
在这个阶段,绑定可能还没有机会更新自己,因此发送以前的版本。
此问题的解决方法是使用 VM 内的调度程序服务自行重新评估命令:
在构造函数中:
private readonly IDispatcherService _dispatcherService;
public MainWindowViewModel(IDispatcherService dispatcherService)
{
Argument.IsNotNull(() => dispatcherService);
_dispatcherService = dispatcherService;
}
覆盖 OnPropertyChanged:
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
// Don't alter current behavior by calling base
base.OnPropertyChanged(e);
// Dispatcher so the bindings get a chance to update
_dispatcherService.BeginInvoke(await () =>
{
await Task.Delay(10);
ViewModelCommandManager.InvalidateCommands();
});
}
免责声明:这是在没有编辑器的情况下编写的代码,但希望它能让您了解它应该做什么