All posts in WPF

Welcome to Part 2 of our series Making a Better ObservableCollection. If you missed Making a Better ObservableCollection Part 1 – Extensions you can get to it here.

In this next section I am going to share a version of my ObservableCollectionEx that allows cross-threading. The idea here is to have an ObservableCollection which you can update from an Async thread so as not to impact the owning thread. This is especially useful in WPF when you don’t wish to block the UI thread while performing collection updates.

Let’s see the code:


/// <summary>
/// Initializes a new instance of the 
/// <see cref="ObservableCollectionEx{T}"/> class.
/// </summary> 
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
    #region Constructors

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="ObservableCollectionEx{T}" /> class.
    /// </summary>
    public ObservableCollectionEx()
    {
    }

    ///
    /// Initializes a new instance of the
    ///  class.
    ///
    ///The collection.
    public ObservableCollectionEx(IEnumerable<T> collection) : this()
    {
        this.AddRange(collection);
    }

    #endregion

    #region Events

    /// <summary>
    /// Source: New Things I Learned
    /// Title: Have worker thread update ObservableCollection that is bound to a ListCollectionView
    /// http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx
    /// Note: Improved for clarity and the following of proper coding standards.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // Use BlockReentrancy
        using (BlockReentrancy())
        {
            var eventHandler = CollectionChanged;
            if (eventHandler == null) return;

            // Only proceed if handler exists.
            Delegate[] delegates = eventHandler.GetInvocationList();

            // Walk through invocation list.
            foreach (var @delegate in delegates)
            {
                var handler = (NotifyCollectionChangedEventHandler)@delegate;
                var currentDispatcher = handler.Target as DispatcherObject;

                // If the subscriber is a DispatcherObject and different thread.
                if ((currentDispatcher != null) &amp;&amp; (!currentDispatcher.CheckAccess()))
                {
                    // Invoke handler in the target dispatcher's thread.
                    currentDispatcher.Dispatcher.Invoke(
                        DispatcherPriority.DataBind, handler, this, e);
                }

                else
                {
                    // Execute as-is
                    handler(this, e);
                }
            }
        }
    }

    /// <summary>
    /// Overridden NotifyCollectionChangedEventHandler event.
    /// </summary>
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion
}

The constructors are pretty straight forward. We want to have an empty constructor and one that allows an immediate “AddRange” of an IEnumerable just like List and ObservableCollection allow, but that is not really the point of this post.

The main feature here is a slightly reformatted version of a wonderful post from a blog called New Things I Learned which covers cross-thread access with an ObservableCollection.

So, what does this code do?

  1. First we use BlockReentracy to prevent changes to the collection while we are evaluating it.
  2. Next, we get the Invocation List from the CollectionChanged event handler. This is the list of delegates subscribing to the event.
  3. Evaluate each delegate by retrieving it’s Target and casting it to a DispatcherObject.
  4. Make sure the DispatcherObject is valid and that the current thread has access.
  5. Invoke the delegate in the DispatcherObject’s thread.

It’s not the simplest code to follow when you aren’t overly familiar with working with event delegates but it gets the job done quite well.

Next time, we will talk about using a custom SortComparer to improve DataGrid performance.

Until next time.

It’s actually kind of difficult to imagine writing anything in WPF without using at least one ObservableCollection or several instances of INotifyPropertyChanged (INPC). And it’s important to note that neither of these are perfect out of the box. I have spent a bit of time talking about ways to streamline your INPC implementations in the 3-part series “Creating a Model Base“, and plan to have another update to that soon. This topic will focus on the ObservableCollection.

Here are a few reasons why we use it:

  • When controls bind to them, Add, Move, and Remove actions automatically update in the UI without any need to directly update those controls.
  • It has a CollectionChanged event allowing us to leverage when items are added, moved, or removed.

Essentially, it let’s us know when our collection gets updated and allows us to leverage the UI without much effort.

Q: So, why extend it?
A: Because there are some extras all good collections should have to make our lives easier.

For this article, we’ll start simple with a few extensions, then move on to multi-threading, then sorting.

 
Apply

One extension I have always wanted to see in any collection is the ability to commit changes to a list in one line. That is what Apply is good for.

So, normally if you wanted to set all the items in a collection to true (assuming the model has a property called “IsSelected”), you would do the following:


foreach (var item in collection)
{
    item.IsSelected = true;
}

With Apply I could write the same code like this:


collection.Apply(x => x.IsSelected = true);

The code is relatively simple:


/// <summary>
/// Applies the specified changes to the collection.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">The items.</param>
/// <param name="predicate">The predicate.</param>
public static void Apply<T>(this IEnumerable<T> items, Action<T> predicate)
{
    foreach (var item in items)
    {
        predicate(item);
    }
}

It’s our familiar for loop that processes each item with the same predicate in a lambda expression.

 
AddRange and RemoveRange

I am of the belief every collection should allow you to add or remove multiple items at a time. We already have this functionality in List, so why not here? It’s actually not hard at all with our friend >Apply:


/// <summary>
/// Adds the range.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">The items.</param>
/// <param name="collection">The collection.</param>
public static void AddRange<T>(this IList<T> items, IEnumerable<T> collection)
{
    // Add range to local items
    collection.Apply(items.Add);
}

/// <summary>
/// Removes the range.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">The items.</param>
/// <param name="collection">The collection.</param>
public static void RemoveRange<T>(this IList<T> items, IEnumerable<T> collection)
{
    // Remove range from local items
    collection.Apply(p => items.Remove(p));
}

If we want to add multiple items to our collection, for example, all items that are selected in a new collection, all we would have to do is this:


collection.AddRange(newCollection.Where(p => p.IsSelected));

If we want to remove the unselected items listed in the new collection, we would do this:


collection.RemoveRange(newCollection.Where(p => p.IsSelected == false));

 
 
SynchCollection

Another common need is to be able to synch the items of 2 collections. This is often used as a way to maintain the instance of the target collection having it update itself based on the contents of another collection.

Our first method matches the item content in our target collection based on the provided source:


/// <summary>
/// Synches the collection items to the target collection items.
/// This does not observe sort order.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">The items.</param>
/// <param name="collection">The collection.</param>
public static void SynchCollection<T>(this IList<T> items, IEnumerable<T> collection)
{
    // Evaluate
    if (collection == null) return;

    // Make a list
    var list = collection.ToList();

    // Add items not in FilteredViewItems that are in list
    items.AddRange(list.Where(p => items.IndexOf(p) == -1).ToList());

    // Remove items from FilteredViewItems not in list
    items.RemoveRange(items.Where(p => list.IndexOf(p) == -1).ToList());
}

AddRange and RemoveRange are a big help here; allowing us to tidy up our code quite a bit.

Q: What about sorting?
A: We need to do a little more work for this, but with an ObservableCollection instead of a List as our target this is possible.


/// <summary>
/// Synches the collection items to the target collection items.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items">The items.</param>
/// <param name="collection">The collection.</param>
/// <param name="canSort">if set to <c>true</c> [can sort].</param>
public static void SynchCollection<T>(this ObservableCollection<T> items, 
    IList<T> collection, bool canSort = false)
{
    // Synch collection
    SynchCollection(items, collection.AsEnumerable());

    // Sort collection
    if (!canSort) return;

    // Update indexes as needed
    for (var i = 0; i < collection.Count; i++)
    {
        // Index of new location
        int index = items.IndexOf(collection[i]);
        if (index == i) continue;

        // Move item to new index if it has changed.
        items.Move(index, i);
    }
}

Since we are talking sorting, we should be talking about ObservableCollections. We go ahead and use our previous SynchCollection method to handle the synch. After that, all we need to do is compare indexes between collections and move items as needed.

Note: This collection does not take a sort comparer because it assumes the source collection is already sorted. You should handle this type of operation independently.

And that’s all there is to it. Next time we will talk about Multithreading an ObservableCollection to offload the work from the UI thread.

If you have ideas for other extensions you would like to see, comment below.

Until next time.

As you may know, there is no double-click event for an Image in WPF. However, there are definitely cases where we might want one. But the question is, how do we pull this off while upholding a pure MVVM implementation?

Some considerations:

  • Consideration 1: We should use Commands instead of Events.
  • Consideration 2: In order to get 2 clicks, we will have to look at the MouseDown event, and look at it’s ClickCount since that is something an Image can handle.
  • Consideration 3: Since this is an Image, there is no inherent Command property. In this case, we will need to use a library that provides one.

To start, let’s create a separate class that implements ICommand. In this example all we are doing is firing a MessageBox on successful completion of the event. In a pure implementation a MessageBox would not be considered kosher, but we are just going to use it for our example so we know if it worked:

using System;
using System.Windows;
using System.Windows.Input;

namespace DoubleClickImageTest
{
    /// <summary>
    /// A test command.
    /// </summary>
    public class TestCommand : ICommand
    {
        public virtual bool CanExecute(object parameter)
        {
            return true;
        }

        public virtual void Execute(object parameter)
        {
            // We shouldn't put message boxes in commands normally, but we will for this test.
            MessageBox.Show("Hey, we just double-clicked our Image!",
                "Double-Click Test", MessageBoxButton.OK);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
}

Next, because this is MVVM we should create a View Model that will hold our command as a property:

using System;

namespace DoubleClickImageTest
{
    public class TestViewModel
    {
        #region Properties

        /// <summary>
        /// Gets or sets my command.
        /// </summary>
        /// <value>
        /// My command.
        /// </value>
        public TestCommand MyCommand { get; set; }

        #endregion

        #region Constructors
        
        /// <summary>
        /// Initializes a new instance of the <see cref="TestViewModel"/> class.
        /// </summary>
        public TestViewModel()
        {
            MyCommand = new TestCommand();
        }

        #endregion
    }
}

To finish setting up the View Model, we will add it to the code-behind of our View and set the Data Context of the View to the View Model instance.

using System;
using System.Windows;

namespace DoubleClickImageTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Members
        
        private TestViewModel _vm;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            // Get and set VM
            _vm = new TestViewModel();
            this.DataContext = _vm;

            // Initialize UI
            InitializeComponent();
        }

        #endregion
    }
}

We have now satisfied the conditions of Consideration 1.

In order to satisfy Consideration 2, we will need to do the following:

  1. Write a class called CustomImage that extends System.Windows.Control.Image.
  2. In CustomImage create a RoutedEvent and RoutedEventHandler to facilitate the double-click event.
  3. In CustomImage override OnMouseLeftButtonDown to evaluate the click count.

First, we create the RoutedEvent:

/// <summary>
/// The mouse left button double click event
/// </summary>
public static readonly RoutedEvent MouseLeftButtonDoubleClick =
    EventManager.RegisterRoutedEvent(
        "MouseLeftButtonDoubleClick",
        RoutingStrategy.Bubble,
        typeof(RoutedEventHandler),
        typeof(CustomImage));

Next we will create the RoutedEventHandler to execute MouseLeftButtonDoubleClick :

/// <summary>
/// Occurs when [mouse left button double click event handler].
/// </summary>
public event RoutedEventHandler MouseLeftButtonDoubleClickEvent
{
    add
    {
        AddHandler(MouseLeftButtonDoubleClick, value);
    }
    remove
    {
        RemoveHandler(MouseLeftButtonDoubleClick, value);
    }
}

Lastly, we need to override the OnMouseLeftButtonDown event.

So, why are we doing this? We know images have OnMouseLeftButtonDown, so we can use that to observe the click-count and call our event. In this case, we observe the ClickCount of our MouseButtonEventArgs. If we observe 2 clicks, we raise the MouseLeftButtonDoubleClick event:

/// <summary>
/// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonDown" /> 
/// routed event is raised on this element. Implement this method to add class handling for 
/// this event.
/// </summary>
/// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that 
/// contains the event data. The event data reports that the left mouse button was pressed.
/// </param>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    // Double-click
    if (e.ClickCount == 2)
    {
        RaiseEvent(new MouseLeftButtonDoubleClickEventArgs(
            MouseLeftButtonDoubleClick, this));
    }
    base.OnMouseLeftButtonDown(e);
}

/// <summary>
/// MouseLeftButtonDoubleClick EventArgs.
/// </summary>
public class MouseLeftButtonDoubleClickEventArgs : RoutedEventArgs
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MouseLeftButtonDoubleClickEventArgs"/> 
    /// class.
    /// </summary>
    /// <param name="routedEvent">The routed event identifier for this instance of the 
    /// <see cref="T:System.Windows.RoutedEventArgs" /> class.</param>
    /// <param name="source">An alternate source that will be reported when the event is 
    /// handled. This pre-populates the
    /// <see cref="P:System.Windows.RoutedEventArgs.Source" /> property.</param>
    public MouseLeftButtonDoubleClickEventArgs(RoutedEvent routedEvent, object source)
        : base(routedEvent, source)
    {
    }
}

So, in it’s entirety, the CustomImage class looks like this:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace DoubleClickImageTest
{
    /// <summary>
    /// Custom image.
    /// </summary>
    public class CustomImage : Image
    {
        /// <summary>
        /// The mouse left button double click event
        /// </summary>
        public static readonly RoutedEvent MouseLeftButtonDoubleClick =
            EventManager.RegisterRoutedEvent(
                "MouseLeftButtonDoubleClick",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof(CustomImage));

        /// <summary>
        /// Occurs when [mouse left button double click event handler].
        /// </summary>
        public event RoutedEventHandler MouseLeftButtonDoubleClickEvent
        {
            add
            {
                AddHandler(MouseLeftButtonDoubleClick, value);
            }
            remove
            {
                RemoveHandler(MouseLeftButtonDoubleClick, value);
            }
        }

        /// <summary>
        /// Invoked when an unhandled <see cref="E:System.Windows.UIElement.MouseLeftButtonDown" /> 
        /// routed event is raised on this element. Implement this method to add class handling for 
        /// this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that 
        /// contains the event data. The event data reports that the left mouse button was pressed.
        /// </param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            // Double-click
            if (e.ClickCount == 2)
            {
                RaiseEvent(new MouseLeftButtonDoubleClickEventArgs(
                    MouseLeftButtonDoubleClick, this));
            }
            base.OnMouseLeftButtonDown(e);
        }

        /// <summary>
        /// MouseLeftButtonDoubleClick EventArgs.
        /// </summary>
        public class MouseLeftButtonDoubleClickEventArgs : RoutedEventArgs
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="MouseLeftButtonDoubleClickEventArgs"/> 
            /// class.
            /// </summary>
            /// <param name="routedEvent">The routed event identifier for this instance of the 
            /// <see cref="T:System.Windows.RoutedEventArgs" /> class.</param>
            /// <param name="source">An alternate source that will be reported when the event is 
            /// handled. This pre-populates the
            /// <see cref="P:System.Windows.RoutedEventArgs.Source" /> property.</param>
            public MouseLeftButtonDoubleClickEventArgs(RoutedEvent routedEvent, object source)
	            : base(routedEvent, source)
            {
            }
        }
    }
}

For Consideration 3 we need to extend our image to allow Commanding, and to do that we will use the Interactivity Library from Expression Blend.

Now, we will build our XAML. Let’s start by adding our CustomImage to a window:

<Window x:Class="DoubleClickImageTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DoubleClickImageTest"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <local:CustomImage 
            Width="200"
            Height="200"
            Source="/DoubleClickImageTest;component/cat_popcorn.jpg" 
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
        </local:CustomImage>
    </Grid>
</Window>

It looks like this at runtime:

Popcorn Cat

So far, there is nothing overly significant about this implementation. Our CustomImage is behaving just like a normal image and it’s center-mass in our window.

Now, let’s add our new MouseLeftButtonDoubleClick event.

<Window x:Class="DoubleClickImageTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DoubleClickImageTest"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:CustomImage 
            Width="200"
            Height="200"
            Source="/DoubleClickImageTest;component/cat_popcorn.jpg" 
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDoubleClickEvent">
                    <i:InvokeCommandAction Command="{Binding Path=MyCommand}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </local:CustomImage>
    </Grid>
</Window>

You will notice we included the Interactivity Library in the header:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

Also, this new block inside CustomImage:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonDoubleClickEvent">
        <i:InvokeCommandAction Command="{Binding Path=MyCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

This says: “whenever MouseLeftButtonDoubleClick is fired, invoke MyCommand“.

Note: you will notice this works exactly the same as setting a command on a Button.

So, now when we double-click our image, we see this:

Popcorn Cat with a double-click event

And just like that, we created our double-clickable image.

Until next time…

Note: This is a continuation of the very popular article Customizing the WPF MenuItem in XAML.

The source code for this solution (Visual Studio 2012) can be downloaded HERE

Q: I have a WPF submenu but I want to limit it’s height. How do I do that?
A: As mentioned in the earlier post you need to override the default Aero MenuItem template and components for WPF.

Q: So, where do we need to modify the style to fix this?
A: There are actually 2 areas in the style that require modification.

These are lines 180 – 199 of the Style (as seen in the original post linked above):


<ContentControl Name="SubMenuBorder"
			Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
			IsTabStop="false">
	<ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
				  Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
		<Grid RenderOptions.ClearTypeHint="Enabled">
			<Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
				<Rectangle
				Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
				Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
				Fill="{StaticResource SubMenuBackgroundBrush}" />
			</Canvas>
			<ItemsPresenter Name="ItemsPresenter" Margin="2"
						KeyboardNavigation.TabNavigation="Cycle"
						KeyboardNavigation.DirectionalNavigation="Cycle"
						SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
						Grid.IsSharedSizeScope="true"/>
		</Grid>
	</ScrollViewer>
</ContentControl>

Notice on line 183 (line 4), we added the MaxHeight=”400″

We repeat the again in the code between lines 457 and 476 of the Style (as seen in the original post linked above):


<ContentControl Name="SubMenuBorder"
				Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
				IsTabStop="false">
	<ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
				  Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
		<Grid RenderOptions.ClearTypeHint="Enabled">
			<Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
				<Rectangle
				Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
				Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
				Fill="{StaticResource SubMenuBackgroundBrush}" />
			</Canvas>
			<ItemsPresenter Name="ItemsPresenter" Margin="2"
						KeyboardNavigation.TabNavigation="Cycle"
						KeyboardNavigation.DirectionalNavigation="Cycle"
						SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
						Grid.IsSharedSizeScope="true"/>
		</Grid>
	</ScrollViewer>
</ContentControl>

Notice on line 460 (line 4), we added the MaxHeight=”400″

The blocks are virtually identical except the first one affects the initial Submenu and the second handles the child Submenu.

The intent here is to control the MaxHeight with a property so, we are going to do just that.

My Project file layout:

Project file layout in VS2012

Important files:

  • MenuItemEnhanced.xaml: Our new and improved MenuItem control (now with submenu height features!)
  • MenuItemEnhanced.xaml.cs: Code-behind for our new and improved MenuItem control.
  • Styles.xaml: A ResourceDictionary containing the MenuItem style mentioned above in it’s entirety.

All right. We saw the initial style sheet in it’s “glory”. Lets look at the code for the new control.

The MenuItemEnhanced XAML:


<MenuItem x:Class="Xcalibur.UI.Menu.Controls.MenuItemEnhanced"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <MenuItem.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Xcalibur.UI.Menu;component/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </MenuItem.Resources>
</MenuItem>

Nothing overly remarkable here. We created a new control that is based on MenuItem.

Now the code-behind:


using System.Windows;
using System.Windows.Controls;

namespace Xcalibur.UI.Menu.Controls
{
    /// <summary>
    /// An extended MenuItem
    /// </summary>
    public partial class MenuItemEnhanced : MenuItem
    {
        #region Properties
        
        /// <summary>
        /// Gets or sets the height of the max submenu.
        /// </summary>
        /// <value>
        /// The height of the max submenu.
        /// </value>
        public double MaxSubmenuHeight
        {
            get { return (double)GetValue(MaxSubmenuHeightProperty); }
            set { SetValue(MaxSubmenuHeightProperty, value); }
        }

        /// <summary>
        /// Gets or sets the height of the max submenu (Dependency Property)
        /// </summary>
        public static readonly DependencyProperty MaxSubmenuHeightProperty =
            DependencyProperty.Register("MaxSubmenuHeight", typeof(double), 
            typeof(MenuItemEnhanced), new PropertyMetadata(400.0));
        
        #endregion

        #region Constructors
        
        /// <summary>
        /// Initializes a new instance of the <see cref="MenuItemEnhanced"/> class.
        /// </summary>
        public MenuItemEnhanced()
        {
            this.InitializeComponent();
        }

        #endregion
    }
}

Here, we created a dependency property called MaxSubmenuHeight of type double. The default height in this case is 400.0 since we used that height in the last post. Nothing more needed to be done here.

The kicker is updating our ResourceDictionary to observe our new, awesome DependencyProperty. To do that, we will go back to those 2 sections of XAML in the style sheet and change this:

MaxHeight=”400″

to this:

MaxHeight=”{Binding Path=MaxSubmenuHeight,RelativeSource={RelativeSource TemplatedParent}}”

That’s all there is to it.

And now for your viewing pleasure (as included in the project linked above), here is an example. The initial menu has been capped at 600px and it’s submenu at 200px:


<Window x:Class="TestBed.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:menu="clr-namespace:Xcalibur.UI.Menu.Controls;assembly=Xcalibur.UI.Menu"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Menu>
            <menu:MenuItemEnhanced Header="Test Menu" MaxSubmenuHeight="600">
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Test Item 1" />
                <menu:MenuItemEnhanced Header="Submenu" MaxSubmenuHeight="200">
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                    <menu:MenuItemEnhanced Header="Test Item 1" />
                </menu:MenuItemEnhanced>
            </menu:MenuItemEnhanced>
        </Menu>
    </Grid>
</Window>

And that looks like this:

Screenshot of our MenuItemEnhanced control in action

That’s all for now. Hope I helped you solve this annoying issue.

In my last post I went over how to retrieve the Windows Product Activation information from Windows Vista / 7.0 / 2008. Today, we are going to do something on the same thread but very much more desirable. And that is retrieving the Windows Key information.

For as long as Windows has had a key, admins have been looking to be able to retrieve the Windows key information on the fly: especially when they need to do a re-install and lost their original media casing.

So, let’s get right to it with a console application.


static void Main(string[] args)
{
    string computername = Environment.MachineName;
    const RegistryHive registryRoot = RegistryHive.LocalMachine;
    const string sSubKeyName = @"SOFTWAREMicrosoftWindows NTCurrentVersion";
    const string sValueName = "DigitalProductId";
    string windowsKey = "";

    // Get product Id
    RegistryKey regoutput = RegistryKey.OpenRemoteBaseKey(
        registryRoot, computername, RegistryView.Default).
        OpenSubKey(sSubKeyName);

    // Convert to byte array
    byte[] digitalProductId;
    if (regoutput != null)
    {
        digitalProductId = regoutput.GetValue(sValueName) as byte[];

        // Get Windows Product Key
        windowsKey = (digitalProductId != null)
            ? DecodeProductKey(digitalProductId)
            : "The product key was not accessible";
    }

    else
    {
        windowsKey = "The product key was not accessible";
    }

    // Output 
    Console.WriteLine(windowsKey);
    Console.Read();
}

In this example, I am choosing my local machine as the target. The location of the value we want is located in the registry here:
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionDigitalProductId.

We then get the byte array representing the key and decode it with the below method.


/// <summary>
/// Decodes a Microsoft product key based on the provided digital product id.
/// </summary>
/// <param name="digitalProductId">The digital product id.</param>
/// <returns></returns>
private static string DecodeProductKey(byte[] digitalProductId)
{
    // Offset of first byte of encoded product key in 
    //  'DigitalProductIdxxx" REG_BINARY value. Offset = 34H.
    const int keyStartIndex = 52;

    // Offset of last byte of encoded product key in 
    //  'DigitalProductIdxxx" REG_BINARY value. Offset = 43H.
    const int keyEndIndex = keyStartIndex + 15;

    // Possible alpha-numeric characters in product key.
    var digits = new[]
            {
                'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R',
                'T', 'V', 'W', 'X', 'Y', '2', '3', '4', '6', '7', '8', '9'
            };

    // Length of decoded product key
    const int decodeLength = 29;

    // Length of decoded product key in byte-form.
    // Each byte represents 2 chars.
    const int decodeStringLength = 15;

    // Array of containing the decoded product key.
    var decodedChars = new char[decodeLength];

    // Extract byte 52 to 67 inclusive.
    var hexPid = new ArrayList();
    for (int i = keyStartIndex; i <= keyEndIndex; i++)
    {
        hexPid.Add(digitalProductId[i]);
    }
    for (int i = decodeLength - 1; i >= 0; i--)
    {
        // Every sixth char is a separator.
        if ((i + 1) % 6 == 0)
        {
            decodedChars[i] = '-';
        }
        else
        {
            // Do the actual decoding.
            int digitMapIndex = 0;
            for (int j = decodeStringLength - 1; j >= 0; j--)
            {
                int byteValue = (digitMapIndex << 8) | (byte)hexPid[j];
                hexPid[j] = (byte)(byteValue / 24);
                digitMapIndex = byteValue % 24;
                decodedChars[i] = digits[digitMapIndex];
            }
        }
    }
    return new string(decodedChars);
}

Now, if you are running 64-bit Windows (and most of us are), you may be running into problems getting your byte array. Why is that? Well, the short answer is that it has to do with your Configuration Manager settings. The default platform target is x86: which does not have access to several 64-bit spaces including certain registry values.

You can change that by doing the following in Visual Studio:

  1. Open your Project Properties.
  2. Go to the Build tab.
  3. For each Configuration set the Platorm target to Any CPU.
  4. For Visual Studio 2012, make sure to uncheck Prefer 32-bit.

That’s all there is to it.

Until next time.

In today’s blog I am going to give you the method I use to obtain the Windows Product Activation information in Astronomy.

This information looks like it does below:

Screen Capture

Q: So, where did this information come from?
A: Well, as you may know, you can obtain this information from the following script:
C:WindowsSystem32slmgr.vbs

I always felt this needed to be modified for use in .Net, so I refactored this a bit and created my own replacement method:


/// <summary>
/// Retrieves licensing information.
/// </summary>
/// <param name="managementScope">The management scope.</param>
/// <returns></returns>
public static string GetLicensingInfo(ManagementScope managementScope)
{
	// Output
	string result = "Information Unavailable";
	const string windowsAppId = "55c92734-d682-4d71-983e-d6ec3f16059f";

	// Default values
	const uint HR_SL_E_GRACE_TIME_EXPIRED = 0xC004F009;
	const uint HR_SL_E_NOT_GENUINE = 0xC004F200;

	// Set WMI query
	var query =
		new WqlObjectQuery(
			"SELECT LicenseStatus, GracePeriodRemaining " +
			"FROM SoftwareLicensingProduct " +
			"WHERE (PartialProductKey <> NULL) AND " +
			"(ApplicationId='" + windowsAppId + "')");

	// Get items
	using (var searcher = new ManagementObjectSearcher(managementScope, query))
	
	// Get data
	using (var colItems = searcher.Get())
	{
		foreach (ManagementBaseObject obj in colItems)
		{
			string subline;

			// License Status
			int licenseStatus = Convert.ToInt32(obj.GetPropertyValue("LicenseStatus"));

			// Time remaining: minutes / days
			int gpMin = Convert.ToInt32(obj.GetPropertyValue("GracePeriodRemaining"));
			int gpDay = gpMin / (24 * 60);

			// Evaluate
			switch (licenseStatus)
			{
				case 0:
					result = "Unlicensed";
					break;

				case 1:
					result = "Licensed";
					if (gpMin > 0)
					{
						subline =
							String.Format(
								"Activation expiration: " +
								"{0} minute(s) ({1} day(s))", gpMin, gpDay);
						result += "n" + subline;
					}
					break;

				case 2:
					result = "Initial grace period";
					subline =
						String.Format("Time remaining: {0} minute(s) " +
										"({1} day(s))", gpMin, gpDay);
					result += "n" + subline;
					break;

				case 3:
					result =
						"Additional grace period (KMS license expired or " +
						"hardware out of tolerance)";
					subline =
						String.Format("Time remaining: {0} minute(s) " +
										"({1} day(s))", gpMin, gpDay);
					result += "n" + subline;
					break;

				case 4:
					result = "Non-genuine grace period.";
					subline =
						String.Format("Time remaining: {0} minute(s) " +
										"({1} day(s))", gpMin, gpDay);
					result += "n" + subline;
					break;

				case 5:
					result = "Notification";

					uint licenseStatusReason;
					uint.TryParse(obj.GetPropertyValue("GracePeriodRemaining").ToString(), 
						out licenseStatusReason);
						
					// Evaluate
					switch (licenseStatusReason)
					{
						case HR_SL_E_NOT_GENUINE:
							subline =
								String.Format(
									"Notification Reason: 0x{0} " +
									"(non-genuine).", licenseStatusReason);
							break;

						case HR_SL_E_GRACE_TIME_EXPIRED:
							subline =
								String.Format(
									"Notification Reason: 0x{0} " +
									"(grace time expired).", licenseStatusReason);
							break;

						default:
							subline =
								String.Format("Notification Reason: 0x{0}.",
												licenseStatusReason);
							break;
					}
					result += "n" + subline;
					break;

				case 6:
					result = "Extended grace period";
					subline =
						String.Format("Time remaining: {0} minute(s) " +
										"({1} day(s))", gpMin, gpDay);
					result += "n" + subline;
					break;

				default:
					result = "Unknown";
					break;
			}
		}
	}
	
	// return
	return result;
}

An overboard implementation:


/// <summary>
/// Connects to a target computer through Kerberos authentication.
/// </summary>
/// <param name="computername">The computername.</param>
/// <param name="user">The user.</param>
/// <param name="securePass">The secure pass.</param>
/// <returns>
/// A ManagementScope context for the current connection.
/// </returns>
public static ManagementScope Connect(string computername, string user, 
	SecureString securePass)
{
	// Build an options object for the remote connection
	var options = new ConnectionOptions
		{
			Impersonation = ImpersonationLevel.Impersonate,
			EnablePrivileges = true
		};

	// Set properties
	// Check name
	if (String.IsNullOrEmpty(computername))
	{
		computername = ".";
	}

	// Check credentials
	// Cannot pass a blank user and password.
	if (!String.IsNullOrEmpty(user))
	{
		options.Username = user;
		options.SecurePassword = securePass;
	}
	
	// Make a connection to a remote computer.
	var managementScope =
		new ManagementScope(@"\" + computername + @"rootcimv2", options);

	try
	{
		// Connect
		managementScope.Connect();
	}
	catch (Exception ex)
	{
		throw ex;
	}
	
	// return
	return managementScope;
}

/// <summary>
/// Gets the license you so desperately want.
/// </summary>
public void GetMyLicense()
{
	// Get the management scope
	var myManagementScope = Connect(".", "XCALIBURjarzt", {my secure password});

	// Get the licensing
	string licensing = GetLicensingInfo(myManagementScope);

	// Report to client
	MessageBox.Show("My current licensing status: " + licensing);
}

So, really that’s all there is to it.

Happy coding!

One of the issues I have seen around the web is in regard to TabControls and missing content. The “missing” content in this case is the content of tab items within a TabControl that are not in focus. By design, a TabControl will only load the content of a TabItem once it has been brought into focus. This is not bad design if you think about it for a moment.

Q: Shouldn’t we just load into memory what we need at the time we need it to minimize our footprint?
A: Yes, definitely. This is good application design.

But, what about the case where I need information from other tabs that have not yet been brought into focus? For example, what if I have a GridView in another tab and I want to show the row count right when the control is loaded? Well, the answer to that is pre-loading your tab items. And since the TabControl does not have this behavior built-in by default, we will have to do it ourselves.

Here is one way I saw on the web that I wanted to bring to your attention:


for (var i = 0; i < myTabControl.Items.Count; i++)
{
	myTabControl.SelectedIndex = i;
	myTabControl.UpdateLayout();
}

Well, this looks pretty good and it has very little code. This should work, right? No, not in all cases. The reason is that this will cycle through the tabs as fast as the UpdateLayout() call completes on each tab. Because this implementation will not necessarily wait until each tab is done loading, we cannot guarantee that the content of each tab has finished loading into the Visual Tree. What we need is a solution that waits until each tab item has completely loaded its content before we move to the next tab.

So, let’s state our list of objectives:

  • Objective 1: Each tab item should wait to complete loading before the next tab item is selected.
  • Objective 2: We should hide the Tab Control from view until it is done pre-loading.
  • Objective 3: We should return to the first tab in the sequence at completion.

The approach:

In order to effectively meet the first objective, we should chain the loading of each tab through recursion.

So, here are our 2 methods:


/// <summary>
/// Preloads tab items of a tab control in sequence.
/// </summary>
/// <param name="tabControl">The tab control.</param>
public static void PreloadTabs(TabControl tabControl)
{
    // Evaluate
    if (tabControl.Items != null)
    {
        // The first tab is already loaded
        // so, we will start from the second tab.
        if (tabControl.Items.Count > 1)
        {
            // Hide tabs
            tabControl.Opacity = 0.0;

            // Last action
            Action onComplete = () =>
            {
                // Set index to first tab
                tabControl.SelectedIndex = 0;

                // Show tabs
                tabControl.Opacity = 1.0;
            };

            // Second tab
            var firstTab = (tabControl.Items[1] as TabItem);
            if (firstTab != null)
            {
                PreloadTab(tabControl, firstTab, onComplete);
            }
        }
    }
}

/// <summary>
/// Preloads an individual tab item.
/// </summary>
/// <param name="tabControl">The tab control.</param>
/// <param name="tabItem">The tab item.</param>
/// <param name="onComplete">The onComplete action.</param>
private static void PreloadTab(TabControl tabControl, TabItem tabItem, Action onComplete = null)
{
    // On update complete
    tabItem.Loaded += delegate
    {
    	// Update if not the last tab
    	if (tabItem != tabControl.Items.Last())
    	{
    	    // Get next tab
            var nextIndex = tabControl.Items.IndexOf(tabItem) + 1;
            var nextTabItem = tabControl.Items[nextIndex] as TabItem;

            // Preload
            if (nextTabItem != null)
            {
                PreloadTab(tabControl, nextTabItem, onComplete);
            }
        }

        else
        {
            if (onComplete != null)
            {
                onComplete();
            }
        }
    };

    // Set current tab context
    tabControl.SelectedItem = tabItem;
}

So what’s going on here?

  1. PreloadTabs acts as our public method where we pass the TabControl.
  2. The opacity of the TabControl is set to 0 (Objective 2). We do not change the visibility in this case because the UI will not properly update if Visibility is Collapsed.
  3. PreloadTabs will make sure it has tab items and then call our private method PreloadTab for the second tab in the collection (since the first tab would already be loaded by the instantiation of the TabControl).
  4. PreloadTab evaluates the second tab item in the collection by setting the TabControl.SelectedItem property to the current tab.
  5. Once the tab item has completed loading, it will be evaluated. (Objective 1)
  6. If it is not the last tab item in the collection, the next tab item is retrieved and passed to the PreloadTab method.
  7. Note: This behavior will repeat until the last item in the tab item collection has been reached.
  8. Once the last item has been reached, the onComplete action is called.
  9. When the onComplete action is called, we set the SelectedIndex of the TabControl back to 0 (Objective 2), and then restore the opacity of the TabControl back to 1 (Objective 3).

And just like that we have a tab pre-loader for our TabControl.

That’s all for now. Happy coding.

This article has been revised here.

I have seen several examples of different methods to perform a deep copy in C#. The most common one is with a Stream, implemented like this:


public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

That is certainly one way to do it but it won’t work in Silverlight.

The suggested Sliverlight approach:


public static T DeepCopy<T>(this T oSource)
{
    T oClone;
    DataContractSerializer dcs = new DataContractSerializer(typeof(T));

    using (MemoryStream ms = new MemoryStream())
    {
        dcs.WriteObject(ms, oSource);
        ms.Position = 0;
        oClone = (T)dcs.ReadObject(ms);
    }

    return oClone;
}

All right, that works in Silverlight but it requires that all objects passed are Serializable. That is actually a bit of a daunting task for custom objects at times, and we should try to use a simpler more universal approach.

So, I suggest using reflection:

/// <summary>
/// Copies all public properties from one object to another.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
/// <exception cref="System.Exception">Source and destination cannot be null and must be 
/// of the same type!</exception>
public static void DeepCopy(object source, object destination)
{
    if ((source != null &amp;&amp; destination != null) &amp;&amp; 
        (source.GetType() == destination.GetType()))
    {
        // Get properties
        var propertyInfos = source.GetType().GetProperties();
        if (propertyInfos.Length <= 0) return;

        // Evaluate
        foreach (var propInfo in propertyInfos)
        {
            // Process only public properties
            if (!propInfo.CanWrite) continue;

            // Get value from source and assign to destination
            object value = propInfo.GetValue(source, null);
            propInfo.SetValue(destination, value, null);
            if (value == null) continue;

            // Check for properties and propogate if they exist
            var newPropInfos = value.GetType().GetProperties();
            if (newPropInfos.Length > 0)
            {
                // Copy properties for each child where necessary
                DeepCopy(
                    source.GetType().GetProperty(propInfo.Name),
                    destination.GetType().GetProperty(propInfo.Name));
            }
        }
    }

    else
    {
        throw new Exception(
            "Source and destination cannot be null and must be of " +
            "the same type!");
    }
}

This will work in Silverlight because of the if block for propInfo.CanWrite. This is added because based on Silverlights rigid security constraints we can only copy public properties.

That’s all for now.

In this post I am going to introduce the idea of subscribing to property change events in our Model Base.

Note: Please read Part I before continuing here.
Note: Please read Part II before continuing here.

The purpose of this is to cover a potentially annoying situation:

Scenario: Let’s say you have a ViewModel, and inside that you have another ViewModel acting as a property like this:


    private MyCustomObject _customObjectInstance;

    public MyCustomObject CustomObjectInstance
    {
        get
        {
            return _customObjectInstance;
        }

        set
        {
            SetValue("CustomObjectInstance", ref _customObjectInstance, ref value);
        }
    }

Okay, now you want your UI bound to the current ViewModel to change every time CustomObjectInstance.MeterReading changes.

Q: (Panic moment) So, how do I do that without breaking my wonderful abstraction?
A: Implementing an ability to Subscribe to a nested property. All it takes is a little reflection and patience.

The idea is that we tell that property to fire a specific Action whenever it is changed in our ViewModelBase.

The first thing we will need to add is a private list of subscriptions:


/// <summary>
/// Subscription list.
/// </summary>
private readonly List<Tuple<string, Action<object>>> _subscriptions;

Instead of creating yet another custom object, I decided to use a Tuple because they are convenient.

  • The string value will serve as the name of the property you want to subscribe to.
  • The Action will serve as the Action you wish to call when the property is changed. The object parameter allows you to return an object if needed.

Next, we make sure to create a new instance of _subscriptions in the Constructor:


/// <summary>
/// Default constructor.
/// </summary>
protected ViewModelBase()
{
    _subscriptions = new List<Tuple<string, Action<object>>>();
}

Now we will expose a Subscribe method.

        /// <summary>
        /// Subscribes an action to a specific property that will be called
        /// during that property's OnPropertyChanged event.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChange"></param>
        public void Subscribe(string propertyName, Action<object> onChange)
        {
            // Verify property
            var propInfo = this.GetType().GetProperty(propertyName);

            // If valid, add to subscription pool.
            if (propInfo != null)
            {
                _subscriptions.Add(
                    new Tuple<string, Action<object>>(propertyName, onChange));
            }
            else
            {
                // Invalid property name provided.
                throw new Exception(
                    "Property "" + propertyName + "" could not be " +
                    "found for type "" + this.GetType().ToString() + ""!");
            }
        }

This idea here is fairly simple:

  • We pass our property name and intended Action that will fire OnPropertyChanged.
  • If the property name is not valid, we will throw an exception to ensure we didn’t pass invalid information into our Subscribe method.

Q: Alright, now we have a nice Tuple-list full of property names and Actions. Now what?
A: Glad you asked. Here comes the hard part:


        /// <summary>
        /// Processes existing subscriptions matching the provided property name.
        /// </summary>
        /// <param name="propertyName"></param>
        private void ProcessSubscriptions(string propertyName)
        {
            // Get matching subscriptions
            var subList =
                (from p in _subscriptions
                 where p.Item1 == propertyName
                 select p).ToList();

            // Check if any matches were found.
            if (subList.Any())
            {
                // Process actions
                foreach (var sub in subList)
                {
                    // Evaluate action
                    var onChange = sub.Item2;
                    if (onChange != null)
                    {
                        // Get property value by name
                        var propInfo = this.GetType().GetProperty(propertyName);
                        var propValue = propInfo.GetValue(this, null);

                        // Invoke action
                        onChange(propValue);
                    }
                }
            }
        }

ProcessSubscriptions does the following:

  • Looks up a specific property by name in _subscriptions and gets a list of all entries that are registered for that property.
  • Loop: If a specific entry has a valid Action assigned to it, it will use reflection to get that property value and pass it to the action (as out object parameter mentioned earlier).

So, the last piece is making sure ProcessSubscriptions is fired when the property has been changed. And that is as easy as augmenting our trusted OnPropertyChanged method:


        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChanged"></param>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                // Call handler
                handler(this, new PropertyChangedEventArgs(propertyName));

                // Subscriptions
                ProcessSubscriptions(propertyName);

                // On changed
                if (onChanged != null)
                {
                    onChanged();
                }
            }
        }

No worries if your specific property being changed is without entries. ProcessSubscriptions only acts on what is present in _subscriptions, so no entries means it just moves on.

Here is how you would use it in your parent ViewModel:


CustomObjectInstance.Subscribe("MeterReading", obj => MyActionThatDoesStuff());

Now, every time CustomObjectInstance.MeterReading is updated, the MyActionThatDoesStuff Action will be called allowing you to always have the latest values from your nested properties.

Here is our new ViewModelBase in it’s entirety:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #region Members

        /// <summary>
        /// Subscription list.
        /// </summary>
        private readonly List<Tuple<string, Action<object>>> _subscriptions;

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        protected ViewModelBase()
        {
            _subscriptions = new List<Tuple<string, Action<object>>>();
        }

        #endregion

        #region Methods

        /// <summary>
        /// To be used within the "set" accessor in each property.
        /// This invokes the OnPropertyChanged method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="newValue"></param>
        /// <param name="onChanged"></param>
        protected void SetValue<T>(string name, ref T value, ref  T newValue,
            Action onChanged = null)
        {
            if (newValue != null)
            {
                if (!newValue.Equals(value))
                {
                    value = newValue;
                    OnPropertyChanged(name, onChanged);
                }
            }
            else
            {
                value = default(T);
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// The PropertyChanged event handler.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Calls the PropertyChanged event
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChanged"></param>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                // Call handler
                handler(this, new PropertyChangedEventArgs(propertyName));

                // Subscriptions
                ProcessSubscriptions(propertyName);

                // On changed
                if (onChanged != null)
                {
                    onChanged();
                }
            }
        }

        /// <summary>
        /// Subscribes an action to a specific property that will be called
        /// during that property's OnPropertyChanged event.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="onChange"></param>
        public void Subscribe(string propertyName, Action<object> onChange)
        {
            // Verify property
            var propInfo = this.GetType().GetProperty(propertyName);

            // If valid, add to subscription pool.
            if (propInfo != null)
            {
                _subscriptions.Add(
                    new Tuple<string, Action<object>>(propertyName, onChange));
            }
            else
            {
                // Invalid property name provided.
                throw new Exception(
                    "Property "" + propertyName + "" could not be " +
                    "found for type "" + this.GetType().ToString() + ""!");
            }
        }
        
        /// <summary>
        /// Clears the subscriptions.
        /// </summary>
        public void ClearSubscriptions()
        {
            _subscriptions.Clear();
        }

        /// <summary>
        /// Processes existing subscriptions matching the provided property name.
        /// </summary>
        /// <param name="propertyName"></param>
        private void ProcessSubscriptions(string propertyName)
        {
            // Get matching subscriptions
            var subList =
                (from p in _subscriptions
                 where p.Item1 == propertyName
                 select p).ToList();

            // Check if any matches were found.
            if (subList.Any())
            {
                // Process actions
                foreach (var sub in subList)
                {
                    // Evaluate action
                    var onChange = sub.Item2;
                    if (onChange != null)
                    {
                        // Get property value by name
                        var propInfo = this.GetType().GetProperty(propertyName);
                        var propValue = propInfo.GetValue(this, null);

                        // Invoke action
                        onChange(propValue);
                    }
                }
            }
        }

        #endregion
    }

I hope this has been helpful for you.

Note: This topic has been updated with a complete solution in Part 2.

So, I was spending some time trying to figure out how to make a menu in WPF scrollable before it maxed out on my screen height. Well, unfortunately, the only way to do it is to override the default Aero template for the MenuItem in WPF. You can get that here.

Here’s the section to change. Notice the addition of “MaxHeight” on the “SubMenuScrollViewer” object. It’s really that simple.


<Popup x:Name="PART_Popup"
    AllowsTransparency="true"
    Placement="Right"
    VerticalOffset="-3"
    HorizontalOffset="-2"
    IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
    Focusable="false"
    PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
    <theme:SystemDropShadowChrome Name="Shdw" Color="Transparent">
        <ContentControl Name="SubMenuBorder"
            Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
            IsTabStop="false">
            <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                <Grid RenderOptions.ClearTypeHint="Enabled">
                    <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                        <Rectangle
                            Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                            Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                            Fill="{StaticResource SubMenuBackgroundBrush}" />
                    </Canvas>
                    <ItemsPresenter Name="ItemsPresenter" Margin="2"
                        KeyboardNavigation.TabNavigation="Cycle"
                        KeyboardNavigation.DirectionalNavigation="Cycle"
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                        Grid.IsSharedSizeScope="true"/>
                </Grid>
            </ScrollViewer>
        </ContentControl>
    </theme:SystemDropShadowChrome>
</Popup>

 

Here is a screenshot of the desired effect:

Example of Height-Controlled Menu

Here is the full Resource Dictionary xaml:


<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">

    <MenuScrollingVisibilityConverter x:Key="MenuScrollingVisibilityConverter"/>
    <Geometry x:Key="DownArrow">M 0,0 L 3.5,4 L 7,0 Z</Geometry>
    <Geometry x:Key="UpArrow">M 0,4 L 3.5,0 L 7,4 Z</Geometry>
    <Geometry x:Key="RightArrow">M 0,0 L 4,3.5 L 0,7 Z</Geometry>
    <Geometry x:Key="Checkmark">M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z</Geometry>
    <SolidColorBrush x:Key="SubMenuBackgroundBrush" Color="#FFF5F5F5" />

    <LinearGradientBrush x:Key="MenuItemSelectionFill"
                         StartPoint="0,0"
                         EndPoint="0,1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#34C5EBFF"
                          Offset="0"/>
            <GradientStop Color="#3481D8FF"
                          Offset="1"/>
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key="MenuItemPressedFill"
                         StartPoint="0,0"
                         EndPoint="0,1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#28717070"
                          Offset="0"/>
            <GradientStop Color="#50717070"
                          Offset="0.75"/>
            <GradientStop Color="#90717070"
                          Offset="1"/>
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
    <Style x:Key="{x:Static MenuItem.SeparatorStyleKey}"
           TargetType="{x:Type Separator}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Separator}">
                    <Grid SnapsToDevicePixels="true" Margin="0,6,0,4">
                        <Rectangle Height="1"
                                   Margin="30,0,1,1"
                                   Fill="#E0E0E0"/>
                        <Rectangle Height="1"
                                   Margin="30,1,1,0"
                                   Fill="White"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle x:Name="OuterBorder"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle Name="Bg"
                       Margin="1"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="1"
                       RadiusY="1"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="2"/>
            <DockPanel>
                <ContentPresenter x:Name="Icon"
                                  Margin="4,0,6,0"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Path x:Name="GlyphPanel"
                      Margin="7,0,0,0"
                      Visibility="Collapsed"
                      VerticalAlignment="Center"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      FlowDirection="LeftToRight"
                      Data="{StaticResource Checkmark}"/>
                <ContentPresenter ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </DockPanel>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#90717070"/>
                <Setter TargetName="OuterBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>

            </Trigger>
            <Trigger Property="IsKeyboardFocused"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle x:Name="OuterBorder"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle Name="Bg"
                       Margin="1"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="1"
                       RadiusY="1"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="2"/>
            <DockPanel>
                <ContentPresenter x:Name="Icon"
                                  Margin="4,0,6,0"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Path x:Name="GlyphPanel"
                      Margin="7,0,0,0"
                      Visibility="Collapsed"
                      VerticalAlignment="Center"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      FlowDirection="LeftToRight"
                      Data="{StaticResource Checkmark}"/>
                <ContentPresenter ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </DockPanel>
            <Popup x:Name="PART_Popup"
                   HorizontalOffset="1"
                   VerticalOffset="-1"
                   AllowsTransparency="true"
                   Placement="Bottom"
                   IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
                   Focusable="false"
                   PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                <theme:SystemDropShadowChrome Name="Shdw"
                                              Color="Transparent">
                    <ContentControl Name="SubMenuBorder"
                                    Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
                                    IsTabStop="false">
                        <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                                      Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                            <Grid RenderOptions.ClearTypeHint="Enabled">
                                <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                    <Rectangle
                                    Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                                    Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                                    Fill="{StaticResource SubMenuBackgroundBrush}" />
                                </Canvas>
                                <ItemsPresenter Name="ItemsPresenter" Margin="2"
                                            KeyboardNavigation.TabNavigation="Cycle"
                                            KeyboardNavigation.DirectionalNavigation="Cycle"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                            Grid.IsSharedSizeScope="true"/>
                            </Grid>
                        </ScrollViewer>
                    </ContentControl>
                </theme:SystemDropShadowChrome>
            </Popup>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation"
                     Value="true">
                <Setter TargetName="PART_Popup"
                        Property="PopupAnimation"
                        Value="None"/>
            </Trigger>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger SourceName="PART_Popup"
                     Property="Popup.HasDropShadow"
                     Value="true">
                <Setter TargetName="Shdw"
                        Property="Margin"
                        Value="0,0,5,5"/>
                <Setter TargetName="Shdw"
                        Property="Color"
                        Value="#71000000"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#90717070"/>
                <Setter TargetName="OuterBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50FFFFFF"/>

            </Trigger>
            <Trigger Property="IsKeyboardFocused"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsSubmenuOpen"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#E0717070"/>
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemPressedFill}"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#50747272"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!-- Submenu -->
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle Name="Bg"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="24"
                                      Width="Auto"
                                      SharedSizeGroup="MenuItemIconColumnGroup"/>
                    <ColumnDefinition Width="4"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="37"/>
                    <ColumnDefinition Width="Auto"
                                      SharedSizeGroup="MenuItemIGTColumnGroup"/>
                    <ColumnDefinition Width="17"/>
                </Grid.ColumnDefinitions>
                <ContentPresenter x:Name="Icon"
                                  Margin="1"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Border x:Name="GlyphPanel"
                        Background="#E6EFF4"
                        BorderBrush="#CDD3E6"
                        BorderThickness="1"
                        CornerRadius="3"
                        Margin="1"
                        Visibility="Hidden"
                        Width="22" 
                        Height="22">
                    <Path Name="Glyph"
                          Width="9"
                          Height="11"
                          Fill="#0C12A1"
                          FlowDirection="LeftToRight"
                          Data="{StaticResource Checkmark}"/>
                </Border>
                <ContentPresenter Grid.Column="2"
                                  ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <TextBlock Grid.Column="4"
                           Text="{TemplateBinding MenuItem.InputGestureText}"
                           Margin="{TemplateBinding MenuItem.Padding}"/>
            </Grid>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemSelectionFill}"/>
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#8071CBF1"/>
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#40FFFFFF"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Background"
                        Value="#EEE9E9"/>
                <Setter TargetName="GlyphPanel"
                        Property="BorderBrush"
                        Value="#DBD6D6"/>
                <Setter TargetName="Glyph"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}"
                     TargetType="{x:Type MenuItem}">
        <Grid SnapsToDevicePixels="true">
            <Rectangle Name="Bg"
                       Fill="{TemplateBinding MenuItem.Background}"
                       Stroke="{TemplateBinding MenuItem.BorderBrush}"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Rectangle x:Name="InnerBorder"
                       Margin="1"
                       Stroke="Transparent"
                       StrokeThickness="1"
                       RadiusX="2"
                       RadiusY="2"/>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="24"
                                      Width="Auto"
                                      SharedSizeGroup="MenuItemIconColumnGroup"/>
                    <ColumnDefinition Width="4"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="37"/>
                    <ColumnDefinition Width="Auto"
                                      SharedSizeGroup="MenuItemIGTColumnGroup"/>
                    <ColumnDefinition Width="17"/>
                </Grid.ColumnDefinitions>
                <ContentPresenter x:Name="Icon"
                                  Margin="1"
                                  VerticalAlignment="Center"
                                  ContentSource="Icon"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <Border x:Name="GlyphPanel"
                        Background="#E6EFF4"
                        BorderBrush="#CDD3E6"
                        BorderThickness="1"
                        CornerRadius="3"
                        Margin="1"
                        Visibility="Hidden"
                        Width="22" 
                        Height="22">
                    <Path Name="Glyph"
                          Width="9"
                          Height="11"
                          Fill="#0C12A1"
                          FlowDirection="LeftToRight"
                          Data="{StaticResource Checkmark}"/>
                </Border>
                <ContentPresenter Grid.Column="2"
                                  ContentSource="Header"
                                  Margin="{TemplateBinding MenuItem.Padding}"
                                  RecognizesAccessKey="True"
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                <TextBlock Grid.Column="4"
                           Text="{TemplateBinding MenuItem.InputGestureText}"
                           Margin="{TemplateBinding MenuItem.Padding}"
                           Visibility="Collapsed"/>
                <Path Grid.Column="5"
                      VerticalAlignment="Center"
                      Margin="4,0,0,0"
                      Fill="{TemplateBinding MenuItem.Foreground}"
                      Data="{StaticResource RightArrow}"/>
            </Grid>
            <Popup x:Name="PART_Popup"
                   AllowsTransparency="true"
                   Placement="Right"
                   VerticalOffset="-3"
                   HorizontalOffset="-2"
                   IsOpen="{Binding Path=IsSubmenuOpen,RelativeSource={RelativeSource TemplatedParent}}"
                   Focusable="false"
                   PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
                <theme:SystemDropShadowChrome Name="Shdw"
                                              Color="Transparent">
                    <ContentControl Name="SubMenuBorder"
                                    Template="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=SubmenuContent}}"
                                    IsTabStop="false">
                        <ScrollViewer Name="SubMenuScrollViewer" CanContentScroll="true" MaxHeight="400"
                                      Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}}">
                            <Grid RenderOptions.ClearTypeHint="Enabled">
                                <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                    <Rectangle
                                    Height="{Binding ElementName=SubMenuBorder,Path=ActualHeight}" 
                                    Width="{Binding ElementName=SubMenuBorder,Path=ActualWidth}" 
                                    Fill="{StaticResource SubMenuBackgroundBrush}" />
                                </Canvas>
                                <ItemsPresenter Name="ItemsPresenter" Margin="2"
                                            KeyboardNavigation.TabNavigation="Cycle"
                                            KeyboardNavigation.DirectionalNavigation="Cycle"
                                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                            Grid.IsSharedSizeScope="true"/>
                            </Grid>
                        </ScrollViewer>
                    </ContentControl>
                </theme:SystemDropShadowChrome>
            </Popup>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation"
                     Value="true">
                <Setter TargetName="PART_Popup"
                        Property="PopupAnimation"
                        Value="None"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="InnerBorder"
                        Property="Stroke"
                        Value="#D1DBF4FF"/>
            </Trigger>
            <Trigger Property="Icon"
                     Value="{x:Null}">
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsChecked"
                     Value="true">
                <Setter TargetName="GlyphPanel"
                        Property="Visibility"
                        Value="Visible"/>
                <Setter TargetName="Icon"
                        Property="Visibility"
                        Value="Collapsed"/>
            </Trigger>
            <Trigger SourceName="PART_Popup"
                     Property="Popup.HasDropShadow"
                     Value="true">
                <Setter TargetName="Shdw"
                        Property="Margin"
                        Value="0,0,5,5"/>
                <Setter TargetName="Shdw"
                        Property="Color"
                        Value="#71000000"/>
            </Trigger>
            <Trigger Property="IsHighlighted"
                     Value="true">
                <Setter TargetName="Bg"
                        Property="Fill"
                        Value="{StaticResource MenuItemSelectionFill}"/>
                <Setter TargetName="Bg"
                        Property="Stroke"
                        Value="#8571CBF1"/>
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Foreground"
                        Value="#FF9A9A9A"/>
                <Setter TargetName="GlyphPanel"
                        Property="Background"
                        Value="#EEE9E9"/>
                <Setter TargetName="GlyphPanel"
                        Property="BorderBrush"
                        Value="#DBD6D6"/>
                <Setter TargetName="Glyph"
                        Property="Fill"
                        Value="#848589"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <Style x:Key="{x:Type MenuItem}"
           TargetType="{x:Type MenuItem}">
        <Setter Property="HorizontalContentAlignment"
                Value="{Binding Path=HorizontalContentAlignment,RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment"
                Value="{Binding Path=VerticalContentAlignment,RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Background"
                Value="Transparent"/>
        <Setter Property="ScrollViewer.PanningMode"
                Value="Both"/>
        <Setter Property="Stylus.IsFlicksEnabled"
                Value="False"/>
        <Setter Property="Template"
                Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}}"/>
        <Style.Triggers>
            <Trigger Property="Role"
                     Value="TopLevelHeader">
                <Setter Property="Padding"
                        Value="7,2,8,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="TopLevelItem">
                <Setter Property="Padding"
                        Value="7,2,8,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="SubmenuHeader">
                <Setter Property="Padding"
                        Value="2,3,2,3"/>
                <Setter Property="Template"
                        Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}}"/>
            </Trigger>
            <Trigger Property="Role"
                     Value="SubmenuItem">
                <Setter Property="Padding"
                        Value="2,3,2,3"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

It’s not pretty. I hope MS adds the ability to change the height in future versions as I would rather not write a separate extended control just for this.

Until next time…