· 4 min read
How to implement the File Context Menu
The context menu that appears when you right-click a file in Windows Explorer—how can you implement this in a .NET application? This article explains the process

Introduction
The context menu that appears when you right-click a file in Windows Explorer—how can you implement this in a .NET application? This article explains the process, focusing on the following key points:
- COM operations in a NativeAOT build
- Handling structures with LibraryImport
- Implementation in an Avalonia application
If you’ve ever wondered, “How can I open a file’s context menu in a .NET app?” this article provides a direct answer.
My application, Filedini, which I’m currently developing, includes a feature to open file context menus. The code for this is publicly available, and this article explains its implementation.
https://github.com/YoshihiroIto/Filedini-public/tree/main/Source/_70_ServiceImplements/Windows
Why Use NativeAOT Builds?
.NET applications (such as WPF apps) often suffer from long startup times. Even with R2R (ReadyToRun) builds, performance may still feel sluggish.
Developing an application with Avalonia that supports NativeAOT can significantly reduce startup time, leading to a much smoother user experience.
Once you experience this speed, you may find it hard to go back to traditional methods. In some cases, the benefits of NativeAOT are compelling enough to justify trade-offs.
COM Operations in a NativeAOT Environment
For handling COM in a NativeAOT environment, this article by SHINTA provides valuable insights: https://zenn.dev/shinta0806/articles/native-aot-com
Understanding these foundational techniques is crucial.
Context Menu Implementation Example
The process of displaying a context menu follows these steps. For details, see the ShellContextMenu.Show method:
- Retrieve the desktop folder
- Get the parent folder of the target file
- Create an IDL for the target file
- Obtain the context menu interface
- Display the menu
- Execute commands based on user selection
Handling Structures Containing Strings with LibraryImport
Here is a specific implementation example:
private static unsafe void InvokeCommand(IContextMenu contextMenu, uint cmd, string folderName, int x, int y)
{
fixed (char* p = folderName)
{
var command = new CMINVOKECOMMANDINFOEX
{
cbSize = sizeof(CMINVOKECOMMANDINFOEX),
lpVerb = (IntPtr)(cmd - CMD_FIRST),
lpDirectory = p,
lpVerbW = (IntPtr)(cmd - CMD_FIRST),
lpDirectoryW = p,
fMask = CMIC.UNICODE | CMIC.PTINVOKE |
(IsKeyPressControlKey ? CMIC.CONTROL_DOWN : 0) |
(IsKeyPressShiftKey ? CMIC.SHIFT_DOWN : 0),
ptInvoke = new POINT(x, y),
nShow = SW.SHOWNORMAL
};
contextMenu.InvokeCommand(ref command);
}
}Here, fixed is used to obtain a pointer to the string, which is then passed to the structure.
Obtaining a Window Handle in Avalonia
Avalonia is a cross-platform framework, but to use Windows-specific features, you need a native window handle.
public static void Show(TopLevel topLevel, FileInfo[] files, int x, int y)
{
// ...
var handleOwner = topLevel.TryGetPlatformHandle()?.Handle;
if (handleOwner is null)
return;
// ...
}By calling TryGetPlatformHandle() on Avalonia’s TopLevel class, you can retrieve the platform-specific handle. On Windows, this corresponds to an HWND.
Hooking the Window Procedure
To properly display the context menu, you need to handle specific window messages.
file class Hook : IDisposable
{
private readonly TopLevel _topLevel;
private readonly IContextMenu2? _contextMenu2;
private readonly IContextMenu3? _contextMenu3;
public Hook(TopLevel topLevel, IContextMenu2? contextMenu2, IContextMenu3? contextMenu3)
{
_topLevel = topLevel;
_contextMenu2 = contextMenu2;
_contextMenu3 = contextMenu3;
Win32Properties.AddWndProcHookCallback(_topLevel, WndProc);
}
public void Dispose()
{
Win32Properties.RemoveWndProcHookCallback(_topLevel, WndProc);
}
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (_contextMenu2 != null && (msg == (uint)WM.INITMENUPOPUP || msg == (uint)WM.MEASUREITEM || msg == (uint)WM.DRAWITEM))
{
if (_contextMenu2.HandleMenuMsg(msg, wParam, lParam) == S_OK)
{
handled = true;
return IntPtr.Zero;
}
}
if (_contextMenu3 != null && msg == (uint)WM.MENUCHAR)
{
if (_contextMenu3.HandleMenuMsg2(msg, wParam, lParam, IntPtr.Zero) == S_OK)
{
handled = true;
return IntPtr.Zero;
}
}
return IntPtr.Zero;
}
}WM.INITMENUPOPUP- When initializing a popup menuWM.MEASUREITEM- When measuring menu item sizeWM.DRAWITEM- When drawing menu itemsWM.MENUCHAR- When handling keyboard shortcuts in the menu
Avalonia’s Win32Properties Class
Rather than opening a new window, it’s sufficient to process messages within an existing window. Since the window handle is already available, you can use Win32 API directly, but Avalonia provides the Win32Properties class for hooking into the Windows procedure.
// Add hook
Win32Properties.AddWndProcHookCallback(_topLevel, WndProc);
// Remove hook
Win32Properties.RemoveWndProcHookCallback(_topLevel, WndProc);Conclusion
Through this implementation of the Windows Shell API for context menus, we covered the following technical points:
- COM operations in a NativeAOT build
- Native interop using
LibraryImport - Obtaining a Windows handle in an Avalonia app
- Setting up and managing window procedure hooks
By leveraging these techniques, you can incorporate Windows-native features into cross-platform Avalonia applications.
While Avalonia is a multi-platform UI framework, certain use cases require working closely with the OS shell. This implementation serves as an excellent example of using cross-platform libraries while integrating Windows-specific functionality.
Take this opportunity to explore Avalonia for your development projects!
References
- https://www.codeproject.com/Articles/15059/Csharp-File-Browser
- The original inspiration for this article
- https://zenn.dev/shinta0806/articles/native-aot-com
- Handling COM in a NativeAOT environment
- https://github.com/YoshihiroIto/Filedini-public/tree/main/Source/_70_ServiceImplements/Windows
- Implementation example
