简体   繁体   English

条件DataTrigger,用于动态更改WPF UserControl中按钮的启用状态

[英]Conditional DataTrigger for dynamic changing the enabled state of a button in WPF UserControl

I created an UserControl to use as a Data Navigator. 我创建了一个UserControl用作数据浏览器。 I've defined two DependencyProperties in this control as follows (DependencyProperty implied): 我在此控件中定义了两个DependencyProperty,如下所示(隐含了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)); }
}

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: 所有此触发器所做的就是检查DataCollection DependencyProperty是否为null,并假设RelativeResource TemplatedParent作为每个按钮的DataContext传递,如下所示:

<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: 然后,我根据比较操作和比较值创建了以下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)
}

Finally, I used this ME to test the "Enabling" conditions for each button. 最后,我使用此ME来测试每个按钮的“启用”条件。 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. InvalidOperationException:无法获取不属于视图树的ViewNode的NodePath。

Does anyone have a better solution? 有谁有更好的解决方案? Maybe I'm trying to kill a fly with a cannon here. 也许我想在这里用大炮杀死苍蝇。 :) :)

Thanks in advance. 提前致谢。 Eduardo Melo 爱德华多·梅洛

In my opinion, the best way to implement enabled/disabled functionality for buttons is by using the ICommand interface and using the CanExecute method. 我认为,实现按钮的启用/禁用功能的最佳方法是使用ICommand接口并使用CanExecute方法。 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. 为此,您可以使用轻量级的实现,例如Prism中的DelegateCommand或MVVMLight中的RelayCommand,如果您确实想要最高效率,请不要在CommandManager中注册命令-而是在特定条件下触发CanExecuteChanged,该条件应在代码中可用。

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. 实际上,这意味着您的用户控件将包含(或本身就是)某种类型的micro-ViewModel(具有ICommand实例以及其Execute和CanExecute方法的实现),无论如何,这是进行WPF开发的最佳方法。 In that case all you need in XAML is binding the Command property of the buttons to the appropriate ICommand. 在这种情况下,您在XAML中所需要做的就是将按钮的Command属性绑定到适当的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); 例如,使ICommands由控件公开并在控件模板中将按钮绑定到相同的命令是完全合法的(使用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. 您甚至可以将其可见性绑定到其他属性(UseBuiltInButtons),并且如果您以后想要将控件与一些精美的界面集成,则只需隐藏按钮并将外部按钮链接到相同的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. 当然,这只是一个主意,可能还有其他同样不错的主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM