1

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???

CaffGeek
  • 21,856
  • 17
  • 100
  • 184

1 Answers1

4

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.

Community
  • 1
  • 1
Arthur Nunes
  • 6,718
  • 7
  • 33
  • 46
  • It's not a DependencyProperty. I had already tried just adding a property to the ViewModel and binding to that. It failed. – CaffGeek Nov 16 '12 at 16:01
  • Let's try using an attached property, then! See the edited answer. – Arthur Nunes Nov 16 '12 at 16:20
  • Mind that in the second approach, the attached property will apply a new behavior instance through the style, not change the "DecimalLimit" of an existing behavior instance. It should work, because it will be essentialy the same case of the working example. – Arthur Nunes Nov 17 '12 at 22:21
  • one more question, is there a way to have `Value="Fuel"` and a default case that's for when it's not fuel? – CaffGeek Nov 19 '12 at 14:50
  • You can put a common setter to the property "NumericTextBoxBehaviorExtension.DecimalLimit" in the style and use the triggers to change it for particular cases. The way the logic in the method "OnDecimalLimitChanged" was implemented should remove any previous attached instances of the behavior, so the setter in the triggers should override the style setter. – Arthur Nunes Nov 19 '12 at 15:16
  • What should we do if we have more than just one properties to set for the behavior, in my case, I need to set four properties of the behavior. But need to apply one property conditionally. So is it that I should change all those 4 properties to be attached properties? – Randeep Singh May 27 '15 at 09:56