C#和WPF系统托盘应用程序开发

在本文中,将探讨如何使用C#WPF创建一个系统托盘应用程序。系统托盘应用程序通常在用户的操作系统托盘中显示一个图标,允许用户通过点击该图标来执行各种操作。将构建一个示例应用程序,该应用程序模拟一个设备,该设备根据用户的菜单命令在运行和非运行状态之间切换。

系统托盘应用程序的主要功能

系统托盘应用程序将实现以下功能:

  • 在系统托盘中显示一个图标。
  • 当用户右键或左键点击图标时,显示弹出菜单。
  • 通过菜单命令启动一系列视图。
  • 当设备状态发生变化时,在系统托盘上方显示气泡文本。
  • 工具提示。
  • 根据设备状态变化的图标。

菜单命令集

应用程序将包含以下基本命令集:

  • 显示应用程序信息。
  • 显示状态信息。
  • 启动模拟设备。
  • 停止模拟设备。
  • 退出系统托盘应用程序。

代码架构

代码提供了一个基本框架,可以轻松地根据需求进行修改,例如控制连接到USB端口的硬件设备。架构故意保持简单,对象数量少,职责划分清晰:

  • 应用程序上下文对象仅初始化应用程序。
  • 设备管理器对象包装(模拟)设备,并实现允许客户端对象控制设备的接口。将接口与实现分离是许多原因的良好设计,包括减少组件之间的耦合,轻松交换实现,以及允许客户端使用虚拟接口进行测试,与实现无关。
  • 视图管理器对象管理用户界面。它拥有NotifyIcon对象以及各种菜单和视图。它通过设备管理器接口控制设备。

要理解本文,需要了解.NET和WPF。.NET的NotifyIcon类可以轻松创建系统托盘应用程序,但它与WPF不兼容。因此,基于NotifyIcon类的系统托盘应用程序通常使用WinForms实现视图和对话框。这里采用的替代方法是将WPF窗体放入单独的程序集中。

代码实现

Main函数首先检查是否已经有应用程序实例在运行,如果有,则终止,因为一次只能运行一个实例。它通过创建一个具有固定名称的命名互斥体来检测另一个实例的存在。如果该互斥体已经存在,则必须已经有另一个实例在运行。互斥体名称是程序集的GUID,这应该避免与系统中其他命名互斥体的冲突。

bool createdNew = false; string mutexName = System.Reflection.Assembly.GetExecutingAssembly().GetType().GUID.ToString(); using (System.Threading.Mutex mutex = new System.Threading.Mutex(false, mutexName, out createdNew)) { if (!createdNew) { // Only allow one instance return; } }

接下来,创建应用程序上下文实例。通常,应用程序会创建其主窗口对象并将其传递给Application.Run方法。然而,不需要主窗口,所以传递一个应用程序上下文。

Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); try { STAApplicationContext context = new STAApplicationContext(); Application.Run(context); } catch (Exception exc) { MessageBox.Show(exc.Message, "Error"); }

应用程序上下文派生自ApplicationContext类,负责初始化系统。它只有两个属性:

private ViewManager _viewManager; private DeviceManager _deviceManager;

ViewManager对象管理用户界面,并使用IDeviceManager接口与设备交互。DeviceManager类管理模拟设备。它实现了IDeviceManager接口。

public STAApplicationContext() { _deviceManager = new DeviceManager(); _viewManager = new ViewManager(_deviceManager); _deviceManager.OnStatusChange += _viewManager.OnStatusChange; _deviceManager.Initialise(); }

IDeviceManager接口定义了一组简单的命令和属性来控制(模拟)设备:

public interface IDeviceManager { string DeviceName { get; } DeviceStatus Status { get; } List> StatusFlags { get; } void Initialize(); void Start(); void Stop(); void Terminate(); }

上述接口由DeviceManager类实现。有关DeviceManager类的更多详细信息,请参见示例代码。可以说,它只是一个模拟真实设备的外壳。

ViewManager类在其构造函数中创建并初始化NotifyIcon实例:

public ViewManager(IDeviceManager deviceManager) { System.Diagnostics.Debug.Assert(deviceManager != null); _deviceManager = deviceManager; _components = new System.ComponentModel.Container(); _notifyIcon = new System.Windows.Forms.NotifyIcon(_components) { ContextMenuStrip = new ContextMenuStrip(), Icon = SystemTrayApp.Properties.Resources.NotReadyIcon, Text = "System Tray App: Device Not Present", Visible = true, }; _notifyIcon.ContextMenuStrip.Opening += ContextMenuStrip_Opening; _notifyIcon.DoubleClick += notifyIcon_DoubleClick; _notifyIcon.MouseUp += notifyIcon_MouseUp; _aboutViewModel = new WpfFormLibrary.ViewModel.AboutViewModel(); _statusViewModel = new WpfFormLibrary.ViewModel.StatusViewModel(); _statusViewModel.Icon = AppIcon; _aboutViewModel.Icon = _statusViewModel.Icon; _hiddenWindow = new System.Windows.Window(); _hiddenWindow.Hide(); }

.NET的NotifyIcon类实现了系统托盘图标。上述代码为上下文菜单打开、双击和鼠标弹起事件安装了系统托盘事件处理程序。它还为两个视图创建了视图模型的实例,即关于视图和状态视图。

ContextMenuStrip_Opening方法构建上下文菜单(如果尚不存在),然后根据需要启用/禁用菜单项:

private void ContextMenuStrip_Opening(object sender, System.ComponentModel.CancelEventArgs e) { e.Cancel = false; if (_notifyIcon.ContextMenuStrip.Items.Count == 0) { _startDeviceMenuItem = ToolStripMenuItemWithHandler("Start Device", "Starts the device", startStopReaderItem_Click); _notifyIcon.ContextMenuStrip.Items.Add(_startDeviceMenuItem); _stopDeviceMenuItem = ToolStripMenuItemWithHandler("Stop Device", "Stops the device", startStopReaderItem_Click); _notifyIcon.ContextMenuStrip.Items.Add(_stopDeviceMenuItem); _notifyIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator()); _notifyIcon.ContextMenuStrip.Items.Add(ToolStripMenuItemWithHandler("Device S&tatus", "Shows the device status dialog", showStatusItem_Click)); _notifyIcon.ContextMenuStrip.Items.Add(ToolStripMenuItemWithHandler("&About", "Shows the About dialog", showHelpItem_Click)); _notifyIcon.ContextMenuStrip.Items.Add(ToolStripMenuItemWithHandler("Code Project &Web Site", "Navigates to the Code Project Web Site", showWebSite_Click)); _notifyIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator()); _exitMenuItem = ToolStripMenuItemWithHandler("&Exit", "Exits System Tray App", exitItem_Click); _notifyIcon.ContextMenuStrip.Items.Add(_exitMenuItem); } SetMenuItems(); }

当用户选择“设备状态”命令时,系统将调用showStatusItem_Click方法:

private void showStatusItem_Click(object sender, EventArgs e) { ShowStatusView(); } private void ShowStatusView() { if (_statusView == null) { _statusView = new WpfFormLibrary.View.StatusView(); _statusView.DataContext = _statusViewModel; _statusView.Closing += ((arg_1, arg_2) => _statusView = null); _statusView.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; _statusView.Show(); UpdateStatusView(); } else { _statusView.Activate(); } _statusView.Icon = AppIcon; }

如果视图存在,代码将激活它并设置图标。否则,它将创建一个状态视图,并初始化它,包括添加Closing事件处理程序和更新内容。关于视图的代码非常相似,并包含在示例代码中。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485