WPF 调用 Windows 桌面右键新增文件菜单的实现方案

news/2025/9/19 17:35:26/文章来源:https://www.cnblogs.com/dosswy/p/19101331

WPF 工具类NewMenuUtility的实现,核心是读取 Windows 系统注册表中 “新建文件” 相关配置,在 WPF 应用中生成并绑定系统风格的右键新建文件菜单,支持创建对应类型文件。

核心点

  • 注册表读取:从HKEY_CLASSES_ROOT下的*\ShellNew键、文件扩展名子键(如.txt),获取新建文件的类型、图标、默认内容等配置;
  • 菜单数据处理:整理文件类型描述、图标路径、默认文件名,补充 “文件夹” 和 Office 文件(.docx等)特殊项,去重生成NewMenuItem列表;
  • WPF 菜单绑定:将NewMenuItem转为 WPF 的MenuItem(加载图标),绑定到ContextMenu;
  • 文件创建功能:点击菜单项时,在桌面生成唯一命名的文件(含内容或空文件),处理文件重名,提示创建结果。

添加NewMenuItem.cs 右键菜单类

 /// <summary>/// 新建菜单项的类,包含文件的基本信息/// </summary>public class NewMenuItem : INotifyPropertyChanged{// INotifyPropertyChanged 实现public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}private string _header;/// <summary>/// 菜单名称/// </summary>public string Header{get { return _header; }set{_header = value;OnPropertyChanged(nameof(Header));}}private string _extension;/// <summary>/// 文件扩展名/// </summary>public string Extension{get { return _extension; }set{_extension = value;OnPropertyChanged(nameof(Extension));}}private string _iconPath;/// <summary>/// 图标路径/// </summary>public string IconPath{get { return _iconPath; }set{_iconPath = value;OnPropertyChanged(nameof(IconPath));}}private string _fileName;/// <summary>/// 默认文件名/// </summary>public string FileName{get { return _fileName; }set{_fileName = value;OnPropertyChanged(nameof(FileName));}}private byte[] _fileContents;/// <summary>/// 文件内容/// </summary>public byte[] FileContents{get { return _fileContents; }set{_fileContents = value;OnPropertyChanged(nameof(FileContents));}}}

添加 NewMenuUtility 菜单工具类

 /// <summary>/// 工具类/// </summary>public class NewMenuUtility{/// <summary>/// 获取所有新建菜单项/// </summary>/// <returns></returns>public static List<NewMenuItem> GetNewMenuItems(){List<NewMenuItem> items = new List<NewMenuItem>();// 检查 HKEY_CLASSES_ROOT\* 下的 ShellNew 键using (RegistryKey shellNewKey = Registry.ClassesRoot.OpenSubKey(@"*\ShellNew")){if (shellNewKey != null){foreach (string valueName in shellNewKey.GetValueNames()){if (valueName != "Classes"){AddNewMenuItem(items, valueName);}}}}// 检查 HKEY_CLASSES_ROOT 下所有以点开头的子键using (RegistryKey classesRoot = Registry.ClassesRoot){foreach (string keyName in classesRoot.GetSubKeyNames().Where(k => k.StartsWith("."))){using (RegistryKey extensionKey = classesRoot.OpenSubKey(keyName + @"\ShellNew")){if (extensionKey != null){AddNewMenuItem(items, keyName);}}}}// 添加特殊项目,如文件夹items.Add(new NewMenuItem{Header = "文件夹",Extension = "",IconPath = @"%SystemRoot%\System32\shell32.dll,3"});// 处理 Office 文件类型HandleOfficeFileTypes(items);return items.Distinct(new NewMenuItemComparer()).ToList();}/// <summary>/// 从 ShellNew 键添加新菜单项/// </summary>/// <param name="items"></param>/// <param name="subKeyPath"></param>private static void AddShellNewItems(List<NewMenuItem> items, string subKeyPath){// 打开指定的注册表子键using (RegistryKey shellNewKey = Registry.ClassesRoot.OpenSubKey(subKeyPath)){if (shellNewKey != null) // 若子键存在{// 遍历子键中的所有值foreach (string valueName in shellNewKey.GetValueNames()){// 排除 Classes 值if (valueName != "Classes"){// 添加新菜单项AddNewMenuItem(items, valueName);}}}}}/// <summary>/// 从扩展名添加新菜单项/// </summary>/// <param name="items"></param>private static void AddExtensionItems(List<NewMenuItem> items){// 打开 HKEY_CLASSES_ROOT 注册表根using (RegistryKey classesRoot = Registry.ClassesRoot){// 遍历所有以点开头的子键(即文件扩展名)foreach (string keyName in classesRoot.GetSubKeyNames().Where(k => k.StartsWith("."))){// 添加新菜单项AddNewMenuItem(items, keyName);}}}/// <summary>/// 添加新菜单项/// </summary>/// <param name="items"></param>/// <param name="extension"></param>private static void AddNewMenuItem(List<NewMenuItem> items, string extension){// 获取文件类型描述string fileTypeName = GetFileTypeDescription(extension);// 获取图标路径string iconPath = GetIconPath(extension);// 获取新建文件的内容byte[] fileContents = GetNewFileContents(extension);// 获取新建文件的默认名称string fileName = GetNewFileName(extension);// 创建新菜单项并添加到列表items.Add(new NewMenuItem{Header = fileTypeName, // 设置菜单项名称Extension = extension, // 设置文件扩展名IconPath = iconPath, // 设置图标路径FileName = fileName, // 设置默认文件名FileContents = fileContents // 设置文件内容});}/// <summary>/// 获取文件类型描述/// </summary>/// <param name="extension"></param>/// <returns></returns>private static string GetFileTypeDescription(string extension){// 打开与扩展名对应的注册表子键using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension)){if (extensionKey != null) // 若子键存在{// 获取默认值(文件类型名称)object defaultValue = extensionKey.GetValue("");if (defaultValue != null){// 打开文件类型对应的注册表子键using (RegistryKey typeKey = Registry.ClassesRoot.OpenSubKey(defaultValue.ToString())){// 若子键存在if (typeKey != null){// 获取文件类型的描述object description = typeKey.GetValue("");if (description != null){return description.ToString();}}}}}}// 若没有找到描述,返回默认格式return $"New {extension.TrimStart('.')} File";}/// <summary>/// 获取新建文件的内容/// </summary>/// <param name="extension"></param>/// <returns></returns>private static byte[] GetNewFileContents(string extension){// 打开与扩展名对应的 ShellNew 注册表子键using (RegistryKey shellNewKey = Registry.ClassesRoot.OpenSubKey($@"{extension}\ShellNew")){if (shellNewKey != null) // 若子键存在{// 若存在 Data 值,返回其内容if (shellNewKey.GetValue("Data") is byte[] data){return data;}// 若存在 FileName 值,读取文件内容else if (shellNewKey.GetValue("FileName") is string fileName){// 扩展环境变量string fullPath = Environment.ExpandEnvironmentVariables(fileName);// 若文件存在if (File.Exists(fullPath)){return File.ReadAllBytes(fullPath);}}}}return null; // 若没有内容,返回 null}/// <summary>/// 获取新建文件的默认名称/// </summary>/// <param name="extension"></param>/// <returns></returns>private static string GetNewFileName(string extension){// 返回默认文件名return $"New File{extension}";}// 处理 Office 文件类型private static void HandleOfficeFileTypes(List<NewMenuItem> items){// 定义 Office 文件类型及其名称Dictionary<string, string> officeApps = new Dictionary<string, string>{{".docx", "Microsoft Word 文档"},{".xlsx", "Microsoft Excel 工作表"},{".pptx", "Microsoft PowerPoint 演示文稿"}};// 遍历 Office 文件类型foreach (var app in officeApps){// 获取图标路径string iconPath = GetIconPath(app.Key);// 添加新菜单项items.Add(new NewMenuItem{Header = app.Value, // 设置菜单项名称Extension = app.Key, // 设置文件扩展名IconPath = iconPath, // 设置图标路径FileName = app.Value,});}}/// <summary>/// 获取图标路径/// </summary>/// <param name="extension"></param>/// <returns></returns>private static string GetIconPath(string extension){// 打开与扩展名对应的注册表子键using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension)){if (extensionKey != null) // 若子键存在{// 获取文件类型string fileType = (string)extensionKey.GetValue("");// 若文件类型存在if (!string.IsNullOrEmpty(fileType)){// 打开文件类型的 DefaultIcon 子键using (RegistryKey typeKey = Registry.ClassesRoot.OpenSubKey(fileType + @"\DefaultIcon")){// 若子键存在if (typeKey != null){// 返回图标路径return (string)typeKey.GetValue("");}}}}}return null; // 若没有找到图标路径,返回 null}/// <summary>/// 比较器用于去重/// </summary>private class NewMenuItemComparer : IEqualityComparer<NewMenuItem>{// 判断两个菜单项是否相等public bool Equals(NewMenuItem x, NewMenuItem y){// 根据扩展名判断return x.Extension == y.Extension;}// 获取菜单项的哈希码public int GetHashCode(NewMenuItem obj){// 返回扩展名的哈希码return obj.Extension.GetHashCode();}}/// <summary>/// 绑定新菜单项到上下文菜单/// </summary>/// <param name="contextMenu"></param>public static void BindNewMenuItems(ContextMenu contextMenu){// 获取新菜单项列表List<NewMenuItem> newMenuItems = GetNewMenuItems();// 遍历新菜单项foreach (var item in newMenuItems){MenuItem menuItem = new MenuItem{Header = item.Header // 设置菜单项标题};// 若图标路径不为空if (!string.IsNullOrEmpty(item.IconPath)){try{// 分割图标路径和索引string[] iconInfo = item.IconPath.Split(',');// 扩展环境变量string iconPath = Environment.ExpandEnvironmentVariables(iconInfo[0]);// 获取图标索引int iconIndex = iconInfo.Length > 1 ? int.Parse(iconInfo[1]) : 0;// 加载图标为 BitmapSourceBitmapSource iconSource = LoadIconAsBitmapSource(iconPath, iconIndex);// 若图标加载成功if (iconSource != null){// 设置菜单项图标menuItem.Icon = new System.Windows.Controls.Image { Source = iconSource };}}catch{// 若加载图标失败,忽略错误}}// 为菜单项添加点击事件menuItem.Click += (sender, e) => CreateNewFile(item);// 将菜单项添加到上下文菜单contextMenu.Items.Add(menuItem);}}/// <summary>/// 加载图标为 BitmapSource/// </summary>/// <param name="filePath"></param>/// <param name="iconIndex"></param>/// <returns></returns>private static BitmapSource LoadIconAsBitmapSource(string filePath, int iconIndex){// 初始化图标句柄IntPtr hIcon = IntPtr.Zero;try{// 从文件中提取图标hIcon = NativeMethods.ExtractIcon(IntPtr.Zero, filePath, iconIndex);// 若图标提取成功if (hIcon != IntPtr.Zero){// 创建 BitmapSourcereturn Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());}}finally{// 若图标句柄有效if (hIcon != IntPtr.Zero){// 销毁图标句柄NativeMethods.DestroyIcon(hIcon);}}return null; // 若没有图标,返回 null}// 本地方法调用private static class NativeMethods{// 从指定文件提取图标[System.Runtime.InteropServices.DllImport("shell32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]public static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);// 销毁图标句柄[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]public static extern bool DestroyIcon(IntPtr handle);}/// <summary>/// 创建新文件/// </summary>/// <param name="item"></param>private static void CreateNewFile(NewMenuItem item){try{// 获取桌面路径string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);// 组合文件路径string filePath = Path.Combine(desktopPath, item.FileName);// 确保文件名是唯一的int counter = 1;// 若文件已存在while (File.Exists(filePath)){// 生成新的文件名string fileName = $"{Path.GetFileNameWithoutExtension(item.FileName)} ({counter}){item.Extension}";// 更新文件路径filePath = Path.Combine(desktopPath, fileName);// 计数器递增counter++;}// 创建新文件// 若文件内容不为空if (item.FileContents != null){// 写入文件内容File.WriteAllBytes(filePath, item.FileContents);}else{// 创建空文件并立即释放资源File.Create($"{filePath}{item.Extension}").Dispose();}// 显示成功消息MessageBox.Show($"文件创建成功:{filePath}", "成功", MessageBoxButton.OK, MessageBoxImage.Information);}catch (Exception ex){// 显示错误消息MessageBox.Show($"创建文件时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);}}}

MainWindow.xaml 文件中添加右键菜单样式

<Windowx:Class="SystemNewFileMenuDemo.Views.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:prism="http://prismlibrary.com/"Title="{Binding Title}"Width="525"Height="350"prism:ViewModelLocator.AutoWireViewModel="True"><Window.Resources><!--  菜单项  --><Style TargetType="{x:Type MenuItem}"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type MenuItem}"><Borderx:Name="Border"Background="Transparent"BorderBrush="Transparent"BorderThickness="1"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" SharedSizeGroup="Icon" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><!--  图标  --><ContentPresenterx:Name="Icon"Width="16"MaxHeight="16"Margin="6,0,6,0"VerticalAlignment="Center"ContentSource="Icon" /><!--  文本  --><ContentPresenterGrid.Column="1"MinWidth="80"Margin="0,6,6,6"VerticalAlignment="Center"ContentSource="Header" /></Grid></Border><ControlTemplate.Triggers><!--  鼠标悬停效果  --><Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Border" Property="Background" Value="#F2F2F2" /><Setter TargetName="Border" Property="BorderBrush" Value="#F2F2F2" /></Trigger><!--  选中效果  --><Trigger Property="IsPressed" Value="True"><Setter TargetName="Border" Property="Background" Value="#D0D0D0" /><Setter TargetName="Border" Property="BorderBrush" Value="#CCCEDB" /></Trigger><!--  禁用效果  --><Trigger Property="IsEnabled" Value="False"><Setter Property="Foreground" Value="#888888" /></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style><!--  右键菜单样式  --><SolidColorBrush x:Key="WindowBackgroundBrush" Color="#E7E8EC" /><SolidColorBrush x:Key="SolidBorderBrush" Color="#CCCEDB" /><Style TargetType="{x:Type ContextMenu}"><Setter Property="SnapsToDevicePixels" Value="True" /><Setter Property="OverridesDefaultStyle" Value="True" /><Setter Property="FontFamily" Value="Segoe UI" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ContextMenu}"><BorderName="Border"Margin="9"Background="{StaticResource WindowBackgroundBrush}"BorderBrush="{StaticResource SolidBorderBrush}"BorderThickness="0"><StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" /><Border.Effect><DropShadowEffectBlurRadius="8"ShadowDepth="1"Color="#E8E8E8" /></Border.Effect></Border></ControlTemplate></Setter.Value></Setter></Style></Window.Resources><!--  添加右键菜单交互  --><GridWidth="500"Height="300"HorizontalAlignment="Center"VerticalAlignment="Center"Background="#F2F2F2"><TextBlockHorizontalAlignment="Center"VerticalAlignment="Center"Text="鼠标点击右键调用菜单" /><Grid.ContextMenu><ContextMenu x:Name="myContextMenu" /></Grid.ContextMenu></Grid>
</Window>

MainWindow.xaml.cs 文件中绑定右键菜单

 /// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();// 绑定右键菜单NewMenuUtility.BindNewMenuItems(myContextMenu);}}

效果图

微信图片_2025-09-19_173001_938

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/907963.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HR 需了解的绩效评估应包含的内容

什么是员工绩效评估? 员工绩效审查,也被称为绩效评估或绩效考核,是对员工在特定时间段内的工作的正式评估。在员工绩效评估中,管理者会评估该人的整体表现,找出他们的优势和劣势,提供反馈,并帮助他们设定目标。…

解题报告-P12022 [USACO25OPEN] Hoof Paper Scissors Minus One B

P12022 [USACO25OPEN] Hoof Paper Scissors Minus One B 题目描述 在一局蹄子剪刀布游戏中,Bessie 和 Elsie 可以出 \(N\) (\(1 \leq N \leq 3000\))种不同的蹄子手势,编号为 \(1\dots N\),每个手势对应一种不同的…

实用指南:前端Form表单提交后跳转到指定页面

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

CentOS架构修改网卡命名的方法总结

CentOS架构修改网卡命名的方法总结pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

主流的开源协议(MIT,Apache,GPL v2/v3) - 实践

主流的开源协议(MIT,Apache,GPL v2/v3) - 实践2025-09-19 17:29 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displ…

np.clip的使用

np.clip() 是 NumPy 中一个非常实用的函数,用于将数组中的值裁剪(或者说限制)在一个指定的区间内。 它的作用就像一把剪刀,把数组中所有超出规定范围的数值都剪掉,让它们回到这个范围的边缘。 np.clip() 的基本用…

重看P4211 [LNOI2014] LCA 以及 P5305 [GXOI/GZOI2019] 旧词 题解

P4211 [LNOI2014] LCA P5305 [GXOI/GZOI2019] 旧词 重看 P4211 求 \(\sum_{i=l}^{r} dep[LCA(i,x)]\),首先把 LCA 都求出来行不通,我们考虑转化计算贡献的形式,一个点的深度就是根到它的路径上的点的个数,两个点的…

25.9.19随笔联考总结

考试 通读题面,决定顺序开题。很快做出 T1,感觉 T2T3 都可做,优势在我!各想一个小时未果,T4 也不会,坠机。赛后得知后三道都是 NOI 难度的题。 估计:100+0+0+0。实际:100+0+0+0。 总结 面对一些神秘题的时候还…

解题报告-P12025 [USACO25OPEN] Sequence Construction S

P12025 [USACO25OPEN] Sequence Construction S 题目描述 最近,农夫约翰农场里的奶牛们迷上了观看《炼乳神探》这档节目。节目讲述了一头聪明的奶牛侦探CowCow解决各类案件的故事。贝茜从节目中发现了新的谜题,但答案…

解题报告-P12026 [USACO25OPEN] Compatible Pairs S

P12026 [USACO25OPEN] Compatible Pairs S 题目描述 在遥远的乡村,农夫约翰的奶牛并非普通的农场动物——它们隶属于一个秘密的奶牛情报网络。每头奶牛都有一个由精英密码学家精心分配的ID号码。但由于农夫约翰随意的…

深入解析:Xilinx Video Mixer

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

maxu

https://v1.paoliangcloud.com/publicwowo.html?to=85ed9abcfe82``

makefile 入门1

makefile 入门1目标 目标的语法非常简单targets: prerequisitesrecipes... 或者 targets: prerequisites ; recipesrecipes...当执行目标时,make会检测prerequisites中的所有文件是否存在,如果都存在则执行targets后…

详细介绍:【 C/C++ 算法】入门动态规划-----一维动态规划基础(以练代学式)

详细介绍:【 C/C++ 算法】入门动态规划-----一维动态规划基础(以练代学式)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-fam…

iOS 26 能耗检测实战指南 如何监测 iPhone 电池掉电、Adaptive Power 模式效果与后台耗能问题(uni-app 与原生 App 优化必看)

本文结合 iOS 26 新增的 Adaptive Power 模式与电池界面功能,介绍如何检测 iPhone 能耗问题(包括启动耗电、后台耗电、动画特效与资源加载耗电等),并结合 Instruments、克魔等工具给出 uni-app 项目的实战优化指南…

Transformer的个人理解

Transformer工作原理 分词 文本进来之后,首先经过Tokenizer(分词器)分割成很多个token。每个token都会赋予一个从0开始的ID,用于后续索引。 然后通过一个embedding层,将token转换成一个多维向量,也叫做embedding…

国标GB28181平台EasyGBS如何实现企业园区视频监控一体化管理?

国标GB28181平台EasyGBS如何实现企业园区视频监控一体化管理?企业园区安防系统面临多品牌设备兼容难、系统孤立、管理效率低等痛点。本文详细探讨基于国标GB28181协议的EasyGBS视频平台如何为企业园区提供完整的视频监…

360环视硬件平台为什么推荐使用米尔RK3576开发板?

在汽车智能化和智慧安防快速发展的今天,360环视系统 已成为保障行车与场景安全、提升体验的重要技术。无论是自动泊车、驾驶辅助,还是智慧社区监控,核心诉求都是能够接入 多路摄像头,并通过高效的 推流 实现低延迟…

C语言弱函数

C语言弱函数project/ ├── core/ │ └── system_init.c // 核心框架代码,包含弱函数 ├── boards/ │ └── my_board.c // 板级特定代码,提供强函数 └── main.c // 主程序 /…

高质量票据识别数据集:1000张收据图像+2141个商品标注,支持OCR模型训练与文档理解研究

​获取更多高质量数据集,请访问典枢数据交易平台:https://dianshudata.com/引言与背景 在数字化转型的浪潮中,票据识别技术已成为金融、零售、餐饮等行业自动化处理的核心技术。传统的票据处理依赖人工录入,效率低…