CaffGeek
CaffGeek

Reputation: 22064

XAML Style, Setting Behavior Property on DataTrigger

So, I'm new to WPF, so maybe this is trivial but I can't figure it out.

I have a textbox.

<TextBox Text="{Binding NewRateAdjustment.Amount, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True,ValidatesOnExceptions=True}" Style="{StaticResource SurchargeAmountTextBox}" AttachedProperties:TextRules.TextRule ="{StaticResource numericRule}">
    <i:Interaction.Behaviors>
        <gl:NumericTextBoxBehavior DecimalLimit="2" />
    </i:Interaction.Behaviors>
</TextBox>

Now, I need to change the DecimalLimit based upon the choice in a drop down on the page, so I created this Style.

<Style x:Key="SurchargeAmountTextBox" TargetType="{x:Type TextBox}" BasedOn="{StaticResource DefaultTextBox}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=NewRateAdjustment.SelectedRateAdjustment.CalculationMethod.Name, UpdateSourceTrigger=PropertyChanged}" Value="Fuel">
            <Setter Property="Background" Value="Red"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=NewRateAdjustment.SelectedRateAdjustment.CalculationMethod.Name, UpdateSourceTrigger=PropertyChanged}" Value="">
            <Setter Property="Background" Value="Green"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

It seems to work for the colors. But how do write the Property Setter for the DecimalLimit???

Upvotes: 1

Views: 5439

Answers (1)

Arthur Nunes
Arthur Nunes

Reputation: 7058

You can't change a behavior property through a style, but you can try to apply the behavior through a style. The subject has been aborded in other questions, like this, but in your particular case, you want not only to apply the behavior through a style, but apply it with a different configuration depending on the data. In the following approach I will use a attached property to accomplish that. First, a dummy behavior similar to the one you are using:

public class NumericTextBoxBehavior : Behavior<TextBox>
{
    public double DecimalLimit { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();

        // Dummy action so we can see the change when its applied
        this.AssociatedObject.Text = this.DecimalLimit.ToString();
    }
}

Now we create an attached property which is gonna be responsible for applying the behavior (you can do this in another class, or in the behavior class if you have access to it):

public static class NumericTextBoxBehaviorExtension
{
    public static double? GetDecimalLimit(DependencyObject obj)
    {
        return (double?)obj.GetValue(DecimalLimitProperty);
    }
    public static void SetDecimalLimit(DependencyObject obj, double? value)
    {
        obj.SetValue(DecimalLimitProperty, value);
    }
    public static readonly DependencyProperty DecimalLimitProperty =
        DependencyProperty.RegisterAttached("DecimalLimit", typeof(double?), typeof(NumericTextBoxBehaviorExtension), new PropertyMetadata(null, OnDecimalLimitChanged));

    private static void OnDecimalLimitChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        var behaviors = Interaction.GetBehaviors(sender);

        // Remove the existing behavior instances
        foreach (var old in behaviors.OfType<NumericTextBoxBehavior>().ToArray())
            behaviors.Remove(old);

        if (args.NewValue != null)
        {
            // Creates a new behavior and attaches to the target
            var behavior = new NumericTextBoxBehavior { DecimalLimit = (double)args.NewValue };

            // Apply the behavior
            behaviors.Add(behavior);
        }
    }
}

Finally, the following test case will emulate your scenario. We have a TextBox style which is gonna apply a different DecimalLimit depending on the state of the TextBox's DataContext. The xaml:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <Style x:Key="TextBoxStyle" TargetType="TextBox">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Name}" Value="Fuel">
                <Setter Property="local:NumericTextBoxBehaviorExtension.DecimalLimit" Value="10.0"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Name}" Value="">
                <Setter Property="local:NumericTextBoxBehaviorExtension.DecimalLimit" Value="1000.0"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <Button Content="Button" Height="23" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    <TextBox Height="23" HorizontalAlignment="Left" Margin="81,1,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Style="{StaticResource TextBoxStyle}"/>
</Grid>

In the code behind, we will make the button's action swap the TextBox's DataContext to verify that the style will update the behavior correctly:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var target = this.textBox1.DataContext as Target;
        if (this.textBox1.DataContext == null || string.IsNullOrEmpty(target.Name))
        {
            this.textBox1.DataContext = new Target() { Name = "Fuel" };
        }
        else
        {
            this.textBox1.DataContext = new Target() { Name = "" };
        }
    }
}

As you can see, the TextBox's Text will change every time we swap the DataContext, which means the style is indeed aplying the correct behavior.

Upvotes: 4

Related Questions