我有一个(WPF)带DataGrid的Catel数据窗口,其中SelectedItem属性绑定到VM属性,并且有两个缩进按钮以在选定的数据网格项上启动不同的Catel任务命令。
注意,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代码。
第一次显示窗口时,没有选择数据网格行,并且正如预期的那样,这两个按钮都被禁用。如果选择了第一个数据网格行(通过单击鼠标),那么只有绑定到VM的SelectedPerson属性的一个按钮启用,而令我惊讶的是,另一个按钮保持禁用状态。在选择不同的项目时,两个按钮都被启用,在ctrl - click取消选中行时,VM绑定按钮被禁用,而通过ElementName机制绑定的按钮则不会。
使用调试器断点,我证明了CanExecute函数在窗口初始化和每个项目选择更改时都被调用。然而,通过ElementName引用进行Button绑定的参数在后面单击一次。
如果我在其中一个命令中更改VM SelectedPerson属性,两个按钮都按预期更新,它们的CanExecute处理程序获得正确的项值。
我认为绑定到VM属性并不坏,因为它将在业务逻辑的其他地方有用,但我想了解为什么这两种方法的行为如此不同。
'ElementName'绑定发生了什么,为什么它是一个点击后面?
最后,这是VM
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内部的dispatcher服务自己重新评估命令:
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();
});
}
免责声明:这是在没有编辑器的情况下编写的代码,但希望它能让你知道它应该做什么