高效管理无线网卡:基于.NET的Windows WPF工具开发实战
1. 为什么我们需要一个无线网卡管理工具作为一个经常需要切换网络环境的开发者我发现自己每天要反复启用/禁用无线网卡不下十次。比如在会议室连接投影仪时需要临时关闭Wi-Fi或者在家办公时需要快速切换到有线网络以获得更稳定的连接。Windows自带的网络管理界面操作路径太深需要点击6次才能完成切换而命令行操作又不够直观。这就是我决定开发这个工具的最初动机。你可能不知道Windows系统底层其实提供了丰富的网络管理API但普通用户很难直接利用这些功能。通过.NET和WPF技术栈我们可以将这些底层能力封装成简单易用的图形界面。实测下来使用这个工具切换无线网卡状态只需要1次点击比系统原生方式快了至少5倍。2. 开发环境准备与项目搭建2.1 基础开发环境配置首先需要安装Visual Studio 2022社区版即可选择.NET桌面开发工作负载。我推荐使用.NET 8这个长期支持版本它在Windows原生互操作方面做了很多优化。创建一个新的WPF应用项目时记得勾选启用Docker支持选项虽然我们暂时用不到但为后续扩展留有余地。项目结构建议采用标准的MVVM模式WifiManager/ ├── Models/ # 数据模型 ├── ViewModels/ # 业务逻辑 ├── Views/ # 界面组件 ├── Services/ # 底层服务 └── Assets/ # 静态资源2.2 关键NuGet包引用除了默认引用的WPF基础包我们还需要添加几个关键依赖PackageReference IncludeMicrosoft.Toolkit.Mvvm Version8.2.0 / PackageReference IncludeHardcodet.NotifyIcon.Wpf Version1.0.13 / PackageReference IncludeSerilog Version3.1.1 /第一个包提供了MVVM开发的基础设施第二个是实现系统托盘图标的利器第三个则是日志记录的瑞士军刀。在app.xaml.cs中初始化日志组件Log.Logger new LoggerConfiguration() .WriteTo.File(wifi-manager.log) .CreateLogger();3. 核心功能实现详解3.1 无线网卡枚举与状态检测获取无线网卡列表是首要任务。经过多次测试我发现最可靠的方式是组合使用netsh命令和WMI查询public static ListNetworkAdapter GetWirelessAdapters() { var adapters new ListNetworkAdapter(); // 方法1使用netsh命令最快 try { var process new Process { StartInfo new ProcessStartInfo { FileName netsh, Arguments wlan show interfaces, RedirectStandardOutput true, UseShellExecute false } }; process.Start(); var output process.StandardOutput.ReadToEnd(); // 解析输出获取网卡信息 var matches Regex.Matches(output, 名称\s*:\s*(.?)\r\n.*?GUID\s*:\s*({[A-F0-9-]})); foreach (Match match in matches) { adapters.Add(new NetworkAdapter { Name match.Groups[1].Value.Trim(), Guid match.Groups[2].Value }); } } catch { /* 失败时回退到方法2 */ } // 方法2使用WMI查询更全面 if (adapters.Count 0) { using var searcher new ManagementObjectSearcher( SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID IS NOT NULL); foreach (var obj in searcher.Get()) { if (obj[NetConnectionID].ToString().Contains(Wireless)) { adapters.Add(new NetworkAdapter { Name obj[NetConnectionID].ToString(), Guid obj[GUID].ToString() }); } } } return adapters; }3.2 网卡状态切换实现启用/禁用网卡的核心代码其实很简单但有几个关键细节需要注意public static bool ToggleAdapter(string adapterGuid, bool enable) { try { using var adapter new ManagementObject( $Win32_NetworkAdapter.DeviceID{adapterGuid}); var result adapter.InvokeMethod( enable ? Enable : Disable, null); return (int)result 0; } catch { // 回退到devcon.exe方案 return RunDevCon(adapterGuid, enable); } } private static bool RunDevCon(string guid, bool enable) { var process new Process { StartInfo new ProcessStartInfo { FileName devcon.exe, Arguments ${(enable ? enable : disable)} \{guid}\, Verb runas, // 请求管理员权限 UseShellExecute true } }; process.Start(); process.WaitForExit(); return process.ExitCode 0; }这里有个重要技巧我们同时准备了两种实现方案。主方案使用WMI回退方案使用devcon.exe。这是因为某些厂商的网卡驱动对WMI支持不完善而devcon.exe是Windows官方工具兼容性更好。4. 用户体验优化技巧4.1 系统托盘集成一个好的工具应该做到随用随走。使用Hardcodet.NotifyIcon包可以轻松实现系统托盘功能Window.Resources ContextMenu x:KeyTrayMenu MenuItem Header启用Wi-Fi Command{Binding EnableCommand}/ MenuItem Header禁用Wi-Fi Command{Binding DisableCommand}/ Separator/ MenuItem Header退出 Command{Binding ExitCommand}/ /ContextMenu /Window.Resources tb:TaskbarIcon IconSource/Assets/wifi.ico ToolTipTextWi-Fi管理器 ContextMenu{StaticResource TrayMenu}/4.2 管理员权限处理网络操作需要管理员权限我们可以在项目属性中直接配置右键项目 → 属性 → 安全性 → 启用ClickOnce安全设置编辑app.manifest文件取消注释以下行requestedExecutionLevel levelrequireAdministrator uiAccessfalse /但这样每次启动都会弹出UAC提示。更好的做法是仅在需要时请求权限public static void RestartAsAdmin() { var startInfo new ProcessStartInfo { FileName Assembly.GetEntryAssembly().Location, UseShellExecute true, Verb runas }; Process.Start(startInfo); Application.Current.Shutdown(); }5. 高级功能扩展5.1 命令行支持为方便高级用户我们添加命令行参数支持static void Main(string[] args) { if (args.Length 0) { // 命令行模式 var parser new Parser(settings { settings.CaseSensitive false; settings.HelpWriter Console.Out; }); parser.ParseArgumentsOptions(args) .WithParsed(RunInCliMode); } else { // 图形界面模式 var app new App(); app.InitializeComponent(); app.Run(); } } private static void RunInCliMode(Options opts) { if (opts.ListAdapters) { Console.WriteLine(可用无线网卡); foreach (var adapter in GetWirelessAdapters()) { Console.WriteLine(${adapter.Name} ({adapter.Guid})); } return; } // 其他命令处理... }5.2 多显示器适配WPF在高DPI和多显示器环境下经常会出现显示问题。这是我总结的最佳实践protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); // 获取当前屏幕信息 var screen Screen.FromHandle(new WindowInteropHelper(this).Handle); var dpi VisualTreeHelper.GetDpi(this); // 计算居中位置 Left screen.WorkingArea.Left (screen.WorkingArea.Width - ActualWidth * dpi.DpiScaleX) / 2; Top screen.WorkingArea.Top (screen.WorkingArea.Height - ActualHeight * dpi.DpiScaleY) / 2; }6. 实际使用中的坑与解决方案在开发过程中我遇到了几个典型问题网卡状态更新延迟禁用网卡后系统需要几秒钟才能更新状态。解决方案是添加状态轮询机制async Task WaitForAdapterState(string guid, bool desiredState) { var timeout DateTime.Now.AddSeconds(10); while (DateTime.Now timeout) { if (GetAdapterState(guid) desiredState) return; await Task.Delay(500); } throw new TimeoutException(); }多语言系统兼容性netsh命令的输出在不同语言系统下格式不同。我们通过检测系统语言来动态调整正则表达式var isChinese CultureInfo.CurrentCulture.Name.StartsWith(zh); var pattern isChinese ? 名称\s*:\s*(.?)\r\n.*?GUID\s*:\s*({[A-F0-9-]}) : Name\s*:\s*(.?)\r\n.*?GUID\s*:\s*({[A-F0-9-]});虚拟网卡干扰某些VPN软件会创建虚拟网卡我们需要在枚举时过滤掉它们if (adapter.Name.Contains(Virtual) || adapter.Name.Contains(VPN) || adapter.Name.Contains(TAP)) { continue; }7. 性能优化实践为了让工具启动更快我做了这些优化延迟加载将耗时的网卡枚举操作放到后台线程async void Window_Loaded(object sender, RoutedEventArgs e) { Adapters.Clear(); await Task.Run(() { foreach (var adapter in NetworkService.GetWirelessAdapters()) { Dispatcher.Invoke(() Adapters.Add(adapter)); } }); }缓存机制将常用网卡的GUID保存到本地配置文件{ DefaultAdapter: {c33b9d21-b6e2-417e-950e-0ca2d2f9da42}, LastUsedTime: 2023-12-20T14:30:00 }极简模式通过--fast参数跳过所有状态检查直接执行操作if (options.FastMode) { Process.Start(netsh, $interface set interface \{adapterName}\ admindisable); Environment.Exit(0); // 立即退出 }8. 项目打包与分发最后我们需要将项目打包成用户友好的形式发布为单文件应用dotnet publish -c Release -r win-x64 -p:PublishSingleFiletrue添加快捷方式生成功能void CreateShortcut() { var shell new WshShell(); var shortcut (IWshShortcut)shell.CreateShortcut( Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.Desktop), 禁用Wi-Fi.lnk)); shortcut.TargetPath Assembly.GetEntryAssembly().Location; shortcut.Arguments --disable --fast; shortcut.WorkingDirectory AppContext.BaseDirectory; shortcut.Save(); }添加自动更新检查可选async Task CheckForUpdates() { using var client new HttpClient(); var latest await client.GetStringAsync( https://api.github.com/repos/yourname/wifi-manager/releases/latest); var remoteVer JObject.Parse(latest)[tag_name].ToString(); if (remoteVer ! Assembly.GetEntryAssembly().GetName().Version.ToString()) { // 提示用户更新 } }