Viele von euch kennen das wahrscheinlich: Bei einem Rechtsklick auf das Fenster der Anwendung (bzw. die Titelleiste) werden zusätzliche Menüpunkte eingeblendet. Aber wie wird das realisiert? In diesem Artikel sehen wir uns das etwas genauer an.

Normalerweise sieht das Kontextmenü so aus, wie auf dem Bild dargestellt:

Standardmäßiges Kontextmenü bei einem WPF-Fenster

Win32 einbinden

Wie können wir nun neue Einträge diesem Menü hinzufügen? Dazu müssen wir uns die User32.dll etwas genauer ansehen. Dort gibt es die Methoden GetSystemMenu und AppendMenu. Mit diesen Methoden können wir uns das Kontextmenü holen und um neue Einträge erweitern[1].

Ich erstelle hierzu gerne eine Wrapperklasse, welche die nativen Methodenaufrufe sammelt. In meinem Falle heisst diese „WUser32“. W für Wrapper und anschließend der Name der DLL.
Dort können wir die oben genannten Methoden einbinden (Snippets befinden sich am Ende des Artikels).

Im Hauptfenster überschreiben wir die Methode OnSourceInitialized. Dort holen wir uns den aktuellen WindowHandler. Der Namespace System.Windows.Interopt bietet zusätzliche Funktionen um beispielsweise zwischen WPF und Win32 APIs zu kombinieren[2].

Anschließend erweitern wir die Methode WndProc. Diese wird u.a. bei einem Klick auf den Menüpunkt ausgeführt. Wir überprüfen hierzu, ob der wparam auch mit unserem festgelegten Code übereinstimmt. Es ist möglich, hierdurch verschiedene Menüeinträge zu unterscheiden.

Wenn der wparam mit unserem festgelegten Wert übereinstimmt, zeigen wir das gewählte Fenster an. Exemplarisch wird dies durch eine Messagebox dargestellt.

protected override void OnSourceInitialized(EventArgs e) {
  HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
  source.AddHook(WndProc);
  IntPtr handlerSystemMenu = WUser32.GetSystemMenu(source.Handle, false);


  // Add custom menu items (separator and the new item)
  WUser32.AppendMenu(handlerSystemMenu, WUser32.MF_SEPARATOR, 0, string.Empty);
  WUser32.AppendMenu(handlerSystemMenu, WUser32.MF_STRING, WUser32.SYSMENU_NEW, "New menu item");

  base.OnSourceInitialized(e);
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) {
  if ((msg == WUser32.WM_SYSCOMMAND) && (wparam.ToInt32() == WUser32.SYSMENU_NEW)) {
    MessageBox.Show("MESSAGEBOX","New item clicked!");
  }

  return hwnd;
}
private const string USER32_DLL = "user32.dll";

#region Custom Codes

public const int SYSMENU_NEW = 0x100;

#endregion


#region MF-Commands

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647616.aspx
public const uint MF_STRING = 0x00000000;
public const uint MF_SEPARATOR = 0x00000800;

#endregion


#region WM-Commands

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360.aspx
public const int WM_SYSCOMMAND = 0x112;

#endregion


#region PInvoke-Methods

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647985.aspx
[DllImport(USER32_DLL, CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647616.aspx
[DllImport(USER32_DLL, CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool AppendMenu(IntPtr hMenu, uint uFlags, int uIDNewItem, string lpNewItem);

#endregion

Ergebnis

Und so sieht das Ergebnis am Ende aus:

Kontextmenü mit unserem neu erstellten Eintrag
MessageBox nach Klick auf den neuen Menüeintrag

Habt ihr schon mit der Win32 API gearbeitet und welche Funktionen habt ihr genutzt? Schreibt eure Erfahrungen in die Kommentare 🙂

[1]: Siehe GetSystemMenu function (MSDN)
[2]: Siehe https://msdn.microsoft.com/en-us/library/system.windows.interop.aspx