DataTrigger does not fire on a derived class of a

2019-08-22 17:55发布

问题:

We have a ton of Buttons with an Icon and Text across the App, and I am trying to extract a common style for all of them. I came up with this idea of deriving from a Button and have a couple of dependency properties. This custom button class has a style common to all the Buttons in our Application.

My Custom Button definition:

<Button x:Class="UIElements.Controls.CustomToolBarButton"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" mc:Ignorable="d"
               >
<!--Style="{StaticResource ResourceKey=ToolBarButtonStyle}"-->
<StackPanel Orientation="Horizontal">
    <Image Source="{Binding Icon, Mode=TwoWay}" Style="{StaticResource ResourceKey=ToolBarButtonIconStyle}" />
    <TextBlock Text="{Binding DisplayText, Mode=TwoWay}" Style="{StaticResource ResourceKey=ToolBarButtonDisplayTextStyle}" />
</StackPanel>

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
//using Telerik.Windows.Controls;

namespace UIElements.Controls
{
    public partial class CustomToolBarButton : Button
    {
        public CustomToolBarButton()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        public static readonly DependencyProperty IconProperty =
            DependencyProperty.Register("Icon", typeof(BitmapImage), typeof(Button), new PropertyMetadata(default(BitmapImage)));

        public BitmapImage Icon
        {
            get { return (BitmapImage)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }

        public static readonly DependencyProperty DisplayTextProperty =
            DependencyProperty.Register("DisplayText", typeof(string), typeof(Button), new PropertyMetadata(default(string)));

        public string DisplayText
        {
            get { return (string) GetValue(DisplayTextProperty); }
            set { SetValue(DisplayTextProperty, value); }
        }
    }
}

And I am using this control as follows:

<Controls1:CustomToolBarButton Icon="{DynamicResource ImageSave}"
                        DisplayText="Test Display">
<Controls1:CustomToolBarButton.Style>
    <Style TargetType="Controls1:CustomToolBarButton">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding SelectedListItem.ListId}" Value="0" />
                </MultiDataTrigger.Conditions>
                <!--<Setter Property="Button.IsEnabled" Value="False" />-->
                <Setter Property="Background" Value="BurlyWood" />
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</Controls1:CustomToolBarButton.Style>

Things look good, however the trigger is not firing when the bounded data changes. If I apply the same trigger to the regular button, it seems to be working fine.

Could you please tell me if I am missing something?

EDIT: Found an elegant solution. Made use of Attached Properties

回答1:

Add this line of code to your static constructor

DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomToolBarButton), new FrameworkPropertyMetadata(typeof(CustomToolBarButton)));

http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.defaultstylekey.aspx



回答2:

In your CustomToolBarButton, you are setting it's DataContext to the button itself(this.DataContext = this;). By doing this you have stopped the propagation of the parent(parent control of your button, say grid or UserControl) data-context to your button(which contains the SelectedListItem property).

In case you just want to fix this, try to use RelativeSource or ElementName Binding to access the buttons parent control and then use it's DataContext to access the SelectedListItem.

<Grid x:Name="ParentGrid">

    <Controls1:CustomToolBarButton Icon="{DynamicResource ImageSave}"
                            DisplayText="Test Display">
    <Controls1:CustomToolBarButton.Style>
        <Style TargetType="Controls1:CustomToolBarButton">
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=ParentGrid,
                           Path=DataContext.SelectedListItem.ListId}" Value="0" />
                    </MultiDataTrigger.Conditions>
                    <!--<Setter Property="Button.IsEnabled" Value="False" />-->
                    <Setter Property="Background" Value="BurlyWood" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Controls1:CustomToolBarButton.Style>
</Grid>

The problem is happening as you have used a UserControl to extend the Button; as you said you have a ton of buttons, I would suggest you to create a CustomControl with template, like this; that way you won't have to mess with the DataContext propagation.

Here is a blog explaining different ways of doing exactly what you want -

http://blogs.msdn.com/b/knom/archive/2007/10/31/wpf-control-development-3-ways-to-build-an-imagebutton.aspx



回答3:

First of all you should never use this.DataContext = this;, ever.

Secondly if you inherit from button you should not create a XAML file but a default style in the Themes/Generic.xaml resource dictionary (if you inherit from UserControl a file like this is fine).

This will also mean that you will need to define a Control.Template in the style and hence should use TemplateBindings instead of normal ones (or use RelativeSource).

As noted in another answer you may need to override the metadata.