用于动态更改 WPF UserControl 中按钮的启用状态的条件 DataTrigger
我创建了一个 UserControl 用作数据导航器。我在此控件中定义了两个 DependencyProperties,如下所示(隐含 DependencyProperty):
public ICollection DataCollection
{
get { return GetValue(DataCollectionProperty) as ICollection; }
set { SetValue(DataCollectionProperty, value); }
}
public ICollectionView View
{
get { return (DataCollection == null ? null : CollectionViewSource.GetDefaultView(DataCollection)); }
}
然后,我放置了四个按钮来执行基本导航操作(第一个、上一个、下一个、最后一个)。每个按钮都具有以下样式:
<Style x:Key="NavButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataCollection}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
该触发器所做的全部工作是检查 DataCollection DependencyProperty 是否为 null,假设相对资源 TemplatedParent 作为每个按钮的 DataContext 传递,如下所示:
<Button (...) DataContext="{RelativeSource TemplatedParent}">
然后我创建了以下 MarkupExtension 来比较值并返回 true或 false,基于比较操作和比较值:
[MarkupExtensionReturnType(typeof(bool))]
public class ComparisonBinding : BindingDecoratorBase
{
public ComparisonOperation Operation { get; set; }
public object Comparand { get; set; }
public override object ProvideValue(IServiceProvider provider)
{
base.ProvideValue(provider);
DependencyObject targetObject;
DependencyProperty targetProperty;
bool status = TryGetTargetItems(provider, out targetObject, out targetProperty);
if (status && Comparand != null)
{
if (Comparand is MarkupExtension)
Comparand = (Comparand as MarkupExtension).ProvideValue(provider);
return Compare(targetObject.GetValue(targetProperty), Comparand, Operation);
}
return false;
}
private static bool Compare(object source, object target, ComparisonOperation op)
}
最后,我使用此 ME 来测试每个按钮的“启用”条件。以下是First按钮的条件:
<Button (...) DataContext="{RelativeSource TemplatedParent}"
IsEnabled="{DynamicResource {mark:ComparisonBinding Path=View.CurrentPosition, RelativeSource={RelativeSource TemplatedParent}, Comparand={Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataCollection.Count}, Operation=EQ}}">
不幸的是,这个解决方案不起作用。我不断收到此设计时异常:
InvalidOperationException:无法获取不属于视图树一部分的 ViewNode 的 NodePath。
有人有更好的解决方案吗?也许我想用大炮打死一只苍蝇。 :)
提前致谢。 爱德华多·梅洛
I created an UserControl to use as a Data Navigator. I've defined two DependencyProperties in this control as follows (DependencyProperty implied):
public ICollection DataCollection
{
get { return GetValue(DataCollectionProperty) as ICollection; }
set { SetValue(DataCollectionProperty, value); }
}
public ICollectionView View
{
get { return (DataCollection == null ? null : CollectionViewSource.GetDefaultView(DataCollection)); }
}
Then, I've put four buttons to perform the basic navigation operations (first, prev, next, last). Each button gets the following style:
<Style x:Key="NavButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataCollection}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
All this trigger does is to check if the DataCollection DependencyProperty is null, assuming that the RelativeResource TemplatedParent is passed as the DataContext of each button, like this:
<Button (...) DataContext="{RelativeSource TemplatedParent}">
Then I created the following MarkupExtension to compare values and return true or false, based in the comparison operation and compared values:
[MarkupExtensionReturnType(typeof(bool))]
public class ComparisonBinding : BindingDecoratorBase
{
public ComparisonOperation Operation { get; set; }
public object Comparand { get; set; }
public override object ProvideValue(IServiceProvider provider)
{
base.ProvideValue(provider);
DependencyObject targetObject;
DependencyProperty targetProperty;
bool status = TryGetTargetItems(provider, out targetObject, out targetProperty);
if (status && Comparand != null)
{
if (Comparand is MarkupExtension)
Comparand = (Comparand as MarkupExtension).ProvideValue(provider);
return Compare(targetObject.GetValue(targetProperty), Comparand, Operation);
}
return false;
}
private static bool Compare(object source, object target, ComparisonOperation op)
}
Finally, I used this ME to test the "Enabling" conditions for each button. Here's the condition for the First button:
<Button (...) DataContext="{RelativeSource TemplatedParent}"
IsEnabled="{DynamicResource {mark:ComparisonBinding Path=View.CurrentPosition, RelativeSource={RelativeSource TemplatedParent}, Comparand={Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataCollection.Count}, Operation=EQ}}">
Unfortunately, this solution didn't work. I keep getting this design-time exception:
InvalidOperationException: Cannot get NodePath for ViewNode which is not a part of the view tree.
Does anyone have a better solution? Maybe I'm trying to kill a fly with a cannon here. :)
Thanks in advance.
Eduardo Melo
在我看来,实现按钮启用/禁用功能的最佳方法是使用 ICommand 接口并使用 CanExecute 方法。为此,您可以使用轻量级实现,例如 Prism 中的 DelegateCommand 或 MVVMLight 中的 RelayCommand,如果您确实想要最高效率,请不要向 CommandManager 注册命令 - 而是在代码中应提供的特定条件下触发 CanExecuteChanged。
实际上,这意味着您的用户控件将包含(或本身就是)某种微 ViewModel(带有 ICommand 实例及其 Execute 和 CanExecute 方法的实现),无论如何,这都是 WPF 开发的最佳方式。在这种情况下,您在 XAML 中所需要做的就是将按钮的 Command 属性绑定到适当的 ICommand。这也将干净地向任何其他调用者公开命令(从功能角度来看,可以将其视为“任务”),包括单元测试(如果您愿意的话)。
例如,让控件公开 ICommand,并将控件模板内的按钮绑定到这些相同的命令(使用relativesource self)是完全合法的;您甚至可以将它们的可见性绑定到其他一些属性(UseBuiltInButtons),如果您想稍后将控件与一些奇特的界面集成,您可以简单地隐藏按钮并将外部按钮链接到相同的 ICommand。
让我知道这是否有帮助或只是令人困惑,我会尽力阐明这个问题!当然,这只是一个想法,可能还有其他同样好的想法。
In my opinion, the best way to implement enabled/disabled functionality for buttons is by using the ICommand interface and using the CanExecute method. For this purpose you could use a lightweight implementation like the DelegateCommand in Prism or RelayCommand in MVVMLight, and if you really want top efficiency don't register the commands with the CommandManager - instead trigger the CanExecuteChanged on specific conditions which should be available in code.
In practice this means your user control will contain (or itself be) some sort of micro-ViewModel (with the ICommand instances and the implementation for their Execute and CanExecute methods), which is the best way to go in WPF development anyway. In that case all you need in XAML is binding the Command property of the buttons to the appropriate ICommand. This will also cleanly expose the commands (which can be regarded as 'tasks' from a functional viewpoint) to any other callers, including unit tests if you're so inclined.
It's perfectly legal to have ICommands exposed by your control, for example, and have buttons inside your control template bound to those same commands (using RelativeSource Self); you could even bind their visibility to some other property (UseBuiltInButtons), and if you want to integrate your control later with some fancy interface, you can simply hide the buttons and link external ones to the same ICommands.
Let me know if that helps or is just confusing, and I'll try to shed more light on the matter! Of course, this is just an idea and there may be other equally good ones.