All posts tagged WPF

Create an MVVM-based MAUI Application

Categories: .Net, C#, MAUI, MVVM, WPF, XAML
Comments: No

What is MAUI?

Microsoft released MAUI only a few weeks ago and it serves as the successor to UWP and Xamarin in the platform-agnostic space. Consider it Microsoft’s competitor with the Javascript-based UI frameworks we have all become familiar with over the last 10 years. You can read more about MAUI here:

https://docs.microsoft.com/en-us/dotnet/maui/what-is-maui

Paths to building the MAUI front-end

Microsoft offers 2 different paths to building your MAUI front-end: XAML or Blazor. In this article we will be looking at the default XAML application and we will modify it to utilize MVVM like we have been doing for years with other XAML implementation. The end result will offer better abstraction and simpler UI implementation. If you don’t know what MVVM is, read about it here:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm

Let’s get to it…

Prerequisites

  • Visual Studio 17.3+ (2022+)

Step 1 – Create a new MAUI project

  1. In Visual Studio 2022 for to File -> New -> Project.
  2. Search for .NET MAUI App, select, and click Next.
  3. Make the Project Name MauiMvvmDemo, choose your repository location, check Place solution and project in the same directory, and click Next.
  4. For Framework choose .NET 6.0 (Long-term support) and click Create.

Step 2 – Install Libraries

  1. Open the Nuget Browser, by navigating to Tools -> NuGet Package Manager -> Manage Nuget Packages for Solution…

    Step 2 - Open Nuger Browser

  2. Select Browse and search for Xcalibur.Extensions.MVVM.V2.
  3. Select your project in the right pane (MauiMvvmDemo) and Choose Install. Accept the licensing prompts (if they appear).

    Step 2 - Install Library

  4. Close the Nuget Solution tab.

Step 3 – Create the ViewModel

  1. Create a new class in the project root and call it MainPageViewModel.cs.
  2. Paste in the following code:
using System.Windows.Input;
using Xcalibur.Extensions.MVVM.V2.Models;

namespace MauiMvvmDemo
{
    /// <summary>
    /// View Model - Main
    /// </summary>
    /// <seealso cref="ModelBase" />
    internal class MainViewModel : ModelBase
    {
        #region Members

        private int _count;
        private string _buttonText;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the button text.
        /// </summary>
        /// <value>
        /// The button text.
        /// </value>
        public string ButtonText
        {
            get => _buttonText;
            set => NotifyOfChange(value, ref _buttonText);
        }

        /// <summary>
        /// Gets or sets the button command.
        /// </summary>
        /// <value>
        /// The button command.
        /// </value>
        public ICommand ButtonCommand => new Command(UpdateCount);

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MainViewModel"/> class.
        /// </summary>
        public MainViewModel()
        {
            ButtonText = "Click me!";
        }

        #endregion

        #region Methods

        /// <summary>
        /// Updates the count command.
        /// </summary>
        /// <returns></returns>
        private void UpdateCount()
        {
            _count++;

            // Update text
            var suffix = _count == 1 ? "time" : "times";
            var text = $"Clicked this {_count} {suffix}!";
            ButtonText = text;

            // Announce to screen reader
            SemanticScreenReader.Announce(text);
        }

        #endregion
    }
}

Deep Dive – What are the key pieces of this ViewModel?

Since we are working with a ViewModel, it is important to notice we are only working with properties and methods rather than control-based events.

ModelBase

https://xcalibursystems.com/wp-content/Documentation/Xcalibur.Extensions.MVVM/html/bb64d759-d714-fe71-b1c3-7ec574cdd8c2.htm

This class does a lot of the INPC work for us and makes implementation a lot simpler.

Here is the key:

/// <summary>
/// Gets or sets the button text.
/// </summary>
/// <value>
/// The button text.
/// </value>
public string ButtonText
{
	get => _buttonText;
	set => NotifyOfChange(value, ref _buttonText);
}

All of the INPC goodness can be simplified to a property using NotifyOfChange with a backing field.
https://xcalibursystems.com/creating-a-viewmodel-base-part-i-cleaning-up-your-custom-objects/

ICommand

Back in the WPF days we used to have to create a separate class to inherit from ICommand in order to make clean commands for WPF to trigger. Since those days this has been dramatically simplified as such:

/// <summary>
/// Gets or sets the button command.
/// </summary>
/// <value>
/// The button command.
/// </value>
public ICommand ButtonCommand => new Command(UpdateCount);

All you need to do is invoke a new Command and call your method, which can be made private.

UpdateCount

Lastly, we have our UpdateCount method which handles updating the button text via a bound property rather than affecting the control directly.

/// <summary>
/// Updates the count command.
/// </summary>
/// <returns></returns>
private void UpdateCount()
{
	_count++;

	// Update text
	var suffix = _count == 1 ? "time" : "times";
	var text = $"Clicked this {_count} {suffix}!";
	ButtonText = text;

	// Announce to screen reader
	SemanticScreenReader.Announce(text);
}

Anyhow, let’s continue…

Step 4 – Update MainPage.xaml

Let’s update the XAML file with the following code to use our ViewModel:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MauiMvvmDemo"
             x:Class="MauiMvvmDemo.MainPage">

    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />

            <Label
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Label
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button
                Text="{Binding Path=ButtonText}"
                SemanticProperties.Hint="Counts the number of times you click"
                Command="{Binding Path=ButtonCommand}"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Deep Dive – What are the key changes?

Now that we have a ViewModel instead of direct calls in the codebehind, we will need to make a few key changes.

The Binding Context

In WPF we typically did this from the codebehind. To simplify that code, we add this to the XAML with the following block:

<ContentPage.BindingContext>
	<vm:MainViewModel />
</ContentPage.BindingContext>

Now our XAML’s binding context is our MainViewModel as desired.

The Button

For this example, the heart of the binding centers on the Button:

<Button
	Text="{Binding Path=ButtonText}"
	SemanticProperties.Hint="Counts the number of times you click"
	Command="{Binding Path=ButtonCommand}"
	HorizontalOptions="Center" />
  • ButtonText gives us the current button text.
  • ButtonCommand maps to our button click action.

Step 5 – Cleanup Mainpage.xaml.cs

Let’s update our codebehind page to be simply:

namespace MauiMvvmDemo;

public partial class MainPage : ContentPage
{
	public MainPage()
	{
		InitializeComponent();
	}
}

Remember, the work done originally is now abstracted to the ViewModel. So, this codebehind doesn’t need to do anything but Initialize the UI.

Step 6 – Build

Note, this might take a little time the first time through.

Step 6 – Output Window

Step 7 – Run

Step 8 - Testing
Step 8 – Testing

Keep clicking the button to see the label update.

Step 8 – All Done!

That’s all there is to it. Happy coding….

This is the second of a 2-part series on Accessing Monitor Information with C#.

The series will include the following:

Like I said in Part 1 this uses Native Methods so don’t act surprised if I drop a few in this post.

Note: if easily startled by unmanaged code, please seek the help of a medical professional. Otherwise, read on.

So, to continue, we are going to start by using our GetMonitors method to grab the current set of Monitors associated with our computer:


private static IList<MonitorInfoWithHandle> _monitorInfos;

/// <summary>
/// Gets the monitors.
/// </summary>
/// <returns></returns>
public static MonitorInfoWithHandle[] GetMonitors()
{
    // New List
    _monitorInfos = new List<MonitorInfoWithHandle>();

    // Enumerate monitors
    NativeMonitorHelper.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);

    // Return list
    return _monitorInfos.ToArray();
}

Now, we won’t be using this method directly in this post, but you should observe the results to understand which handles are associated with each monitor on your machine. For this purposes of this exercise, we will be looking from the process-side and determining which monitor handle it relates to.

So, to continue we look to the native method EnumWindows:

 
EnumWindows
Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is enumerated or the callback function returns false.

 
EnumWindowsProc
An application-defined callback function used with the EnumWindows or EnumDesktopWindows function. It receives top-level window handles. The WNDENUMPROC type defines a pointer to this callback function. EnumWindowsProc is a placeholder for the application-defined function name.


/// <summary>
/// EnumWindows Processor (delegate)
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
public delegate bool EnumWindowsProc(IntPtr windowHandle, IntPtr lParam);

/// <summary>
/// Enums the windows.
/// </summary>
/// <param name="enumWindowsProcessorDelegate">The enum windows processor delegate.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc enumWindowsProcessorDelegate, IntPtr lParam);

The key to using this method is to provide it a delegate which will act on each instance discovered and help us extract the results.

To get the monitor related to a window handle we need to first get the RECT the window occupies with GetWindowRect.

 
GetWindowRect
Retrieves the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.


/// <summary>
/// Gets the rectangle representing the frame of a window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="rectangle">The rectangle.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr windowHandle, ref RECT rectangle);

 
Next we need to derive the monitor from the RECT found in our last operation and we will do that using MonitorFromRect:

MonitorFromRect
The MonitorFromRect function retrieves a handle to the display monitor that has the largest area of intersection with a specified rectangle.


/// <summary>
/// Monitors from rect.
/// </summary>
/// <param name="rectPointer">The RECT pointer.</param>
/// <param name="flags">The flags.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromRect([In] ref RECT rectPointer, uint flags);

 
So, now let’s put that all together with an example:


#region Members

private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
private IList<WindowAndMonitorHandle> _windowAndMonitorHandles;

#endregion

#region Methods

/// <summary>
/// Retrieves a list of all main window handles and their associated process id's.
/// </summary>
/// <returns></returns>
public static WindowAndMonitorHandle[] GetWindowAndMonitorHandles()
{
	// new list
	_windowAndMonitorHandles = new List<WindowAndMonitorHandle>();

	// Enumerate windows
	WindowHelper.EnumWindows(EnumTheWindows, IntPtr.Zero);

	// Return list
	return _windowAndMonitorHandles.ToArray();
}

/// <summary>
/// Enumerates through each window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
private static bool EnumTheWindows(IntPtr windowHandle, IntPtr lParam)
{
	// Get window area
	var rect = new RECT();
	MonitorHelper.GetWindowRect(windowHandle, ref rect);

	// Get current monitor
	var monitorHandle = MonitorHelper.MonitorFromRect(ref rect, MONITOR_DEFAULTTONEAREST);

	// Add to enumerated windows
	_windowAndMonitorHandles.Add(new WindowAndMonitorHandle(windowHandle, monitorHandle));
	return true;
}

#endregion

#region Native Methods

/// <summary>
/// EnumWindows Processor (delegate)
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The lparameter.</param>
/// <returns></returns>
public delegate bool EnumWindowsProc(IntPtr windowHandle, IntPtr lParam);

/// <summary>
/// Enums the windows.
/// </summary>
/// <param name="enumWindowsProcessorDelegate">The enum windows processor delegate.</param>
/// <param name="lParam">The lparameter.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc enumWindowsProcessorDelegate, IntPtr lParam);

/// <summary>
/// Gets the rectangle representing the frame of a window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="rectangle">The rectangle.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr windowHandle, ref RECT rectangle);

/// <summary>
/// Monitors from rect.
/// </summary>
/// <param name="rectPointer">The RECT pointer.</param>
/// <param name="flags">The flags.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromRect([In] ref RECT rectPointer, uint flags);

#endregion

/// <summary>
/// A simple class to group our handles.
/// </summary>
/// <returns></returns>
public class WindowAndMonitorHandle
{
	public IntPtr WindowHandle { get; }
	public IntPtr MonitorHandle { get; }
	
	public WindowAndMonitorHandle(IntPtr windowHandle, IntPtr monitorHandle)
	{
		WindowHandle = windowHandle;
		MonitorHandle = monitorHandle;
	}
}

 
Does it seem like a lot of work? Yes, definitely. With native methods, PAIN = GAIN. Don’t concern yourself with how much code it takes, but focus on what you are actually gaining. If you compare the monitor handle for each WindowAndMonitorHandle with what you returned from GetMonitors you should immediately be able to relate a window handle to a monitor.

So, until next time…

It’s been a little while since we last hung out, but I think it’s time for some more informative articles on crazy stuff some are afraid to try at home. This week we are going to look at reacting to when Alt+Tab is pressed in our WPF applications. To do this, we will be using the Native Methods in Windows via p/invoke as we have in previous articles.

 
The Native Methods
It has been accepted in the .Net community that you should use Native Methods only when the .Net Framework does not offer a managed solution. Manged is always better because it has been configured to be properly managed in memory, and to be recycled by the garbage collector. I tend to use Native Methods most often when I need to interact with an external process and there are no valid hooks in the Framework. This is one of those examples.

So, for today’s example we will use the following Native Methods:

SetWindowsHookEx
This will set our application procedure into a hook chain. Why a chain? Because we are listening for certain messages that relate to our key presses, but we are not attempting to replace what is already there.

For our example, we will be using WH_KEYBOARD_LL since we will be interacting with low-level keyboard events.

Additionally, we will need a low-level keyboard processor (LowLevelKeyboardProc) to carry out the work we want to perform, which consists of a code number, the keyboard action taken, and a struct (KBDLLHOOKSTRUCT) which allows us to get additional information about our keyboard action.

CallNextHookEx
Passes the hook information to the next procedure in the hook chain. We should make a habit of calling this after we do the work in our hook procedure to ensure the next procedures in the hook chain are properly invoked. It’s still a bit controversial if this is still needed, but the general opinion of our peers is that it should still be used.

GetKeyState
Gets the state of a provided virtual key. This will allow us to determine if the desired keys have been pressed.

UnhookWindowsHookEx
Removes a hook procedure from a hook chain. We will use this method to cleanup when we are finished with our work.

 
Okay, not so bad, right?

So, for this example we will add the code we need to App.xaml.cs. To start, here are our native methods that correspond with what we stated above:


/// <summary>
/// Hook Processor.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

/// <summary>
/// Sets a windows hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <param name="processor">The processor.</param>
/// <param name="moduleInstance">The module instance.</param>
/// <param name="threadId">The thread identifier.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int hookId, HookProc processor, IntPtr moduleInstance, int threadId);

/// <summary>
/// Unsets a windows hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int hookId);

/// <summary>
/// Calls the next hook.
/// </summary>
/// <param name="hookId">The hook identifier.</param>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int hookId, int code, IntPtr wParam, IntPtr lParam);

/// <summary>
/// Gets the state of a virtual key.
/// </summary>
/// <param name="virtualKey">The virtual key.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern short GetKeyState(int virtualKey);

 
Note: we can use HookProc for all of our processors because this is the standard delegate for interacting with SetWindowsHookEx. The key to getting information indigenous to a specific hook type is by casting the lParam parameter to the expected structure type. We’ll talk more about that below.

 
Next, we need to make sure we define our fields:


/// <summary>
/// Event hook types
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959%28v=vs.85%29.aspx
/// </summary>
public static class EventHookTypes
{
    public static readonly int WH_MSGFILTER = -1;
    public static readonly int WH_JOURNALRECORD = 0;
    public static readonly int WH_JOURNALPLAYBACK = 1;
    public static readonly int WH_KEYBOARD = 2;
    public static readonly int WH_GETMESSAGE = 3;
    public static readonly int WH_CALLWNDPROC = 4;
    public static readonly int WH_CBT = 5;
    public static readonly int WH_SYSMSGFILTER = 6;
    public static readonly int WH_MOUSE = 7;
    public static readonly int WH_HARDWARE = 8;
    public static readonly int WH_DEBUG = 9;
    public static readonly int WH_SHELL = 10;
    public static readonly int WH_FOREGROUNDIDLE = 11;
    public static readonly int WH_CALLWNDPROCRET = 12;
    public static readonly int WH_KEYBOARD_LL = 13;
    public static readonly int WH_MOUSE_LL = 14;
}

// Key states
const int KEYSTATE_NONE = 0;
const int KEYSTATE_NOTPRESSED_TOGGLED = 1;
const int KEYSTATE_PRESSED_TOGGLED = -128;
const int KEYSTATE_PRESSED_NOT_TOGGLED = -127;

/// <summary>
/// Contains information about a low-level keyboard input event.
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct KBLLHOOKSTRUCT
{
    public int KeyCode;
    public int ScanCode;
    public int Flags;
    public int Time;
    public int ExtraInfo;
}

private static int _hookHandle;
private static HookProc _altTabCallBack;

 
The event hook types represent all possible event hook types used by SetWindowsHookEx. Given, we don’t need more than one for this example but I wanted to give you the entire picture so you leave here looking to see how deep this rabbit hole goes.

The key states represent the different key states we want to track. There are different states for buttons that serve as toggles versus standard buttons.

KBLLHOOKSTRUCT is our expected structure which we will obtain by casting the lParam in our HookProc.

Lastly, we have private members for the hook handle, and the HookProc to ensure the Garbage Collector does not dispose of these prematurely. Remember, Native Methods are unmanaged, and without the proper references Garbage Collector will assume they are unused and collect them as soon as it checks. This can lead to a fun exception “A callback was made on a garbage collected delegate of type…” which I will talk about in a future post.

 
Creating the Processor

So, now that we have established the Native Methods and the private members it’s time to get to the heart of our code:


/// <summary>
/// Sets a keyboard Alt-Tab hook.
/// </summary>
private static void SetAltTabHook()
{
    // Set reference to callback
    _altTabCallBack = AltTabProcessor;

    // Set system-wide hook.
    _hookHandle = SetWindowsHookEx(EventHookTypes.WH_KEYBOARD_LL,
        _altTabCallBack, IntPtr.Zero, 0);
}

/// <summary>
/// The processor for Alt+Tab key presses.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="wParam">The w parameter.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
private static int AltTabProcessor(int code, IntPtr wParam, IntPtr lParam)
{
    const int altKey = 0x12;
    const int tabKey = 0x09;

    // Evaluate
    if (code >= 0)
    {
        var hookStruct = (KBLLHOOKSTRUCT)Marshal.PtrToStructure(
            lParam, typeof(KBLLHOOKSTRUCT));

        // Determine if the alt key is pressed
        bool isAltKeyDown = 
            GetKeyState(altKey) == KEYSTATE_PRESSED_NOT_TOGGLED ||
            GetKeyState(altKey) == KEYSTATE_PRESSED_TOGGLED;

        // In addition to the alt key being pressed, check if TAB is also pressed
        if (isAltKeyDown && hookStruct.KeyCode == tabKey)
        {
            // Do stuff when Alt+Tab is pressed
        }

        else
        {
            // Do stuff when Alt+Tab is released
        }
    }

    // Pass to other keyboard handlers. This allows other applications with hooks to 
    // get the notification.
    return CallNextHookEx(_hookHandle, code, wParam, lParam);
}

 
SetAltTabHook does 3 things:

  1. Sets a reference to our AltTabProcessor to ensure it’s not Garbage Collected.
  2. Uses SetWindowsHookEx to set the windows hook with a type of WH_KEYBOARD_LL and our AltTabProcessor for the local application.
  3. Stores the returned hook handle.

AltTabProcessor is our HookProc designed specifically to manage our Alt+Tab check. What this does:

  1. Checks to ensure the code returned is greater than or equal to HC_ACTION (0).
  2. Casts lParam to a structure of type KBLLHOOKSTRUCT to expose additional information about what was processed.
  3. Uses GetKeyState on our Alt key to ensure it has been pressed or toggled.
  4. Checks that in addition to the Alt key being pressed, that the KBLLHOOKSTRUCT.KeyCode is equal to the Tab key.
  5. Relays the hook information to the next hook procedure in the hook chain with CallNextHookEx.

 
Putting it all together

Our complete App.xaml.cs:


using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace WpfAltTabExample
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        #region Members

        /// <summary>
        /// Event hook types
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959%28v=vs.85%29.aspx
        /// </summary>
        public static class EventHookTypes
        {
            public static readonly int WH_MSGFILTER = -1;
            public static readonly int WH_JOURNALRECORD = 0;
            public static readonly int WH_JOURNALPLAYBACK = 1;
            public static readonly int WH_KEYBOARD = 2;
            public static readonly int WH_GETMESSAGE = 3;
            public static readonly int WH_CALLWNDPROC = 4;
            public static readonly int WH_CBT = 5;
            public static readonly int WH_SYSMSGFILTER = 6;
            public static readonly int WH_MOUSE = 7;
            public static readonly int WH_HARDWARE = 8;
            public static readonly int WH_DEBUG = 9;
            public static readonly int WH_SHELL = 10;
            public static readonly int WH_FOREGROUNDIDLE = 11;
            public static readonly int WH_CALLWNDPROCRET = 12;
            public static readonly int WH_KEYBOARD_LL = 13;
            public static readonly int WH_MOUSE_LL = 14;
        }

        // Key states
        const int KEYSTATE_NONE = 0;
        const int KEYSTATE_NOTPRESSED_TOGGLED = 1;
        const int KEYSTATE_PRESSED_TOGGLED = -128;
        const int KEYSTATE_PRESSED_NOT_TOGGLED = -127;

        /// <summary>
        /// Contains information about a low-level keyboard input event.
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct KBLLHOOKSTRUCT
        {
            public int KeyCode;
            public int ScanCode;
            public int Flags;
            public int Time;
            public int ExtraInfo;
        }

        private static int _hookHandle;
        private static HookProc _altTabCallBack;

        #endregion

        #region Methods

        /// <summary>
        /// Sets a keyboard Alt+Tab hook.
        /// </summary>
        private static void SetAltTabHook()
        {
            // Set reference to callback
            _altTabCallBack = AltTabProcessor;

            // Set system-wide hook.
            _hookHandle = SetWindowsHookEx(EventHookTypes.WH_KEYBOARD_LL,
                _altTabCallBack, IntPtr.Zero, 0);
        }

        /// <summary>
        /// The processor for Alt+Tab key presses.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        private static int AltTabProcessor(int code, IntPtr wParam, IntPtr lParam)
        {
            const int altKey = 0x12;
            const int tabKey = 0x09;

            // Evaluate
            if (code >= 0)
            {
                var hookStruct = (KBLLHOOKSTRUCT)Marshal.PtrToStructure(
                    lParam, typeof(KBLLHOOKSTRUCT));

                // Determine if the alt key is pressed
                bool isAltKeyDown = 
                    GetKeyState(altKey) == KEYSTATE_PRESSED_NOT_TOGGLED ||
                    GetKeyState(altKey) == KEYSTATE_PRESSED_TOGGLED;

                // In addition to the alt key being pressed, check if TAB is also pressed
                if (isAltKeyDown && hookStruct.KeyCode == tabKey)
                {
                    // Do stuff when Alt+Tab is pressed
                }

                else
                {
                    // Do stuff when Alt+Tab is released
                }
            }

            // Pass to other keyboard handlers. This allows other applications with hooks to 
            // get the notification.
            return CallNextHookEx(_hookHandle, code, wParam, lParam);
        }

        #endregion

        #region Events

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Startup" /> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnStartup(StartupEventArgs e)
        {
            // Startup
            SetAltTabHook();

            // Base
            base.OnStartup(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Exit" /> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnExit(ExitEventArgs e)
        {
            // Unhook
            UnhookWindowsHookEx(_hookHandle);

            // Base
            base.OnExit(e);
        }

        #endregion

        #region Native Methods

        /// <summary>
        /// Hook Processor.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Sets a windows hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <param name="processor">The processor.</param>
        /// <param name="moduleInstance">The module instance.</param>
        /// <param name="threadId">The thread identifier.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int hookId, HookProc processor, IntPtr moduleInstance, int threadId);

        /// <summary>
        /// Unsets a windows hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int hookId);

        /// <summary>
        /// Calls the next hook.
        /// </summary>
        /// <param name="hookId">The hook identifier.</param>
        /// <param name="code">The code.</param>
        /// <param name="wParam">The w parameter.</param>
        /// <param name="lParam">The l parameter.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int hookId, int code, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Gets the state of a virtual key.
        /// </summary>
        /// <param name="virtualKey">The virtual key.</param>
        /// <returns></returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern short GetKeyState(int virtualKey);

        #endregion
    }
}

 
Final Notes:
Notice we call UnhookWindowsHookEx(_hookHandle) in OnExit. This is done to responsibly remove our hook processor from its hook chain. You should always plan on performing cleanup actions on unmanaged code, otherwise you can’t guarantee it will get released from memory and that’s just plain sloppy.

We are far from done with Native Methods, so stay tuned!

Happy Coding.

That’s what’s up. We are going to stop windows from stealing focus with C#. No frills, no crap, no more of this nonsense. It’s time to stop.

Q: Why does Windows do this and why isn’t there a setting to fix this?
A: Remember Fred Johnson who lived down the street in the 70’s? You know… that slightly overweight jerk that bullied the smart kids? Well, one day on the way home from school he kicked Billy Gates dog and said something off color about his “parentage”. So, now we need to suffer for Fred’s sins. No need to Wiki this, it’s a true story.

 
Joking Aside

So, back in the early 2000’s we had the Windows XP PowerToy called TweakUI which allowed us to control how initialized windows interacted with the desktop environment. This involved changing the following registry key:

HKEY_CURRENT_USER\Control Panel\Desktop\Foreground\LockTimeout

In the modern versions of Windows, this override no longer works. For a long while, this left us SOL and drifting in space looking for the answer… that is until now.

 
The Solution

In my opinion, the C# community was right in thinking the best approach is to get on the P/Invoke track.

For this solution the key is LockSetForegroundWindow on a timer. For this solution we will use the following methods:


// Lock
public static uint LSFW_LOCK = 1;
public static uint LSFW_UNLOCK = 2;

/// <summary>
/// Locks the set foreground window.
/// </summary>
/// <param name="uLockCode">The u lock code.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool LockSetForegroundWindow(uint uLockCode);

/// <summary>
/// Gets the foreground window.
/// </summary>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

 
Now that we have identified the P/Invoke piece, let’s look at how we can implement this effectively with said timer in a WPF application. To do this we are going to create a controller to facilitate all our focus needs:


using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Threading;

using Win32Windows = Xcalibur.Win32.Win32ApiHelper.Windows;

namespace Xcalibur.DontInterruptMe
{
    /// <summary>
    /// Window focus controller.
    /// </summary>
    public class FocusController
    {
        #region Members
        
        private readonly DispatcherTimer _timer;
        private IntPtr _currentHandle;
        private bool _isRunning;
        private bool _isStarted;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="FocusController"/> class.
        /// </summary>
        public FocusController()
        {
            this._isRunning = false;
            this._isStarted = false;

            // Set timer
            this._timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
            this._timer.Tick += (s, e) => this.EvaluateAsync(); 

            // Start
            this.Start();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            if (this._isStarted) return;

            // Start timer
            this._timer.IsEnabled = true;
            this._timer.Start();
            this._isStarted = true;
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            if (!this._isStarted) return;

            // Stop timer
            this._timer.Stop();
            this._timer.IsEnabled = false;
            this._isStarted = false;

            // Unlock set foreground window
            LockSetForegroundWindow(LSFW_UNLOCK);
        }

        /// <summary>
        /// Evaluates this instance.
        /// </summary>
        private void Evaluate()
        {
            if (_isRunning) return;

            // Set as "running"
            _isRunning = true;

            // Get current
            var activeWindowHandle = GetForegroundWindow();
            if (_currentHandle == activeWindowHandle)
            {
                _isRunning = false;
                return;
            }

            // Store current handle
            _currentHandle = activeWindowHandle;

            // Handle cannot be 0
            if (activeWindowHandle == IntPtr.Zero)
            {
                _isRunning = false;
                return;
            }

            // Get related process
            var processes = Process.GetProcesses();
            var currentProcess = processes.FirstOrDefault(x => x.MainWindowHandle == _currentHandle);

            // currentProcess must exist, and the MainWindowTitle must be valid.
            if (currentProcess == null || string.IsNullOrEmpty(currentProcess.MainWindowTitle))
            {
                _isRunning = false;
                return;
            }

            // Lock set foreground window
            LockSetForegroundWindow(LSFW_LOCK);

            // Set as "not running"
            _isRunning = false;
        }

        /// <summary>
        /// Evaluates the asynchronous.
        /// </summary>
        /// <returns></returns>
        private async Task EvaluateAsync()
        {
            await Task.Run(() => this.Evaluate());
        }

        #endregion

        #region P/Invoke 

        // Lock
        public static uint LSFW_LOCK = 1;
        public static uint LSFW_UNLOCK = 2;

        /// <summary>
        /// Locks the set foreground window.
        /// </summary>
        /// <param name="uLockCode">The u lock code.</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        public static extern bool LockSetForegroundWindow(uint uLockCode);

        /// <summary>
        /// Gets the foreground window.
        /// </summary>
        /// <returns></returns>
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        #endregion
    }
}

 
The last piece is the implementation of the FocusController from App.xaml:


using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using Hardcodet.Wpf.TaskbarNotification;

using Microsoft.Win32;
using Application = System.Windows.Application;

namespace Xcalibur.DontInterruptMe
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        #region Members

        private static Mutex _instanceMutex;
        private FocusController _focusController;

        #endregion

        #region Methods

        /// <summary>
        /// Checks if application is already running.
        /// </summary>
        /// <returns></returns>
        private static bool StartInstance()
        {
            // Set mutex
            _instanceMutex = new Mutex(true, Constants.ApplicationKey);

            // Check if already running
            bool isAlreadyInUse = false;
            try
            {
                isAlreadyInUse = !_instanceMutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                KillInstance();
                isAlreadyInUse = false;
            }
            catch (Exception)
            {
                _instanceMutex.Close();
                isAlreadyInUse = false;
            }
            return isAlreadyInUse;
        }

        /// <summary>
        /// Kills the instance.
        /// </summary>
        /// <param name="code">The code.</param>
        private static void KillInstance(int code = 0)
        {
            if (_instanceMutex == null) return;

            // Owning application should release mutex
            if (code == 0)
            {
                try
                {
                    _instanceMutex.ReleaseMutex();
                }
                catch (Exception) { }
            }
            _instanceMutex.Close();
        }

        #endregion

        #region Events

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Startup" /> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnStartup(StartupEventArgs e)
        {
            // Check if running
            if (StartInstance())
            {
                // Already running, Exit
                Current.Shutdown(1);
            }

            // Invoke focus controller
            this._focusController = new FocusController();

            // Base
            base.OnStartup(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Application.Exit" /> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the 
        /// event data.</param>
        protected override void OnExit(ExitEventArgs e)
        {
            if (this._focusController != null)
            {
                // Gracefully exit
                this._focusController.Stop();
            }

            // Kill instance
            KillInstance(e.ApplicationExitCode);

            // Base
            base.OnExit(e);
        }

        #endregion
    }
}

 
You will notice I added the RunOnce Mutex Solution from our last post Restricting WPF Applications to run only once with a Mutex to avoid any potential issues with multiple instances.

 
Future Changes

In the next version of this application I will be tossing Process.GetProcesses() from the Evaluate method for a much faster P/Invoke solution I am planning to use in the next version of Astronomy.

If you want the complete, free product, download Don’t Interrupt Me! now.

Otherwise my friends, Happy Coding!

If you arrived here chances are you have a WPF application you want to lock down to running only once. Maybe you tried doing it by looking at Process.GetProcesses() but found it to be slow and unreliable and wanted something a bit more straight forward? Well, mutexes would be the most direct way to go. There are a few articles on the web about how to do this in .Net and they go into great detail about how mutexes work and how developers misuse them all the time. I am going to touch on the pieces you need for this solution and not waste any more of your time.

Q: What is a mutex?
A: If you took Computer Science courses this should be a no-brainer, but let’s assume you didn’t.

Mutex is short for a mutual exclusion object. This exists as a uniquely named resource that can be shared across multiple threads. Each thread would need to lock the resource to use it, so it cannot be accessed simultaneously.

Q: How does this help me?
A: Multiple applications can access the mutex allowing you to create a unique token to be reserved by a specific application instance.

Now we get to the how part.
First, let create a mutex placeholder and a unique key for our mutex:


private static Mutex _instanceMutex;
private static string MyApplicationKey = "{0036BC97-7DE3-4934-9928-43CE53CBF0AA}";

Next let’s create some key methods to set, evaluate, and terminate our mutex:


/// <summary>
/// Checks if application is already running.
/// </summary>
/// <returns></returns>
public static bool StartInstance()
{
    // Set mutex
    _instanceMutex = new Mutex(true, Constants.MyApplicationKey);

    // Check if already running
    bool isAlreadyInUse = false;
    try 
    {
        isAlreadyInUse = !_instanceMutex.WaitOne(TimeSpan.Zero, true);
    }
    catch (AbandonedMutexException)
    {
        KillInstance();
        isAlreadyInUse = false;
    }
    catch (Exception)
    {
        _instanceMutex.Close();
        isAlreadyInUse = false;
    }
    return isAlreadyInUse;
}

/// <summary>
/// Kills the instance.
/// </summary>
/// <param name="code">The code.</param>
public static void KillInstance(int code = 0)
{
    if (_instanceMutex == null) return;

    // Owning application should release mutex
    if (code == 0)
    {
        try
        {
            _instanceMutex.ReleaseMutex();
        }
        catch (Exception) { }
    }
    _instanceMutex.Close();
}

StartInstance does a few key things:

  • Claim ownership of a new mutex with our application key.
  • Evaluate the mutex by invoking a WaitHandle.
  • An AbandonedMutexException exception may occur which means the mutex exists but was not properly released by the owning process. This is likely caused by the owning process exiting unexpectedly. We kill the mutex in this case (covered later) and set this as not in use.
  • If a general exception occurs we simply close the instance and set it to not in use.
  • Otherwise, it is already in use.

 
Hold on a second!

Q: What is the difference between Releasing a mutex and Closing a mutex?
A: This is an important question.

Releasing a mutex (Mutex.ReleaseMutex()) releases a mutex from memory. This means no application can access it and it will need to be created again. Only the owning application can release the mutex unless that application is no longer in memory.

Closing a mutex (Mutex.Close()) in .Net really means closing the WaitHandle associated with the mutex. This should always be done after accessing a mutex.

 
KillInstance works as follows:

  • If a standard exit code of 0 is provided it assumes itself the owner of the mutex and attempts to release it.
  • The mutex is then closed.

 
And that’s all you need. So, let’s put it in an example.

Let’s add it to our App.xaml.cs OnStartup and OnExit as follows:


/// <summary>
/// Raises the <see cref="E:System.Windows.Application.Startup" /> event.
/// </summary>
/// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the 
/// event data.</param>
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // Check if running
    if (!StartInstance()) return;
    
	// Apparently we are already running our app
    MessageBox.Show("Already Running!");

    // If running, peace out
    Application.Current.Shutdown(1);
}

/// <summary>
/// Raises the <see cref="E:System.Windows.Application.Exit"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.Windows.ExitEventArgs"/> that contains the event 
/// data.</param>
protected override void OnExit(ExitEventArgs e)
{
    base.OnExit(e);

    // Kill instance
    KillInstance(e.ApplicationExitCode);
}

  1. OnStartup evaluates whether the mutex is already in use. If it is, it tells the user it is already running and exits the application.
  2. OnExit passes the exit code to KillInstance and handles closing the mutex.

 
And that’s it! I recommend you read up more on mutexes to make absolutely sure you are comfortable with this approach. But for now, this should get you what you need to get back to work.

Happy coding!

I think the title says it pretty well: we want the DatePicker control to not be stupid and allow tabbing to the calendar button so we can meet mouse-less compliance standards.

Q: Why didn’t Microsoft do this to begin with?
A: *shrugs* But, we’re going to fix it.

The code:


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

/// <summary>
/// Extended DatePicker.
/// </summary>
public class ExtendedDatePicker : DatePicker
{
    #region Members

    private bool _tabInvoked;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="ExtendedDatePicker"/> class.
    /// </summary>
    public ExtendedDatePicker()
    {
        _tabInvoked = false;
        this.PreviewKeyDown += DatePickerPreviewKeyDown;
        this.CalendarClosed += DatePickerCalendarClosed;

        // Allow to be a tab target
        IsTabStop = true;
    }

    #endregion

    #region Events

    /// <summary>
    /// Dates the picker preview key down.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.
    /// </param>
    private void DatePickerPreviewKeyDown(object sender, KeyEventArgs e)
    {
        var datePicker = sender as DatePicker;
        if (datePicker == null || e.Key != Key.Tab) return;

        // Mark that "tab" was pressed
        _tabInvoked = true;

        // Reverse drop down opened state
        datePicker.IsDropDownOpen = !datePicker.IsDropDownOpen;
    }

    /// <summary>
    /// Dates the picker calendar closed.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.
    /// </param>
    private void DatePickerCalendarClosed(object sender, RoutedEventArgs e)
    {
        if (!_tabInvoked) return;

        // Reset marker
        _tabInvoked = false;

        // Go to next control in sequence
        var element = Keyboard.FocusedElement as UIElement;
        if (element == null) return;
        element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
    }

    #endregion
}

Here is what it does in a nutshell:

  • Makes this control a tab stop (it’s not by default)
  • Binds to the PreviewKeyDown event, which catches when Tab is pressed. It then sets a marker, telling the control that the calendar was opened via keyboard and sets IsDropDownOpen to true.
  • Binds to the CalendarClosed event, which (if Tab was invoked) resets the marker and sets the focus to the next control in the view.

It’s not overly complicated, but it solves an annoyance.

Until next time…

Welcome to Part 3 of our series Making a Better ObservableCollection.

If you missed Making a Better ObservableCollection Part 2 – Cross Threading you can get to it here.

We started by showing you a few neat extensions in Part 1 and then followed it up with cross-threading in Part 2. Now that we have an understanding of how to extend and offload the work to another thread, let’s cover another very important feature: Sorting.

We start by creating a custom comparer as learned here:


/// <summary>
/// Customer Sort Comparer.
/// Original Source:
/// http://blogs.msdn.com/b/jgoldb/archive/2008/08/28/improving-microsoft-datagrid-ctp-sorting-performance-part-2.aspx
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CustomSortComparer<T> : ICustomSortComparer<T>
{
    #region Members

    /// <summary>
    /// A two argument delegate for comparing two objects.
    /// </summary>
    /// <param name="arg1">The arg1.</param>
    /// <param name="arg2">The arg2.</param>
    /// <returns></returns>
    protected delegate int TwoArgDelegate(T arg1, T arg2);

    /// <summary>
    /// A two argument delegate instance.
    /// </summary>
    private TwoArgDelegate _myCompare;

    #endregion

    #region Methods

    /// <summary>
    /// Sorts the specified target collection.
    /// </summary>
    /// <param name="targetCollection">The target collection.</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="direction">The direction.</param>
    public void Sort(ObservableCollection<T> targetCollection, string propertyName, 
        ListSortDirection direction)
    {
        // Sort comparer
        var sortComparer = new InternalSorting(propertyName, direction);

        // Sort
        var sortedCollection = targetCollection.OrderBy(x => x, sortComparer).ToList();

        // Synch
        targetCollection.SynchCollection(sortedCollection, true);
    }

    /// <summary>
    /// Performs custom sorting operation.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    /// <param name="direction">The direction.</param>
    internal void CustomSort(string propertyName, ListSortDirection direction)
    {
        int dir = (direction == ListSortDirection.Ascending) ? 1 : -1;

        // Set a delegate to be called by IComparer.Compare
        _myCompare = (a, b) => ReflectionCompareTo(a, b, propertyName) * dir;
    }

    /// <summary>
    /// Custom compareTo function to compare 2 objects derived using Reflection.
    /// If an aliasProperty is provided, the sort is performed on that property
    /// instead.
    /// This is ideal for columns with data types that need to be sorted by another
    /// data type.
    /// i.e. Images that need value associations, or strings with numeric entries.
    /// </summary>
    /// <param name="a">A.</param>
    /// <param name="b">The b.</param>
    /// <param name="propertyName">Name of the property.</param>
    /// <returns></returns>
    private static int ReflectionCompareTo(object a, object b, String propertyName)
    {
        // Get property value for "a"
        PropertyInfo aPropInfo = a.GetType().GetProperty(propertyName);
        var aValue = aPropInfo.GetValue(a, null);
        if (aValue == null) return 0;

        // Get property value for "b"
        PropertyInfo bPropInfo = b.GetType().GetProperty(propertyName);
        var bValue = bPropInfo.GetValue(b, null);
        if (bValue == null) return 0;

        // CompareTo method
        MethodInfo compareToMethod =
            aPropInfo.PropertyType.GetMethod("CompareTo", new[] { aPropInfo.PropertyType });
        if (compareToMethod == null) return 0;

        // Get result
        var compareResult = compareToMethod.Invoke(aValue, new[] { bValue });
        return Convert.ToInt32(compareResult);
    }

    #endregion

    #region ICompare

    /// <summary>
    /// Compares two objects and returns a value indicating whether one is less 
    /// than, equal to, or greater than the other.
    /// </summary>
    /// <param name="x">The first object to compare.</param>
    /// <param name="y">The second object to compare.</param>
    /// <returns>
    /// Value
    /// Condition
    /// Less than zero
    /// <paramref name="x" /> is less than <paramref name="y" />.
    /// Zero
    /// <paramref name="x" /> equals <paramref name="y" />.
    /// Greater than zero
    /// <paramref name="x" /> is greater than <paramref name="y" />.
    /// </returns>
    public int Compare(T x, T y)
    {
        return _myCompare(x, y);
    }

    #endregion

    #region InternalSorting

    /// <summary>
    /// Custom IComparer class to perform custom sorting.
    /// </summary>
    private class InternalSorting : CustomSortComparer<T>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InternalSorting"/> class.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="direction">The direction.</param>
        public InternalSorting(string propertyName, ListSortDirection direction)
        {
            CustomSort(propertyName, direction);
        }
    }

    #endregion
}

Q: So, what is going on with this comparer?
A: Here is a summary:

  • Sort is called against an ObservableCollection, a property name, and a sort direction.
  • A sort comparer is derived by calling an internal class which invokes the CustomSort method.
  • The CustomSort method assesses the sort direction as an integer and then uses reflection to compare each set of values (ReflectionCompareTo).
  • A sorted collection is created against the target collection with the sort comparer applied.
  • The target collection is synched against the sorted collection.

Next, we augment our ObservableCollectionEx class:


/// <summary>
/// Extends the ObservableCollection object:
/// 1. Allows cross-thread updating to offload UI operations.
/// 2. Allows full sorting capabilities without affecting the UI thread.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableCollectionEx<T> : ObservableCollection<T>, IObservableCollectionEx<T>
{
    #region Members

    private readonly ICustomSortComparer<T> _sortComparer;

    #endregion

    #region Constructors

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

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="ObservableCollectionEx{T}" /> class.
    /// </summary>
    /// <param name="collection">The collection.</param>
    public ObservableCollectionEx(IEnumerable<T> collection) : this()
    {
        this.AddRange(collection);
    }

    #endregion

    #region Methods

    /// <summary>
    /// Sorts the observable collection by the property and sort direction.
    /// </summary>
    /// <param name="propertyName">The property within the ObservableCollectionExtender object
    /// to sort by.</param>
    /// <param name="direction">The desired sort direction.</param>
    public void Sort(string propertyName, ListSortDirection direction)
    {
        if (!this.Any()) return;
        _sortComparer.Sort(this, propertyName, direction);
    }

    /// <summary>
    /// Sorts the specified expression.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="expression">The expression.</param>
    /// <param name="direction">The direction.</param>
    public void Sort<TProperty>(Expression<Func<T, TProperty>> expression, 
    ListSortDirection direction)
    {
        if (!this.Any()) return;
        Sort(expression.GetPropertyName(), direction);
    }

    #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

What’s new is our 2 Sort methods. One uses a property name and the other an expression to strongly define the property. Both accomplish calling the sort comparer we just built earlier.

Q: So, why do all this? What was the point?
A: Custom sort comparers will give us the ability to increase the sorting performance of our ObservableCollections when bound to controls. It will also allow us to perform this work in an async thread instead of having to create a CollectionViewSource on the UI thread which will negatively impact the user experience during updates.

Next time, we will look at applying this to a DataGrid to greatly improve sort performance and reliability.

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…