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);}}