Search code examples
c#.netwpfdata-binding

WPF: Creating a custom button with automatic bindings from c# code


I have a large number of buttons that have all the same binding schema based on a key string. I thought I could save some code duplication by making a custom control that takes that string and sets all the bindings accordingly. I came up with the following code:

public class StateTransitionButton : Button
{
    public string StateTransition
    {
        get { return (string)this.GetValue(StateTransitionProperty); }
        set { this.SetValue(StateTransitionProperty, value); }
    }

    public static readonly DependencyProperty StateTransitionProperty =
        DependencyProperty.Register("MyProperty", typeof(string), typeof(StateTransitionButton), new PropertyMetadata(null, OnTransitionChanged));

    private static void OnTransitionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is StateTransitionButton button && e.NewValue is string key)
        {
            button.CommandParameter = key;

            Binding commandBinding = new("ButtonClicked");
            commandBinding.Source = button.DataContext;
            _ = button.SetBinding(CommandProperty, commandBinding);
            button.CommandParameter = key;

            Binding visibilityBinding = new("CurrentState")
            {
                Converter = new UIStateToVisibilityConverter(),
                ConverterParameter = key
            };
            visibilityBinding.Source = button.DataContext;
            _ = button.SetBinding(VisibilityProperty, visibilityBinding);

            Binding tooltipBinding = new("CurrentState")
            {
                Converter = new UIStateToTooltipConverter(),
                ConverterParameter = key
            };
            tooltipBinding.Source = button.DataContext;
            _ = button.SetBinding(ToolTipProperty, tooltipBinding);
        }
    }
}

The converters are already used in the old code and work as intended. In the code above the bindings appear not to be set correctly. When I check them in snoop the command binding has an error (i never figured out how to get usable error text from snoop), visibility still has the default value and not a binding and the tooltip property is not inspectable.

Update: I figured this out as I was finishing up writing it. I will post it anyway and answer it myself because I couldn't find any nice clear examples of how to set up bindings inside a custom control and other might find my solution useful. Please mark this as a duplicate if there is a better one I missed.


Solution

  • So it turns out the StateTransition property was getting set before the datacontext. This means all the bindings were binding onto null. The fix is to listen to the DataContextChanged event and rebind. This is more correct anyway even if the original code had worked because otherwise it would not handle datacontext changes.

    Working example:

    public class StateTransitionButton : Button
    {
        public StateTransitionButton()
        {
            this.DataContextChanged += this.OnDataContextChanged;
        }
    
        public string StateTransition
        {
            get { return (string)this.GetValue(StateTransitionProperty); }
            set { this.SetValue(StateTransitionProperty, value); }
        }
    
        public static readonly DependencyProperty StateTransitionProperty =
            DependencyProperty.Register("MyProperty", typeof(string), typeof(StateTransitionButton), new PropertyMetadata(null, OnTransitionChanged));
    
        private static void OnTransitionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is StateTransitionButton button && e.NewValue is string key)
            {
                button.SetBindings(key);
            }
        }
    
        public void SetBindings(string key)
        {
            this.CommandParameter = key;
    
            Binding commandBinding = new("ButtonClicked");
            commandBinding.Source = this.DataContext;
            _ = this.SetBinding(CommandProperty, commandBinding);
            this.CommandParameter = key;
    
            Binding visibilityBinding = new("CurrentState")
            {
                Converter = new UIStateToVisibilityConverter(),
                ConverterParameter = key
            };
            visibilityBinding.Source = this.DataContext;
            _ = this.SetBinding(VisibilityProperty, visibilityBinding);
    
            Binding tooltipBinding = new("CurrentState")
            {
                Converter = new UIStateToTooltipConverter(),
                ConverterParameter = key
            };
            tooltipBinding.Source = this.DataContext;
            _ = this.SetBinding(ToolTipProperty, tooltipBinding);
        }
    
        private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            this.SetBindings(this.StateTransition);
        }
    }