因为有时下载东西的时候,不想让电脑自动深入睡眠,所以就开启了离开模式。这样不但不节能环保,而且到真正想要睡眠的时候就是一翻蛋疼。
改过自新,关闭了离开模式,同时无操作30分钟后也会进入睡眠模式。但是在下载的时候怎么办呢?反正也是闲着,就写了这东西:
QQ20130831205319_thumb" src="/Upload/Images/2013090103/918F7B33429E3468.jpg" alt="QQ20130831205319_thumb" width="207" height="177" border="0" />
第一张是主界面,最小化时会隐藏窗口。后面的是托盘的菜单,托盘会根据不同的设置选择不同的图标。
其中,开启离开模式不需要修改注册表,自然的,也就在软件开着的时候有效。监控模式则是每隔半分钟,就将本次设置告知系统。为啥需要这个选项呢?往后看看实现就知道了。
程序在最后面。
下面是实现(其实写这个程序的目的之一就是为了学习C#……):
(所谓的)核心代码是调用这个API ( MSDN的介绍) :
class="brush: cpp; first-line: 1; fontsize: 100; ">EXECUTION_STATE WINAPI SetThreadExecutionState( _In_ EXECUTION_STATE esFlags );
这个API作用是允许程序通知系统在使用某些资源,以阻止系统进入睡眠或关闭显示器。
参数esFlags是以下选项的组合:ES_AWAYMODE_REQUIRED, ES_CONTINUOUS,ES_DISPLAY_REQUIRED,ES_SYSTEM_REQUIRED。
按字面意思理解选项即可。其中ES_CONTINUOUS表示在下一次调用该API前,本次设置会一直生效。所以在大部分情况下,加上这个选项的话,只需调用一次API即可。但考虑到可能有别的程序也在调用这个API,因而让本程序的设置失效,所以有了监控模式:每隔一断时间就将设置通知系统。
C# 怎么使用Win32API呢?
using System.Runtime.InteropServices; // 按照API原型,将类型转换C#的类型声明即可 [DllImport("kernel32.dll")] static extern uint SetThreadExecutionState(uint esFlags); // 选项所用到的常数 const uint ES_AWAYMODE_REQUIRED = 0x00000040; const uint ES_CONTINUOUS = 0x80000000; const uint ES_DISPLAY_REQUIRED = 0x00000002; const uint ES_SYSTEM_REQUIRED = 0x00000001;
C#所有的方法变量都必须声明在一个类里。我把这些东西声明到一个叫Public的类。声明后,就可以直接调用该方法了。
(不知道起什么子标题……)
该程序可以通过主界面以及托盘弹出的菜单进行选择的设置,所以需要同步这两个地方的状态,Checkbox该自动打钩时打钩,该取消时取消等等。为了写代码时,不需要考虑这些东西,就抽象出了一个Option类(每次起名的时候就痛恨自己英文太差)。通过该类可以设置选项,同时也可以在里面注册一个委托,当选项有变时,会调用这些委托。同时,该类也会负责在设置选项时自动通知系统。注,因为使用了ES_CONTINUOUS这个选项,所以为了避免程序退出后显示器可能仍然不会自动关闭,所以在析构函数中,以参数0调用一次该API。
主界面:
主界面就是拉拉控件,处理下事件。注意,Checkbox的选项值可能会因为鼠标点击以外的原因改变,所以选择监听鼠标点击事件。为了处理事件时少粘贴点代码,我用一个Dictionary将每个CheckBox和一个选项值绑定在一起。然后使用同一个事件处理函数,在函数里,根据sender确定是哪个Checkbox发送的,再根据那个Dictionary确定需要设置什么样的值。 然后向Option类注册一个委托,在选项有变时改变Checkbox到正确的状态。
最小化时直接隐藏窗口,而不是缩小的任务栏:可以选择监听Resize事件,在事件中判断窗体是不是处于最小化的状态,是的话就隐藏窗体。更彻底的是重载窗体类的消息处理函数,并自己处理最小化消息。
protected override void WndProc(ref Message m) { const int WM_SYSCOMMAND = 0x112; const int SC_CLOSE = 0xF060; const int SC_MINIMIZE = 0xF020; const int SC_MAXIMIZE = 0xF030; if (m.Msg == WM_SYSCOMMAND) { if (m.WParam.ToInt32() == SC_MINIMIZE) { this.Hide(); return; } } base.WndProc(ref m); }
托盘部分:
托盘:使用NotifyIcon控件。注意,这个控件必须设置ICON才能显示。添加一个ContextMenuStrip对象作为右键点击时的弹出菜单。为了让点击托盘时能弹出这个菜单,可以将托盘控件的ContextMenuStrip属性设置为该菜单即可。在这里因为我需要监听鼠标事件,让左键点击时显示主窗口,我就在事件处理函数中顺便处理右键点击了。如果是右键点击,则调用该菜单的Show方法就可以了。
然后是菜单的内容。新建若干ToolStripMenuItem对象,并用ContextMenuStrip.Items.Add(…)方法将这些Item添加进菜单即可。每个Item可以监听鼠标点击事件,同时也可以通过设置它的Checked属性来显示item文本前的小钩钩。需要分割线的话添加ToolStripSeparator对象就可以了。
动态的托盘图标:为了少写点什么读取文件之类的代码,就直接把托盘图标添加进程序的资源文件里了。方法是在项目的属性里选择资源,然后就可以添加想要的资源了。需要访问这些资源时,在myProject.Properties.Resources里就可以访问了。如:System.Drawing.Bitmap bitmap1 = myProject.Properties.Resources.Image01(参考MSDN)。最后再根据选项的状态选择相应的图标即可。
托盘消失:直接执行Application.Exit()的话,托盘不会自动消失,得鼠标从托盘上面滑过才可以。为了让托盘显示,执行NotifyIcon对象的Dispose( )方法就好了。
让程序开始运行时只显示托盘,而不显示主窗体。放狗搜索的时候发现这个问题让很多新手喝了一壶。我的解决方法是:让程序开始是不运行主窗体的代码,只运行托盘控件的代码。转到Main函数,发现Main函数最后执行的是 Application.Run(…) 这个方法。最开始尝试在这个方法的参数里填入一个NotifyIcon对象,但是不行。然后试着直接用new新建一个NotifyIcon对象,这个时候托盘是会显示出来,但是程序马上就会退出了。最后发现这个方法有个无参重载版本,执行后程序就不会退出了……
生成的EXE的图标:在项目的属性里,选择应用程序选项卡,就可以设置图标了。
PS:之前有次面试,面试官问我写的程序的核心是啥,让我不知如何是好。这个程序就是一个例子,核心代码没几行,但实现其他功能花费了绝大部分的时间……