0
votes

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!

2
Without a good, minimal, complete code example that clearly illustrates the question, it would be hard or impossible to provide a good answer. For sure, you can't use {Binding} for Path, because it's not a dependency property. You probably can write code-behind that creates the bindings dynamically based on the Tag property you are using, or it's possible you could bind to a proxy object that can be dynamically updated. Without a good code example, providing specific details on those possibilities isn't possible.Peter Duniho
It isn't complete but I added more information to help the anyone out. There is a lot of code to go through at this point so I may just end up doing it in C#, which is what I wanted to avoid. Was looking for a complete XAML solution.Carson
Your code is a bit confusing (why are you binding to Window.Tag if you are setting Button.Tag for example?), but from my understanding I would make a Custom DependencyProperty for storing the value your trigger is based on. It would be much easier for future developers to understand what is going on, and it would scale better if you ever wanted more than one dynamic value for your style.Rachel
@Carson Ah I understand your binding a bit more now. It's not valid so it was confusing me. You are setting the binding's source to the <Window> object using the Source property of the binding, and trying to set the Path property to another bound value ({Self}.Tag in this case). So the code in your example would resolve to an attempt to bind to Window.IsBold if that kind of thing works. But it doesn't since you can only bind to a DependencyProperty, and the Path property of a binding is not a DependencyProperty. I highly recommend the custom DP for this :)Rachel
@Carson Ok, I added it as an example below then, along with a short XAML example :)Rachel

2 Answers

2
votes

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.

0
votes

Can't you write a style for each datatrigger and then point to that style?