Archives for : July2014

GetTheCode

I’ve been getting quite a few hits lately on my post, Binding a Dependency Property of a View to its ViewModel. After reviewing it I realized that, while the post isn’t too bad, it doesn’t read as smooth as I would like (though it does have some pretty pictures with arrows and boxes and stuff, oh my!). The code samples seem a bit lacking in describing the overall complexity of what needed to be done.

I know I always prefer to have actual code that demonstrates a concept and so I decided to take the sample code, which was incomplete, from my post Composite and Memento Design Patterns – A Real World Example and put together a complete solution.

two tabs

What you see here are the two tabs of the solution.  The first tab is simply a single instance of the control.  It works completely stand-alone and has it’s own View, ViewModel and Model.  The second tab is four instances of the control but there is a lot going on here with the binding on the controls to make everything work.

The  single instance has nothing bound to it other than it’s own view model.  The xaml for the control looks like:

<UserControl x:Class="CompositeMementoSample.Views.FileGroupView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:utility="clr-namespace:CompositeMementoSample.Infrastructure"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <utility:ViewModeToVisibilityConverter x:Key="modeToVisConv"/>
        <utility:InverseViewModeToVisibilityConverter x:Key="invModeToVisConv"/>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Grid.ColumnSpan="2" Grid.Row="0" Margin="5" Foreground="Orange" HorizontalAlignment="Center" FontSize="15"
                   Content="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
                   Visibility="{Binding ViewMode, Converter={StaticResource invModeToVisConv}}" />
        <ListView Grid.Row="1" MinWidth="150" ItemsSource="{Binding CompositeList}" />
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Command="{Binding AddFilesCommand}">Add Files</Button>
            <Button Command="{Binding SaveXmlCommand}" Visibility="{Binding ViewMode, Converter={StaticResource modeToVisConv}}">Save XML</Button>
            <Button Command="{Binding LoadXmlCommand}" Visibility="{Binding ViewMode, Converter={StaticResource modeToVisConv}}">Load XML</Button>
        </StackPanel>
    </Grid>
</UserControl>

The ViewModeToVisibilityConverter and the InverseViewModeToVisibilityConverter just show/hide controls based on what mode we’re in. The default is “Full” but when we want to use the control within other controls we assume that we want “Compact”. In compact mode we may not want to enable all features of the control and so we hide/show what we need to. Here, we want to only show a title when it’s part of another control but we only want to allow them to load and save the xml file that contains the list of files if it is a standalone control.

What is critical here is how the properties are exposed.

public partial class FileGroupView : UserControl
{
	public ViewMode ViewMode
	{
		get { return (ViewMode)GetValue(ViewModeProperty); }
		set { SetValue(ViewModeProperty, value); }
	}

	public static readonly DependencyProperty ViewModeProperty =
		DependencyProperty.Register("ViewMode", typeof(ViewMode), typeof(FileGroupView), new FrameworkPropertyMetadata(ViewMode.Full));

	public string Title
	{
		get { return (string)GetValue(TitleProperty); }
		set { SetValue(TitleProperty, value); }
	}

	public static readonly DependencyProperty TitleProperty =
		DependencyProperty.Register("Title", typeof(string), typeof(FileGroupView), new PropertyMetadata(null));

	public FileGroupModel ActiveFileGroup
	{
		get { return (FileGroupModel)GetValue(ActiveFileGroupProperty); }
		set { SetValue(ActiveFileGroupProperty, value); }
	}

	public static readonly DependencyProperty ActiveFileGroupProperty =
		DependencyProperty.Register("ActiveFileGroup", typeof(FileGroupModel), typeof(FileGroupView), new FrameworkPropertyMetadata(null, OnActiveFileGroupChanged));

	private static void OnActiveFileGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		((FileGroupViewModel)((FileGroupView)d).DataContext).ActiveFileGroup = (FileGroupModel)e.NewValue;
	}

	public FileGroupView()
	{
		OpenSaveDialogInterface dialogInterface = new OpenSaveDialogInterface();
		this.DataContext = new FileGroupViewModel(dialogInterface, dialogInterface);
		InitializeComponent();

		string propertyInViewModel = "ViewMode";
		var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
		this.SetBinding(ViewModeProperty, bindingViewMode);

		propertyInViewModel = "ActiveFileGroup";
		var bindingActiveFileGroup = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
		this.SetBinding(ActiveFileGroupProperty, bindingActiveFileGroup);
	}
}

The important bits here are the “this.SetBinding” which is setting up the binding between our dependency properties (which is what this is all about) and the view model.

Now that we’ve done all this, lets look at the xaml for the view that houses all four of these controls.

<UserControl x:Class="CompositeMementoSample.Views.MultiFileGroupView"
		 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		 xmlns:views="clr-namespace:CompositeMementoSample.Views"
		 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
		 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
		 mc:Ignorable="d" 
		 d:DesignHeight="300" d:DesignWidth="300">
<Grid>
	<Grid.RowDefinitions>
		<RowDefinition />
		<RowDefinition />
		<RowDefinition Height="40" />
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition />
		<ColumnDefinition />
	</Grid.ColumnDefinitions>
	<views:FileGroupView Grid.Row="0" Grid.Column="0" 
		ViewMode="Compact" 
		Title="File Group 1"
		ActiveFileGroup="{Binding DataContext.FileGroup1, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="0" Grid.Column="1" 
		ViewMode="Compact" 
		Title="File Group 2"
		ActiveFileGroup="{Binding DataContext.FileGroup2, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="1" Grid.Column="0" 
		ViewMode="Compact"
		Title="File Group 3"
		ActiveFileGroup="{Binding DataContext.FileGroup3, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<views:FileGroupView Grid.Row="1" Grid.Column="1"
		ViewMode="Compact"
		Title="File Group 4"
		ActiveFileGroup="{Binding DataContext.FileGroup4, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
	<StackPanel Grid.ColumnSpan="2" Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
		<Button Margin="7" Command="{Binding SaveXmlCommand}">Save XML</Button>
		<Button Margin="7" Command="{Binding LoadXmlCommand}">Load XML</Button>
	</StackPanel>
</Grid>
</UserControl>

The binding to the file group is a bit tricky because you have to explicitly call out the property on the data context or else it won’t see it.

And that’s it. Most of the solution is really the MVVM parts: views, view models and support pieces. Hopefully you grab the solution from the “GET THE CODE” button at the top of the post as that is probably the best way to understand what is going on here.

Thanks for reading,
Brian

Capitalization Conventions in .NET

In the middle of a code review the other day one of the engineers turned to me and asked, “Why are all your method parameters capitalized?”
“Hmmm, well,” I told him, “that’s the Microsoft convention.”
“No, it’s not.” he replied.
And he was right.

If you are so inspired and go back to read my code in past posts it may not be obvious as I often use snippets and auto-generated code but it’s there.  This is probably even worse as it introduces an inconsistency where some of my parameters are PascalCased and others are camelCased.

Style and capitalization conventions are something we programmers often tread lightly on. Does the open curly brace go on the line with the method definition or the next line? Should closing curly brace go be lined up with the method definition or with the last line of code? We take style and conventions almost like gospel and it’s hard to let it go so we try not to point out something that we disagree with. That is why there should be some reference that all programmers can refer to either compiled by your lead architect or by some industry standard.  That way when someone suggests something is wrong with our style there can be common guideline rather than taking it personally. I know it may not seem like it but this is really critical when working in other programmers’ code. It goes to code readability and therefore ease of maintenance.

I’m not sure how or why but in the last few years I started PascalCasing my method parameters. If you check the capitalization conventions you find every public facing identifier uses PascalCasing except parameters, which uses camelCasing.

But there is more there than I realized. So for my own reference I decided to write a post about it. This is all available at the link above but presented here as a summary

  1. PascalCasing, as defined by Microsoft, has the first letter upper cased.
  2. camelCasing, as defined by Microsoft, has the first letter lower cased.
  3. All identifiers except parameter names use PascalCasing, this includes acronyms. (HtmlTag, XmlWriter)
  4. The only exception is two-letter acronyms.
    1. For PascalCasing upper case both. (IOStream)
    2. For camelCasing lower case both. (ioStream)
  5. Never use an underscore in an identifier.

Where things really get into the weeds, and I’m sure I’ve violated this quite a bit is the use of compound terms.

  1. Treat most compound terms as single words for purposes of capitalization. (Endpoint, metadata)
    1. There are all sorts of exceptions to this so check the reference. (LogOff, SignIn, fileName)
  2. Treat a closed-form compound term as a single word. (Keyboard, crosstown)
    1. There are exceptions to this so check the reference. (UserName)

That last one especially kills me. Username is a closed-form compound term (based on both the latest Merriam-Webster and Random House Dictionary), meaning that it is treated as a single word. But according to Microsoft, for capitalization purposes, User and Name should both be capitalized.

Notable Common Terms that violate the rules or are special just because Microsoft says so (from the reference):

Pascal Camel Not
BitFlag bitFlag Bitflag
Callback callback CallBack
Canceled canceled Cancelled
DoNot doNot Don’t
Email email EMail
Endpoint endpoint EndPoint
FileName fileName Filename
Gridline gridline GridLine
Hashtable hashtable HashTable
Id id ID
Indexes indexes Indices
LogOff logOff LogOut
LogOn logOn LogIn
Metadata metadata MetaData, metaData
Multipanel multipanel MultiPanel
Multiview multiview MultiView
Namespace namespace NameSpace
Ok ok OK
Pi pi PI
Placeholder placeholder PlaceHolder
SignIn signIn SignOn
SignOut signOut SignOff
UserName userName Username
WhiteSpace whiteSpace Whitespace
Writable writable Writeable

There are a few oddities here I wanted to point out.

  1. UserName, I mentioned earlier. It’s a closed-form compound word that is not treated as a single word.
  2. LogOn. I can almost understand using LogOff instead of LogOut as LogOff is a recognized compound word. But “Log On” and “Log In” are both recognized compound words. The only reason I can think to prefer LogOn to “LogIn” is to be consistant with LogOff.
    1. This also applies to SignIn/SignOut
  3. Indexes. Both indexes and indices are recognized plural forms of index. Maybe just for consistancy?
  4. The two letter words. I point that out because I see this a lot: Ok, ok, not OK, Id, id, not ID, Pi, pi, not PI.

Well, that’s it for now. Hopefully we can all now code to the same conventions when working in .NET.

Thanks for reading,
Brian

BreadCrumb Design Pattern in WPF

GetTheCode

This continues my series on ways you’ve probably used design patterns in real-life and may not have even known it.

I’m going to go even further outside of the standard software design patterns into UI design patterns. Specifically, I wanted to address the bread crumb, a UI design pattern that concerns navigation. In web development the bread crumb is a standard control that has become almost necessary to help the user keep track of where they are. Unlike in an application where users tend to stay in one general area, web sites and web applications often have users moving through multiple pages and it’s easy to get lost. I’m sure you are all familiar with the bread crumb in web development.

Dell Bread Crumb

Above you can see a bread crumb control from Dell’s website. Because there is depth to navigation in most websites the bread crumb helps provide a path for users to return to where they started.

The BreadCrumb in WPF

But how does this apply to desktop application development? As I mentioned above desktop applications tend to be very focused in a single area. There are, however, two primary areas I’ve witnessed where users get lost. The first is in standard business applications where the application has a high number of forms that users have to navigate through. Getting user information, family information, contact information, alternate contact information, etc… it’s easy for users to get lost in their path trying to gather information on clients/customers. The other is in deeply nested trees.

I wanted to present to you a basic bread crumb control. The solution/code provided is a bit of an ugly hybrid in terms of MVVM and code-behind but will get across the basic steps you need to take. Also by providing a working solution in WPF hopefully I’ve done a lot of the work for you.

BreadCrumbSampleImage
The image above is from the sample provided that demonstrates where a bread crumb control is especially useful. When dealing with trees it’s easy to get lost. By providing a bread crumb control you make it easier for your users to navigate through the tree.

Let’s start with the interface the control uses, IParent:

public interface IParent
{
    IParent Parent { get; }
}

Pretty simple. The node that I bind the tree to implements the IParent interface. This way my BreadCrumb control can be a bit more multi-purpose.

Next is looking at BreadCrumbs.xaml:

<UserControl x:Class="BreadCrumbs.Views.BreadCrumbs"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <!--http://stackoverflow.com/questions/780426/link-button-in-wpf-->
        <Style TargetType="Button"
               BasedOn="{StaticResource ResourceKey={x:Type Button}}">
            <Setter Property="Width" Value="Auto"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <ContentPresenter Content="{TemplateBinding Content}" 
                                  ContentTemplate="{TemplateBinding  ContentTemplate}"
                                  VerticalAlignment="Center">
                            <ContentPresenter.Resources>
                                <Style TargetType="{x:Type TextBlock}">
                                    <Setter Property="TextDecorations" Value="Underline" />
                                </Style>
                            </ContentPresenter.Resources>
                        </ContentPresenter>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Foreground" Value="Blue" />
            <Setter Property="FontSize" Value="20" />
            <Setter Property="Cursor" Value="Hand" />
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="Foreground" Value="Red" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <WrapPanel x:Name="pnlContent" />
</UserControl>

The main thing that is interesting here is the style that targets button. In general, we try and present a look and feel that users expect. That’s not a hard and fast rule and I’m certainly open for change in this area, but a generality. By styling the buttons to look like a link we provide a navigational interface users expect.

And now for BreadCrumbs.xaml.cs:

public partial class BreadCrumbs : UserControl
{
	public IParent SelectedItem
	{
		get { return (IParent)GetValue(SelectedItemProperty); }
		set { SetValue(SelectedItemProperty, value); }
	}

	// Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
	public static readonly DependencyProperty SelectedItemProperty =
		DependencyProperty.Register("SelectedItem", typeof(IParent), typeof(BreadCrumbs), new PropertyMetadata(null, OnSelectedItemChanged));

	private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		((BreadCrumbs)d).BuildPath(e.NewValue as IParent);
	}
	
	public BreadCrumbs()
	{
		InitializeComponent();
	}

	private void BuildPath(IParent Node)
	{
		pnlContent.Children.Clear();
		while (Node != null)
		{
			Button b = new Button();
			b.Content = Node.ToString();
			b.Click += b_Click;
			b.Tag = Node;
			pnlContent.Children.Insert(0, b);
			Node = Node.Parent;
			//if we have more parents then we want a seperator
			if (Node != null)
			{
				Label seperator = new Label();
				seperator.Content = ">>";
				pnlContent.Children.Insert(0, seperator);
			}
		}
	}

	void b_Click(object sender, RoutedEventArgs e)
	{
		IParent selectedItem = ((Button)sender).Tag as IParent;
		SelectedItem = selectedItem;
	}
}

The first interesting thing is the dependency property SelectedItem. When this gets used in the parent control we need a dependency property to bind the selected against. In this parent control we use Mode=TwoWay so that not only do we get the item when it is selected from the tree but we can also set the selected item in the parent control by setting our own dependency property.

The BuildPath method is pretty straight forward. We know we have an IParent node and we already have the style set up on the xaml to make all our buttons look like links. We just have to iterate over the node and any parents. And since we’re using a wrap panel all the buttons will show on the control. We also set the SelectedItem when the button is clicked. This allows us to update the SelectedItem in the parent control so that the navigation via the bread crumbs allow users to move backward through the tree.

There is certainly future expansion that can be done here as there may need to be some limit on the wrap panel or you may want to show only n number of nodes back.

Hopefully you get some use out of this. The complete source code is in sample provided. If you have any questions or problems let me know.

Thanks for reading,
Brian

Bind a ComboBox to an enum while using Display attribute

I found a lot of different tutorials on binding a ComboBox to an enum but none of them worked. In every case the ComboBox would not stay selected to the value I chose, instead always reverting back to the previous selected value. Couple that with the fact that I wanted to use the Display attribute and, well, all hell broke loose.

So here I am with a brief write-up on my solution.

Let’s start with our model. In this case we’ll be designing a shirt. We’ll only be defining a shirt type but maybe in the future I might do something else with the sample. You’ll need to make sure you add the “System.ComponentModel.DataAnnotations” reference to your project if it’s not already there. That is where the Display attribute is.

public enum ShirtType
{
    [Display(Name="Dress Shirt")]
    DressShirt,
    [Display(Name="T-Shirt")]
    TShirt,
    CampShirt,
    Polo,
    Jersey
}

public class Shirt : INotifyPropertyChanged
{
    ShirtType type = ShirtType.DressShirt;
    public ShirtType Type
    {
        get { return type; }
        set { SetProperty(ref type, value); }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) 
            return false;

        storage = value;
        OnPropertyChanged(propertyName);

        return true;
    }

    protected void OnPropertyChanged(string propertyName)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}

The shirt model is unremarkable. Since this is just a basic sample I’ve implemented INotifyPropertyChanged instead of using the BindableBase from Prism. What is interesting is the use of the DisplayAttribute on the enum values. The Display attribute allows you to define a name and optional resource for localization. There are some other properties but those are the two we care about. Next we’ll use a converter to change this attribute into a value for the ComboBox.

public class EnumToDisplayAttribConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!value.GetType().IsEnum)
        {
            throw new ArgumentException("Value must be an Enumeration type");
        }

        var fieldInfo = value.GetType().GetField(value.ToString());
        var array = fieldInfo.GetCustomAttributes(false);

        foreach (var attrib in array)
        {
            if (attrib is DisplayAttribute)
            {
                DisplayAttribute displayAttrib = attrib as DisplayAttribute;

                //if there is no resource assume we don't care about localization
                if (displayAttrib.ResourceType == null)
                    return displayAttrib.Name;

                // per http://stackoverflow.com/questions/5015830/get-the-value-of-displayname-attribute
                ResourceManager resourceManager = new ResourceManager(displayAttrib.ResourceType.FullName, displayAttrib.ResourceType.Assembly);
                var entry =
                    resourceManager.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true)
                      .OfType<DictionaryEntry>()
                      .FirstOrDefault(p => p.Key.ToString() == displayAttrib.Name);

                var key = entry.Value.ToString();
                return key;
            }
        }

        //if we get here than there was no attrib, just pretty up the output by spacing on case
        // per http://stackoverflow.com/questions/155303/net-how-can-you-split-a-caps-delimited-string-into-an-array
        string name = Enum.GetName(value.GetType(), value);
        return Regex.Replace(name, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", "$1 ");
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Now this is where the magic happens. The converter takes in the enum bound to the row in the ComboBox and converts it to a string. If a custom DisplayAttribute can be found and it has a resource then it attempts to get the value for localization. Otherwise it just uses the value straight up. If there is no DisplayAttribute then it tries to be smart and converts a Pascal Cased enum to have spaces.

Okay, now we’ve got the enums, we’ve got the converter, now how do we wire it up? We’re going to bind to an ObjectDataProvider in the XAML.

<Window x:Class="BindingToEnum.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib" 
        xmlns:local="clr-namespace:BindingToEnum"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:Shirt />
    </Window.DataContext>
    <Window.Resources>
        <local:EnumToDisplayAttribConverter x:Key="enumToDisplayAttribConverter" />
        <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="dataFromShirtType">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:ShirtType" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ComboBox Grid.Row="0" MinWidth="100" HorizontalAlignment="Left" 
                            FontSize="20"
                            ItemsSource="{Binding Source={StaticResource dataFromShirtType}}"
                            SelectedItem="{Binding Type}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ., Converter={StaticResource enumToDisplayAttribConverter}}" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</Window>

So lets look at the important bits of this:

Line 04: We have to add the sys namespace.
Line 05: Our local namespace.
Line 11: The converter we’re going to use.
Line 12: The ObjectDataProvider. This will get the values of the enum to be used later.
Line 14: The actual type of the enum we’re retrieving.
Line 24: We bind the ItemsSource of the ComboBox to the key of the ObjectDataProvider defined on line 12.
Line 25: We bind the Selected Item to the “Type” property of our model.
Line 26: We want to define a custom data template so we can use our converter.
Line 28: We bind the text of the text block to “.”. This just means use the data context we’re bound to. Since each row in a ComboBox will be bound to an enum, this just means we’re using the enum for the row.
And finally use the converter we defined at line 11.

Of all of this, probably the biggest take-away is binding the TextBlock.Text to “.”. This way the enum will be passed off to our converter. Attached is the code.

Thanks for reading.
Brian