Search code examples
c#wpfbehavior

Better way to implement a behavior


I'm relatively new to WPF and Behaviors.

I have this behavior, I need to execute DoSomething() every time I set IsRedundant in the ViewModel.

Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if ture => set it to false, If false => set it to true). IsRedundant only used to raise the property changed event and for nothing else.

Is there a better way of achieving this ?

Any ideas ?

wpf

  <i:Interaction.Behaviors>
                    <local:UIElementBehavior  Redundant="{Binding IsRedundant, Mode=TwoWay}"/ >
   </i:Interaction.Behaviors>

C#

class UIElementBehavior : Behavior<UIElement>
    {
        public static readonly DependencyProperty RedundantProperty = DependencyProperty.Register(
                "Redundant",
                typeof(bool),
                typeof(UIElementBehavior),
                new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DoSomething));

        public bool Redundant
        {
            get { return (bool)GetValue(RedundantProperty); }
            set { SetValue(RedundantProperty, value); }
        }

        private static void DoSomething(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Do something on the AssociatedObject
        }

    }

Solution

  • Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if true => set it to false, If false => set it to true)

    The problem is that you are using binding. Binding required target to be dependency property. And those are special, their setters aren't called, so you have to use callback to get informed when their value is changed via binding.

    Moreover there is internally a check if value is different, for performance reasons callback is not called if value is the same, so you must change it as you do already.


    An alternative solution is to simply add event in the view model:

    public class ViewModel: INotifyPropertyChanged
    {
         public EventHandler SomethingHappens;
         // call this to tell something to listener if any (can be view or another view model)
         public OnSomethingHappens() => SomethingHappens?.Invoke(this, EventArgs.Empty);
    
         ...
    }
    

    Now you can subscribe/unsubscribe in the view to/from this event and do something in the event handler. If you are purist, then refactor code from the view into reusable behavior.

    Is it shorter? Nope. Is it more clear? Yes, compared to using bool and such "wonderful" code:

    IsRedundant = false;
    IsRedundant = true; // lol
    

    I was using bool properties like you do to inform the view in the past.

    Then I used events.

    Now I use combination of both. Every view model already implements INotifyPropertyChanged so why not use it?

    Think about IsRedundant as a state. It can be used not only to trigger some method, but also used by the view to run animations via data triggers, control visibility of dynamic layout, etc. So you need a normal bool property in view model.

    The view then can subscribe to/unsubscribe from PropertyChanged and simply have to check:

    if(e.PropertyName == nameof(ViewModel.IsRedudant)) { ... }