Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Popup Enumerated Button Control

0.00/5 (No votes)
24 Mar 2016 1  
WPF popup enumerated button control

Introduction

This is just a cute little control I created that when bound to an enumeration property, shows the name of the enumeration and current enumeration, using the Description Attribute if available. It is a ToggleButton that shows the other available states as buttons of the same size with IsChecked, and allows the other enumerations to be selected (only shows buttons for the non-selected enumerations). Fades after 10 seconds.

Basically, it is just a version of a ComboBox, I wanted something that would provide the current state that the program was in. Normally, the state would be automatically updated, but there had to be an override capability. Since it was more important as a state, the ComboBox would really not be as obvious, and uses some more space for the arrows, which I thought would be distracting. The reason it is so big is that the display is normally viewed from a distance, and probably the use will only get close to the display when making changes using the touch screen capability.

Window showing the control in initial, not IsChecked, state.

The ToggleButtion has been clicked to show the other enumeration values available.

Design

This control is implemented as a UserControl:

<Grid Name="MainGrid">
 <ToggleButton Name="SelectedItemToggleButton"
               VerticalAlignment="Stretch"
               Style="{StaticResource TransparentIconToggleButtonStyle}"
               DataContext="{Binding Converter={popUpButtonSample:EnumBindingConverter}}"
               IsChecked="True">
  <StackPanel Grid.Row="2"
              Grid.Column="0"
              HorizontalAlignment="Right"
              Orientation="Vertical">
   <TextBlock Name="LabelTextBox"
              TextAlignment="Center" />
   <TextBlock FontSize="32"
              FontWeight="Black"
              Text="{Binding}"
              TextAlignment="Center" />
  </StackPanel>
 </ToggleButton>
 <Popup Name="PopupList"
        Height="Auto"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Opacity=".1"
        AllowsTransparency="True"
        HorizontalOffset="-4"
        IsOpen="{Binding IsChecked,
                         ElementName=SelectedItemToggleButton,
                         Converter={popUpButtonSample:IsFalseConverter},
                         Mode=TwoWay}"
        Opened="PopupList_OnOpened"
        Placement="Top"
        PlacementTarget="{Binding ElementName=SelectedItemToggleButton}"
        VerticalOffset="-2">
  <ItemsControl ItemsSource="{Binding Converter={popUpButtonSample:EnumBindingConverter}}">
   <ItemsControl.ItemTemplate>
    <DataTemplate>
     <Border Width="{Binding SizingControl.ActualWidth,
                             ElementName=ToggleBoxInstance}"
             Height="{Binding SizingControl.ActualHeight,
                              ElementName=ToggleBoxInstance}"
             Margin="{Binding SizingControl.Margin,
                              ElementName=ToggleBoxInstance}"
             Background="White"
             CornerRadius="4">
      <Border.Visibility>
       <MultiBinding Converter="{popUpButtonSample:IsEqualConverter}"
                     ConverterParameter="Collapsed:Visible">
        <Binding  />
        <Binding Converter="{popUpButtonSample:EnumDescriptionConverter}"
                 Path="DataContext"
                 RelativeSource="{RelativeSource AncestorType=ItemsControl}" />
       </MultiBinding>
      </Border.Visibility>
      <Button Margin="0"
              VerticalAlignment="Stretch"

              Click="ButtonBase_OnClick">
       <StackPanel HorizontalAlignment="Right"
                   Orientation="Vertical">
        <TextBlock Text="{Binding LabelText,
                                  ElementName=ToggleBoxInstance}"
                   TextAlignment="Center" />
        <TextBlock FontSize="32"
                   FontWeight="Black"
                   Text="{Binding}"
                   TextAlignment="Center" />
       </StackPanel>
      </Button>
     </Border>
    </DataTemplate>
   </ItemsControl.ItemTemplate>
  </ItemsControl>
 </Popup>
</Grid>

It has two basic elements, the ToggleButton, and the Popup. For this design the ToggleButton has a TextBlock on for the Content that describes what the type of state represented, and a TextBlock to display the current state.

There are a number of converters derived from IValueConverter and IMultiValueConverter. A big reason for this is that the string in the Description attribute is used to if a Description is specified.

IsFalseConverter

The IsFalseConverter is used in only one place, but is a lot more capable than used here since for this usage is only does a Boolean inverse. The associated ConverterHelper allows any value to the associated with bound true/false values using the ConverterParameter. This class is described in WPF Converter Helper Class.

EnumBindingConverter

The EnumBindingConverter allows a property of an enumerated type to be bound to both the SelectedItem and ItemsSource, and the converter will creates an string collection of enumerations, using the ToString() values or the Description attribute value if it is specified. Then converts the string value in the SelectedItem to the right enumerated value. This converter is descripted in the article Using DescriptionAttribute for enumerations bound to a WPF ComboBox.

IsEqualConverter

The IsEqualConverter checks is derived from the IMultiValueConverter. It also users the ConverterHelper to give it the flexibility of returning any value on true and false results. In this case it is bound to the control's Visibility property and a Visibility result is returned to Collapse the associated control if the currently selected enumerated value is equal to the ListItem's ItemsControl value.

EnumDescriptionConverter

The EnumDescriptionConverter was a converter that created for this project since I needed a way to display the enumeration Description Attribute for the name to use for the status. Uses a lot of the same code that was in the EnumBindingConverter. However it is simpler.

The Popup, which is set to IsOpened = true when the ToggleButton is in the IsChecked state, contains just an ItemsControl that will display Button controls the other available states. Each button is contained within a Border that is used to ensure all each Button is the width and height of the parent ToggleButton using binding to the ActualHeight and ActualWidth of the contained Button, and its Visibility is set to Collapsed if the currently selected state is the state that the Button would allow selection of. A MultiBinding is used because want the Visibility to be updated either because of the state associated with the Button, and the current state associated with the UserContol. MultiBindings are very useful in handling instances like this.

Since it is not intended to be used with a ViewModel, has code behind:

private readonly PopupControlTimer _timer;

public ToggleBox()
{
 InitializeComponent();
 GetLabelName();
 DataContextChanged += delegate { GetLabelName(); };
 _timer = new PopupControlTimer(PopupList);
}

public string LabelText { get; private set; }

public ToggleButton SizingControl => SelectedItemToggleButton;

private void GetLabelName()
{
 if (DataContext == null) return;
 var valueType = DataContext.GetType();
 var valueDescription = (DescriptionAttribute[])valueType.
    GetCustomAttributes(typeof(DescriptionAttribute), false);
 LabelText = (valueDescription.Length == 1) ? valueDescription[0].Description : valueType.Name;
 LabelTextBox.Text = LabelText;
}

private void PopupList_OnOpened(object sender, EventArgs e)
{
 _timer.Start();
}

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
 var toggleButton = (Button)sender;
 DataContext = EnumDescriptionConverter.GetEnum(DataContext.GetType(), toggleButton.DataContext);
 SelectedItemToggleButton.IsChecked = false;
}

Using the Control

Something I did not think about when I originally created this, and did not realize it was not working because on the code where I put this, I had not acutally hooked up the ICommand to really do something, is that Binding needs to have the Mode set to TwoWay:

<local:ToggleBox Width="200"
                 Height="100"
                 DataContext="{Binding PhaseType,
                                       Mode=TwoWay}" />

History

  • 03/24/16: Initial version
  • 03/27/16: Description Update
  • 07/12/16: Fix binding issue in sample, Need TwoWay binding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here