All posts tagged WPF

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 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.

In this article, we are going to look at calling an Action when our property changes.

Note: Please read Part I before continuing here.

Alright, so let’s look at our ViewModelBase from Part I:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #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>
        protected void SetValue<T>(string name, ref T value, ref  T newValue)
        {
            if (newValue != null)
            {
                if (!newValue.Equals(value))
                {
                    value = newValue;
                    OnPropertyChanged(name);
                }
            }
            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>
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }

Q: Okay, so what now?
A: Glad you asked. Let’s augment the above class:


    /// <summary>
    /// Extends the INotifyPropertyChanged interface to the class properties.
    /// </summary>
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #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>
        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>
        protected void OnPropertyChanged(string propertyName, Action onChanged = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));

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

        #endregion
    }

Essentially, we added an Action as a parameter to the SetValue method. This is then passed whenever the OnPropertyChanged method is called and executed after the event fires (if it is not null).

Using it is as easy as this:


    private string phoneNumberValue = String.Empty;

    public string PhoneNumber
    {
        get
        {
            return this.phoneNumberValue;
        }

        set
        {
            SetValue("PhoneNumber", ref this.phoneNumberValue, ref value, () => { UpdateSomeOtherUISection(); });
        }
    }

It’s not a common need, but sometimes something like this can really get you out of a bind when you need to update other areas of your application when this property changes.

Try it sometime.

Having used properties in an ObservableCollection, you are probably familiar with implementing INotifyPropertyChanged in your custom objects.

A property utilizing this implementation would look like this:

private string phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get
	{
		return this.phoneNumberValue;
	}

	set
	{
		if (value != this.phoneNumberValue)
		{
			this.phoneNumberValue = value;
			NotifyPropertyChanged("PhoneNumber");
		}
	}
}

To remedy this repetitive implementation, I created this:

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

	/// <summary>
	/// To be used within the "set" accessor in each property.
	/// This invokes the OnPropertyChanged method.
	/// </summary>
	/// <typeparam name="T"></param>
	/// <param name="name"></param>
	/// <param name="value"></param>
	/// <param name="newValue"></param>
	protected void SetValue&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt;(string name, ref T value, ref  T newValue)
	{
		if (newValue != null)
		{
			if (!newValue.Equals(value))
			{
				value = newValue;
				OnPropertyChanged(name);
			}
		}
		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>
	protected void OnPropertyChanged(string propertyName)
	{
		PropertyChangedEventHandler handler = PropertyChanged;
		if (handler != null)
		{
			handler(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	#endregion
}

Then, all we need to do is inherit ViewModelBase in our custom object.

Now, our property would look something like this:

private string phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get
	{
		return this.phoneNumberValue;
	}

	set
	{
		SetValue("PhoneNumber", ref this.phoneNumberValue, ref value);
	}
}

So, what was the reason for doing this?

  1. It avoids directly implementing INotifyPropertyChanged. This allows the ability to start up objects with this implementation much faster.
  2. Less coding and potential messes involving the set operator.

Update as of August 2022

Good news is since all the C# goodness over the last 10 years, I have greatly improved this implementation. It can be used like this:

private string _phoneNumberValue = String.Empty;

public string PhoneNumber
{
	get => _phoneNumberValue;
	set => NotifyOfChange(value, ref _phoneNumberValue);
}

Simple, right?

Customizing the WPF MenuItem in XAML

Categories: .Net, C#, WPF, XAML
Comments: 4

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

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…