Search code examples
c#wpfxamldatatriggermshtml

How can I dynamically set the path of a DataTrigger XAML


I have several buttons which will be sharing a common style and they all will have DataTriggers based on a bound property. I am trying to avoid writing out each datatrigger for each button, so I would like to know if there is a way to dynamically specify the path of a DataTrigger.Binding?

For example:

<DataTrigger 
Binding="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, 
Path={Binding RelativeSource={RelativeSource Self}, Path=Tag}}" Value="True">
        <!--Do Something-->
</DataTrigger>

I know it may not be possible but I figured I would give it a shot. I am storing the property name as the Tag on each button and the property is supposed to be changed on click. So I want to use the value stored in the Tag name as the Path on the data trigger for the default style. Here is an example button that will bold some text:

<Button Content="B" Tag="IsBold" Click="Button_Click" Style="{StaticResource DefaultButton}"/>

And here is the style used:

<Style TargetType="{x:Type Button}" x:Key="DefaultButton">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="BorderBrush" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Width" Value="20"/>
            <Setter Property="Height" Value="20"/>
            <Setter Property="Margin" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" Padding="2" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="LightBlue"/>
                    <Setter Property="BorderBrush" Value="Blue"/>
                </Trigger>
                <DataTrigger Binding="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path={Binding RelativeSource={RelativeSource Self}, Path=Tag}}" Value="True">

                </DataTrigger>
            </Style.Triggers>
        </Style>

Obviously, the style is invalid at the moment because of the DataTrigger. Is there any way to make this sort of thing completely dynamic or will I have to bind each and every button?

I have tried using ValueConverters and, for what I need, I do not believe they will work since they only fire one time at initialization.

Update with some more information:

I have a data object that works as a sort of view. For all intents and purposes let's say this is all it has in it:

using System;
using mshtml;
using System.Windows.Controls;
using System.Windows.Media;
using System.Reflection;
using System.ComponentModel;

namespace Aspen.Visuals
{
    public class HtmlFormattingExecutor : INotifyPropertyChanged
    {
        public HtmlFormattingExecutor(HTMLDocument doc)
        {
             this.doc = doc;
             this.isBold = false;
        }
        private HTMLDocument doc;
        private Boolean isBold;

        public Boolean IsBold { 
            get {return this.isBold;}
            set
            {
                if(this.isBold != value)
                {
                     this.isBold = value;
                     this.Bold();
                     this.NotifyPropertyChanged("IsBold");
                }
            }
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        private void Bold()
        {
            if (doc != null)
            {
                doc.execCommand("Bold", false, null);
            }
        }
}

That object is set as the data context for a window which has the previously shown Button and Style.

If you would like to see the window code I can show you, otherwise here is the code for the button_click event

private void Button_Click(Object sender, RoutedEventArgs e)
        {
            Button button = e.Source as Button;
            if (button != null)
            {
                this.AlterButtonStyle(button.Tag as String);
                button.Style = (Style)(Window.GetWindow(button).TryFindResource("CurrentStyle"));
            }
        }

private void AlterButtonStyle(String propertyName)
        {
            if (String.IsNullOrWhiteSpace(propertyName)) return;
            PropertyInfo info = this.formatting.GetType().GetProperty(propertyName);
            if (info == null || info.PropertyType != typeof(Boolean)) return;
            info.SetValue(this.formatting, !(Boolean)info.GetValue(this.formatting, null), null);
        }

The "CurrentStyle" shown in the Button_Click event changes based on True or False in the Boolean property

Hope that helps.

Thanks!


Solution

  • This kind of thing is not possible as you have implemented here because you can only set bindings on DependencyProperties, and the Path property of the Binding object is not a DependencyProperty.

    In your case, I would highly recommend making a custom Attached Property for use instead. (It's a type of DependencyProperty that can be attached to any object, not just a specific one)

    For example :

    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(local:CustomProperties.DefaultButtonTriggerProperty)}" 
                     Value="True">
            // setters here
        </DataTrigger>
    </DataTemplate.Triggers>
    

    And

    <Button local:CustomProperties.DefaultButtonTriggerProperty="IsBold" 
            Style="{StaticResource DefaultButton}" ... />
    

    It also has the added advantage of allowing you to give your property a descriptive name, making it clear to other developers what the property is for and how its used. I used DefaultButtonTriggerProperty here, but I'd recommend something more descriptive if you can.