企业应用中,经常会遇到一些需要定时自动执行的程序来完成某些功能,比如:自动定时从第三方web service取回数据、定时对历史数据进行清理、定时向ftp上传业务数据...
这类程序,我习惯称为“机器人”程序,就象机器一样机械、高效、重复的执行某些任务。通常部署上线后,都是放在服务器上一直开着,不允许轻易被关闭,而且最好要有一个界面,随时可以手动方便控制状态或查看运行情况,一旦发生异常情况,能及时通知管理员(Email或短信之类)
如果是采用WPF技术开发,以下是几个需要注意的地方:
1、无边框窗体(防止用户不小心点到 右上角的关闭按钮)
<Window x:Class="WeatherSpider.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStyle="None" ...>
将主窗体的WindowStyle设置成None即可
2、无边框窗体的移动
去掉顶上的边框后,通常为了美观,我们需要自己在顶上放一个伪造的标题栏,类似下面这样
<Border Grid.Row="0" MouseLeftButtonDown="TitleBarOnMouseLeftButtonDown" > <Grid Margin="5,5,5,0" > <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Foreground="GreenYellow" FontSize="16" x:Name="tbTitle">全国机场天气-采集机器人</TextBlock> <TextBlock Text="最小化" Grid.Column="1" Foreground="GreenYellow" FontSize="12" VerticalAlignment="Center" TextAlignment="Right" x:Name="btnMin" Cursor="Hand" MouseLeftButtonDown="btnMin_MouseLeftButtonDown"></TextBlock> </Grid> </Border>
为了实现鼠标拖动标题栏时,窗体也能跟着拖动,需要在标题栏的对象上增加MouseLeftButtonDown事件处理(即:上面代码Border上的MouseLeftButtonDown="TitleBarOnMouseLeftButtonDown" )
private void TitleBarOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { base.DragMove(); }
哦,原来 so easy !
3.最小化到系统托盘
Winform中的NotifyIcon控件在WPF中仍然可以继续使用
先 using System.Windows.Forms; 添加Windows.Forms命名空间的引用
再声明一个窗体级的变量
private readonly NotifyIcon notifyIcon;
最后在主窗体的构架函数中,加入下列这一段
notifyIcon = new NotifyIcon(); notifyIcon.BalloonTipText = Properties.Resources.AppTitle + " 正在运行!"; notifyIcon.Text = Properties.Resources.AppTitle;//指定托盘提示文字为资源中的AppTitle字符串 notifyIcon.Icon = Properties.Resources.App;//指定托盘图标为资源中的"App"图标 notifyIcon.Visible = false; notifyIcon.MouseClick += notifyIcon_MouseClick; //托盘右键菜单 MenuItem itemShowMainForm = new MenuItem("显示主界面"); itemShowMainForm.Click += ShowMainWindow; MenuItem itemExit = new MenuItem("退出"); itemExit.Click += ExitApplication; MenuItem[] menuItems = new[] { itemShowMainForm, itemExit }; notifyIcon.ContextMenu = new ContextMenu(menuItems);
notifyIcon_MouseClick事件代码如下:
public void Show() { Visibility = Visibility.Visible; Activate(); notifyIcon.Visible = false; } /// <summary> /// 托盘图标鼠标点击处理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void notifyIcon_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (Visibility == Visibility.Visible) { Visibility = Visibility.Hidden; notifyIcon.Visible = true; } else { Show(); } } } //显示主界面 void ShowMainWindow(object sender, EventArgs e) { Show(); }
在上面提到的第2点中,可能已经有朋友注意到了“最小化”的文本上,已经加了 MouseLeftButtonDown="btnMin_MouseLeftButtonDown"事件处理,即点击“最小化”这几个字,可以缩小到托盘区,代码如下:
private void btnMin_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Visibility = Visibility.Hidden;//隐藏主窗体 notifyIcon.Visible = true;//显示托盘图标 notifyIcon.ShowBalloonTip(1000);//显示托盘图标上的气泡提示1秒钟 }
4.程序退出时,主动提醒
虽然做了无边框窗体的处理,但是如果用户意外按了Alt+F4,甚至误操作注销或重启Windows,程序还是会直接退出的,最好能给个提示,这样管理员看到提示后,有机会取消误操作
先给主窗体增加Closing事件处理,主窗体构造函数中,加入下面这一行
Closing += Window_Closing;
Window_Closing事件如下:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (MessageBox.Show("确定要退出[" + Properties.Resources.AppTitle + "]吗?", Properties.Resources.AppTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes) { this.Closing -= Window_Closing;//注意:这里要注销事件监听,否则会连续弹出二次提示框才能退出 notifyIcon.Visible = false; e.Cancel = false; } else { e.Cancel = true; } }
经过上述处理后,用户按Alt+F4时,就会提示是否退出。但这样还不够,如果Windows注销时,仍然会直接退出
这就需要 using Microsoft.Win32;使用Win32命名空间下的某些功能了,主窗体构造函数中,增加:
//捕获关机事件 SystemEvents.SessionEnding += SystemEvents_SessionEnding;
处理代码如下:
void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) { if (MessageBox.Show("[" + Properties.Resources.AppTitle + "]正在运行中,确定要退出吗?", Properties.Resources.AppTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes) { e.Cancel = false; } else { e.Cancel = true; } }
同时在刚才的Window_Closing中,增加一行代码:(见下面的注释行)
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (MessageBox.Show("确定要退出[" + Properties.Resources.AppTitle + "]吗?", Properties.Resources.AppTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes) { SystemEvents.SessionEnding -= SystemEvents_SessionEnding; //取消关机事件监听 this.Closing -= Window_Closing; notifyIcon.Visible = false; e.Cancel = false; } else { e.Cancel = true; } }
5.单实例运行
Winform中要实现单实例运行,非常容易(见 利用c#制作托盘程序,并禁止多个应用实例运行),但是WPF中就有点麻烦,网上搜索了一下,有朋友已经解决了这个问题
引用using Microsoft.VisualBasic.ApplicationServices; (注:必须先添加对Microsoft.VisualBasic的程序集引用)
然后把App.xaml编译属性改成Page,同时修改App.xaml.cs代码如下:
using System.Windows; using System.Diagnostics; using System; using WeatherSpider.Helper; namespace WeatherSpider { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { /// <summary> /// Application Entry Point. /// </summary> [STAThread] [DebuggerNonUserCode] public static void Main(string[] a) { SingleApp app = new SingleApp();//SingleApp类后面马上会提到 app.Run(a); } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); MainWindow w = new MainWindow(); w.Show();//即调用主窗体中的Show方法,显示主窗体 } public void Activate() { (MainWindow as MainWindow).Show(); } } }
再创建一个SingleApp类
using Microsoft.VisualBasic.ApplicationServices; namespace WeatherSpider.Helper { public class SingleApp : WindowsFormsApplicationBase { App a; public SingleApp() { this.IsSingleInstance = true; } protected override bool OnStartup(StartupEventArgs eventArgs) { a = new App(); a.Run(); return false; } protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs) { base.OnStartupNextInstance(eventArgs); a.Activate();//第二个实例试图“启动”时,自动把已经运行的实例激活并显示 } } }
最后上图二张: