WPF switch panel

By Mirek on (tags: custom controls, SwithPanel, WPF, categories: code)

The panel which switch its content depending on the value of boolean flag is the topic of this post.

The WPF application I’ve recently coded was a MvvM application and had a number of editable fields. The fields were editable only in specific circumstances and when the proper flag IsEditionMode has been set to true on the ViewModel. For trivial cases I could use TextBox with IsReadOnly property binded to IsEditionMode flag and with proper style applied. This could work for just simple number or text values that can be changed on demand. The complexity of the edition controls however forced me to search for more compact and elegant solution.

The trivial solution that comes first is to put both readonly and editable controls in stack panel or any other panel and set the Visibility of those controls according to the value of IsEditionMode flag

<StackPanel>
     <TextBlock Text="Read only value" 
                Visibility="{Binding IsEditionMode, 
                            Converter={StaticResource BooleanNegationToVisibility}}" />
     <TextBox Text="Edit value here" 
              Visibility="{Binding IsEditionMode, 
                          Converter={StaticResource BooleanToVisibility}}" />
</StackPanel>

With use of converters the text block is visible when flag is false and editable box is visible when flag is set to true. At any time only one of both controls is visible.
This is good solution, but we can make it in a more elegant way by creating a custom control.

public partial class SwitchPanel : Panel
{
   private ContentControl onTrueContent;
   private ContentControl onFalseContent;
 
   public static readonly DependencyProperty ContentTrueProperty =
       DependencyProperty.Register("ContentTrue", typeof(UIElement), typeof(SwitchPanel), new UIPropertyMetadata(null));
   public UIElement ContentTrue
   {
       get { return (UIElement)GetValue(ContentTrueProperty); }
       set { SetValue(ContentTrueProperty, value); }
   }
 
   public static readonly DependencyProperty ContentFalseProperty =
       DependencyProperty.Register("ContentFalse", typeof(UIElement), typeof(SwitchPanel), new UIPropertyMetadata(null));
   public UIElement ContentFalse
   {
       get { return (UIElement)GetValue(ContentFalseProperty); }
       set { SetValue(ContentFalseProperty, value); }
   }
 
   public static readonly DependencyProperty SwitchProperty =
       DependencyProperty.Register("Switch", typeof(bool), typeof(SwitchPanel), new PropertyMetadata(false, OnSwitchChanged));
   public bool Switch
   {
       get { return (bool)GetValue(SwitchProperty); }
       set { SetValue(SwitchProperty, value); }
   }
 
   protected override Size ArrangeOverride(Size finalSize)
   {
       if (Switch)
       {
           onTrueContent.Arrange(new Rect(finalSize));
           return onTrueContent.RenderSize;
       }
       else
       {
           onFalseContent.Arrange(new Rect(finalSize));
           return onFalseContent.RenderSize;
       }
   }
 
   protected override Size MeasureOverride(Size availableSize)
   {
       if (Switch)
       {
           onTrueContent.Measure(availableSize);
           return onTrueContent.DesiredSize;
       }
       else
       {
           onFalseContent.Measure(availableSize);
           return onFalseContent.DesiredSize;
       }
   }
 
   public static void OnSwitchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   {
       ((SwitchPanel)d).SetContentsVisibility((bool)e.NewValue);
   }
 
   private void SetContentsVisibility(bool value)
   {
       if (onTrueContent != null)
           onTrueContent.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
       if (onFalseContent != null)
           onFalseContent.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
   }
 
   protected override void OnInitialized(EventArgs e)
   {
       base.OnInitialized(e);
       onTrueContent = new ContentControl { Content = ContentTrue };
       onFalseContent = new ContentControl { Content = ContentFalse };
       Children.Add(onFalseContent);
       Children.Add(onTrueContent);
   }
 
   public SwitchPanel()
   {
   }
}

This is a custom control which does not have a corresponding XAML representation. All necessary logic is included in this class. SwitchPanel derives from Panel since this is the lowest type of control that can hold more than one child control. The usage of this control is simple. We have to bind the switchable flag which decides on which content should be visible and we of course have to set the content for both cases, when switch is true and when switch is false.

 

<Controls:SwitchPanel Switch="{Binding IsEditionMode}">
    <Controls:SwitchPanel.ContentFalse>
        <TextBlock Text="Read only value" />
    </Controls:SwitchPanel.ContentFalse>
    <Controls:SwitchPanel.ContentTrue>
        <TextBox Text="Edit value here" />
    </Controls:SwitchPanel.ContentTrue>
</Controls:SwitchPanel>

Simple isn’t it?
In the source of the SwitchPanel class you can see than we had to implement the MeasureOverride and ArrangeOverride methods to arrange the content controls properly. We could avoid that by inheriting from StackPanel or Grid instead of just Panel which already arranges their children correctly, but I wanted this control to have the minimum logic required to perform its task and to avoid any unnecessary overhead that would come with more advanced panels.