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.