想必大部分网友都使用过QQ、MSN等聊天程序,它们的界面都相当华丽,尤其是当网友上线以及消息提示时会有一个浮动的窗体从屏幕的右下方缓慢升起,既美观又人性化,作为程序员在享受的同时我们也不禁要问:这到底是怎么实现的呢?本文就利用Visual Studio .Net C# 2005以及.Net框架绘图技术来实现这种任务栏通知窗口。
简介
QQ和MSN的任务栏通知窗口很人性化,它可以在不丢失主窗体焦点的前提下显示一个具备皮肤Skin的通知窗体,当它显示一段时间后会自动消失,所以用户根本不用干预它。这样的通知窗体和一般的具备标题栏、系统图标和按钮的窗体没有太大的区别,窗体表面其实就是画上去的一张位图而已,而窗体的浮动则会复杂一点,我们会用到.Net框架的双重缓冲区绘图技术(参见作者编译文章“Windows窗体的.Net框架绘图技术”)来保证移动窗体时所显示的内容平滑且不闪烁,以及使用P/Invoke平台调用进行对Win32API函数的调用来完成不获得焦点的窗体显示和非标题栏窗体拖动。两种位图的皮肤运行时的界面如下:
ITPUB个人空间4B;n~tZl%s4aac
AX]GV O0
背景知识
通知窗口就是将一般的窗体附加上一层皮肤,这里所谓的皮肤就是一张位图图片,该位图图片通过窗体的OnPaintbackground事件被绘制到窗体表面,在附加位图之前需要调整窗体的可视属性,由于绘制操作是针对于窗体客户区域的,所谓客户区域就是指窗体标题栏下方以及窗体边框以内的所有区域,所以需要将窗体的边框和外观属性FormBorderStyle调整为:None,这样所绘制的图像就会填充整个窗体。
首先,我们会用到Region对象,Region对象可以精确的描绘出任意形状的轮廓范围,通过一个位图图像创建Region对象后再将其传递给窗体的Region属性就可以使窗体按照Region所定义的轮廓显示出来。作为皮肤使用的位图文件可以通过任何图像编辑软件诸如:Photeshop来创建和编辑,只是注意一点,需要将图片的背景色调成特定颜色以便程序绘制时将其清除,我们在这里使用的背景色为粉红色。为了能够让Region对象按照图像中感兴趣的内容边框来创建窗体,我们还需要使用GraphicsPath类将图像轮廓按照一定路径标注下来,稍后便按照该路径创建Region对象。然后通过窗体的绘图事件将位图的内容显示在窗体表面,我们没有直接使用OnPaintbackground事件而是重载了该方法,这样做的好处就是一些低层的绘制操作还继续交由.Net框架运行时来处理,我们只考虑实际需要的绘制操作即可。在OnPaintbackground方法中我们启用了双重缓冲区绘图技术,所谓该技术就是指先在内存中的一块画布上把将要显示的图像显示出来或进行处理,等到操作完成再将该画布上所显示的图像放置到窗体表面,这样的机制可以非常有效的降低闪烁的出现,使图像显示更加平滑。通知窗体从屏幕的右下方进行升起停留一段时间后再慢慢回落,这里需要用到返回屏幕区域的大小范围的.Net框架方法Screen.GetWorkingArea(WorkAreaRectangle),通过一定算法计算出通知窗体显示前的初始位置。最后,我们将要显示的文本按照一定格式和Rectangle对象所指定的区域范围绘制到窗体表面。通知窗体的关闭操作是通过设定一个区域,当用户用鼠标单击时检测单击坐标是否在该区域内,若在区域内就可以执行隐藏通知窗体的代码。
我们注意了,当QQ和MSN的通知窗口显示时其主窗体的焦点没有丢失,也就是说程序没有将自身的焦点转移到显示的通知窗体上。经过测试,我们无论怎么样调用.Net框架提供的窗体显示例程譬如:Form.Show都无法保证主窗体的焦点不丢失,在VC环境下我们可以使用Win32API的ShowWindows函数来完成复杂的窗体显示操作,但是.Net框架根本没有提供类似的方法,那么我们能否通过.Net框架调用该API函数来显示窗体呢?幸好.Net框架提供了P/Invoke平台调用,利用平台调用这种服务,托管代码就可以调用在动态链接库中实现的非托管函数,并可以封送其参数,我们可以轻松的显示但不获得焦点的窗体。程序中用到的Windows API以及常量的定义都保存在WinUser.h头文件中,其对应的动态链接库文件就是user32.dll,使用.Net框架提供的DllImportAttribute类对导入的函数进行定义,然后就可以非常方便的在程序中调用该函数了。
由于我们将通知窗体的标题栏隐藏了,所以对窗体拖动操作还需要我们自己动手进行处理。本文介绍了如何更加高效的进行拖动窗体操作,有些网友在对于非标题栏拖动窗体编程时偏向组合使用鼠标事件来进行,这样做的本质没有任何不妥,但是频繁的事件响应和处理反而使程序性能有所降低。我们将继续使用Win32API的底层处理方法来解决该问题,就是向窗体发送标题栏被单击的消息,模拟实际的拖动操作。
我们会通过2个计时器来完成窗体的显示、停留和隐藏,通过设置速度变量可以改变窗口显示和隐藏的速度。
程序实现
启动Visual Studio .Net 2005,创建C# Windows窗体应用程序,将解决方案命名为TaskbarForm,包含的项目名也为TaskbarForm,首先创建程序的主窗体Form1,在上面添加两个Button控件,一个用于显示通知窗体,另一个则终止程序。然后在解决方案管理器中右击项目,单击“添加– Windows窗体”,我们把新创建的窗体命名为TaskbarForm。
在类TaskbarForm定义的下方,我们创建用于显示的字符串和其颜色的变量,再定义几个Rectangle对象的变量用于放置标题、提示内容以及可以拖动窗体的区域和关闭按钮的区域。然后,我们需要保存窗体在浮动时的高度以便计算移动后的新高度,intervalValue变量用来确定窗体显示和隐藏的速度。进行平台调用时我们需要提前定义好常量的值用来传递给函数,WM_NCLBUTTONDOWN和HT_CAPTION常量用于拖动窗体,他们的值都保存在WinUser.h头文件中,所对应的动态链接库名为:user32.dll。我们用到的Win32API为:SendMessage、ReleaseCapture和ShowWindow,通过使用DllImportAttribute可以导入相应的函数并在程序中重新进行定义,如下:
[DllImportAttribute("user32.dll")]ITPUB个人空间%C7x KO }"Y\L$~o
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
3KWx$@6|e/M)Gtw o-]3{0 //发送消息//winuser.h 中有函数原型定义
i z"qz1U tc b?0 [DllImportAttribute("user32.dll")]ITPUB个人空间l9~1z(q tE
public static extern bool ReleaseCapture(); //释放鼠标捕捉winuser.hITPUB个人空间 _\c+@/e0j
[DllImportAttribute("user32.dll")] //winuser.h
*y c&d#gE4?G4E0 private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
SendMessage向消息循环发送标题栏被按下的消息来模拟窗体的拖动,ShowWindow用来将特定句柄的窗体显示出来,注意第二个参数nCmdShow,它表示窗体应该怎样显示出来,而我们需要窗体不获得焦点显示出来,SW_SHOWNOACTIVATE可以满足我们要求,继续在WinUser.h文件中搜索找到该常量对应的值为4,于是我们就可以这样调用来显示窗体了:
ShowWindow(this.Handle, 4);
我们创建了一个自定义函数ShowForm用来封装上面的ShowWindow用来是显示窗体,同时传递了所用到的几个Rectangle矩形区域对象,最后调用ShowWindows函数将窗体显示出来,代码片段如下:
public void ShowForm(string ftitletext, string fcontenttext, Rectangle fRegionofFormTitle, Rectangle fRegionofFormTitlebar, Rectangle fRegionofFormContent, Rectangle fRegionofCloseBtn)
!r8p'qg:C7sq0{
7|jzgl%?9]u0 titleText = ftitletext;
I"@$Ff0W1Yg0 contentText = fcontenttext;ITPUB个人空间/l n#\(q&kMxu
WorkAreaRectangle = Screen.GetWorkingArea(WorkAreaRectangle);
%D?r8?X7L3n-u)G,j2I0 this.Top = WorkAreaRectangle.Height + this.Height;ITPUB个人空间'a iM7c#^zG
FormBorderStyle. = FormBorderStyle.None;ITPUB个人空间&zl`1A)u5^5R Bsr;H
WindowState = FormWindowState.Normal;ITPUB个人空间9Ibome
this.SetBounds(WorkAreaRectangle.Width - this.Width, WorkAreaRectangle.Height - currentTop, this.Width, this.Height);
0[${rx4[(`5N0 CurrentState = 1;ITPUB个人空间W(]J)@2[P.H8E
timer1.Enabled = true;
P4D\e? d{0 TitleRectangle = fRegionofFormTitle;
)CCp.r3d7p t0 TitlebarRectangle = fRegionofFormTitlebar;ITPUB个人空间Wq G I$g b
ContentRectangle = fRegionofFormContent;ITPUB个人空间i(KTcm;w
CloseBtnRectangle = fRegionofCloseBtn;ITPUB个人空间(k,`_kV`I
ShowWindow(this.Handle, 4); //#define SW_SHOWNOACTIVATE 4
4dJ9[:P b,F1cG/o |0}
XSn[6Yj#P0
CurrentState变量表示窗体的状态是显示中、停留中还是隐藏中,两个计时器根据窗体不同状态对窗体的位置进行更改,我们会使用SetBounds来执行该操作:
this.SetBounds(WorkAreaRectangle.Width - this.Width, WorkAreaRectangle.Height - currentTop, this.Width, this.Height);
h h b*o.h0
当窗体需要升起时将窗体的Top属性值不断减少,而窗体回落时将Top属性值增加并超过屏幕的高度窗体就消失了,虽然原理很简单但仍需精确控制。
SetBackgroundBitmap函数首先将窗体背景图像保存到BackgroundBitmap变量中,然后根据该位图图像轮廓和透明色创建Region,BitmapToRegion就用于完成Bitmap到Region的转换,程序再将这个Region付值给窗体的Region属性以完成不规则窗体的创建。
public void SetBackgroundBitmap(Image image, Color transparencyColor)ITPUB个人空间ll4C`!f$g(j.Vg
{ITPUB个人空间&W4c$G.gh B q-o `
BackgroundBitmap = new Bitmap(image);ITPUB个人空间'DdT p)M+W k
Width = BackgroundBitmap.Width;
^2c;K/zdUA&W#^0 Height = BackgroundBitmap.Height;ITPUB个人空间-~&}4o1? qo$f!`
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);ITPUB个人空间&blZocBZ'D L l
6fL*~h!IS x"Sz0}ITPUB个人空间L@]8Rsc!J L |C
ITPUB个人空间4n1T c tR4e?
public Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)
#zyQx$nM8u0{ITPUB个人空间Z BFM5dV'z
if (bitmap == null)
mu$k3}l'T0 throw new ArgumentNullException("Bitmap", "Bitmap cannot be null!");
&^*K-@6z+g r0 int height = bitmap.Height;
?rQ*nQ0 int width = bitmap.Width;ITPUB个人空间%|yPoCH)w.`
GraphicsPath path = new GraphicsPath();
7k6vH3dCa'B#F @.@0 for (int j = 0; j < height; j++)
$A\+z;|4nPBfk0 for (int i = 0; i < width; i++)ITPUB个人空间lw(da&m@
{
*c7T p S3\w"S9c0 if (bitmap.GetPixel(i, j) == transparencyColor)ITPUB个人空间/F'}oTA{hg
continue;ITPUB个人空间1S6Zr:[$DnW:a"w
int x0 = i;ITPUB个人空间bgnWc HI*A
while ((i < width) && (bitmap.GetPixel(i, j) != transparencyColor))
;A;H+n:A*Jw!Y0 i++;
h2u;Ut1i4I7yu z`hj h0 path.AddRectangle(new Rectangle(x0, j, i - x0, 1));
T dt/Z4c*J0 }ITPUB个人空间HdzwxV
Region region = new Region(path);
-XS FR6`3QtJ[0 path.Dispose();
+M PYoC8h"_,c0 return region;ITPUB个人空间5]JJHg$|v
}
@5R\7H5{h gm0
通知窗体背景以及文字的绘制在重载的OnPaintBackground方法中完成,而且利用了双重缓冲区技术来进行绘制操作,代码如下:
protected override void OnPaintBackground(PaintEventArgs e)ITPUB个人空间p6iOnj5K
{
w$?*X`N0 Graphics grfx = e.Graphics;
5OZ:v+wfGJ0 grfx.PageUnit = GraphicsUnit.Pixel;ITPUB个人空间 mc j'm%c c?
Graphics offScreenGraphics;
h6ak$pg/q)n0 Bitmap offscreenBitmap;
@*uz;s)`ih NB5fw!y0 ffscreenBitmap = new Bitmap(BackgroundBitmap.Width, BackgroundBitmap.Height);ITPUB个人空间&gO/Vp%WNa
ffScreenGraphics = Graphics.FromImage(offscreenBitmap);
ebg_'uN0 if (BackgroundBitmap != null)ITPUB个人空间 Y"W%G:ssTut D
{
a@R4aXb2T$r0 offScreenGraphics.DrawImage(BackgroundBitmap, 0, 0, BackgroundBitmap.Width, BackgroundBitmap.Height);
ce;dx(Ws0 }ITPUB个人空间SA*N2W9Y5I3[X M
DrawText(offScreenGraphics);ITPUB个人空间e/_#vv0j1KL4P]
grfx.DrawImage(offscreenBitmap, 0, 0);ITPUB个人空间0qm$|N7q@z
}
i^:k,y;Z0
上述代码首先返回窗体绘制表面的Graphics并保存在变量grfx中,然后创建一个内存Graphics对象offScreenGraphics和内存位图对象offscreenBitmap,将内存位图对象的引用付值给offScreenGraphics,这样所有对offScreenGraphics的绘制操作也都同时作用于offscreenBitmap,这时就将需要绘制到通知窗体表面的背景图像BackgroundBitmap绘制到内存的Graphics对象上,DrawText函数根据需要显示文字的大小和范围调用Graphics.DrawString将文字显示在窗体的特定区域。最后,调用Graphics.DrawImage将内存中已经绘制完成的图像显示到通知窗体表面。
我们还需要捕获窗体的鼠标操作,有三个操作在这里进行,1、处理拖动窗体操作,2、处理通知窗体的关闭操作,3、内容区域的单击操作。三个操作都需要检测鼠标的当前位置与每个Rectangle区域的包含关系,只要单击落在特定区域我们就进行相应的处理,代码如下:
private void TaskbarForm_MouseDown(object sender, MouseEventArgs e)ITPUB个人空间a7L&D}o BZ.OxG
{
j$oN0V9qU!^2i$^-S0 if (e.Button == MouseButtons.Left)
m7{+HF8iI,z&]0 {ITPUB个人空间6}t+zs@$h7c
if (TitlebarRectangle.Contains(e.Location)) //单击标题栏时拖动ITPUB个人空间D nm5^ m's7K8Z
{ITPUB个人空间UmHM4K TG#LA1]+R"t
ReleaseCapture(); //释放鼠标捕捉
rPR.u-B4zr#Q0 SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0); //发送左键点击的消息至该窗体(标题栏)
Rz,v+I+f,T'U6k@0 }
,t nTpG5wt0 if (CloseBtnRectangle.Contains(e.Location)) //单击Close按钮关闭ITPUB个人空间\Hl Ji*B5?U1P$yy6z
{ITPUB个人空间3k#IS3?C(gd
this.Hide();ITPUB个人空间 wlE o/} X
currentTop = 1;ITPUB个人空间 m7h(k kLK1h&WL*z
}ITPUB个人空间/K3p;iWfuC
if (ContentRectangle.Contains(e.Location )) //单击内容区域ITPUB个人空间$GP.z^z"q2s&o;g
{ITPUB个人空间*R ~m;q-D
System.Diagnostics.Process.Start("http://www.Rithia.com");
2U@tS}0 }ITPUB个人空间,{'h6JX3J#i IU
}
:gD|;dRY3M)u(f0}
结论
该程序可以很好的进行通知窗体的显示、停留和隐藏操作,并且具备简单的换肤机制,在利用了双重缓冲区绘图技术后,可以保证窗体的绘制平滑且没有闪烁。
分享到:
相关推荐
在本教程中,我们将深入探讨如何使用C#实现任务栏通知窗口。 首先,我们需要导入`System.Windows.Forms`命名空间,因为这个命名空间包含了创建和管理此类通知窗口所需的所有类。下面是一段基本的代码引入: ```...
在本文中,我们将深入探讨如何使用C#编程语言在Windows操作系统上实现一个任务栏通知窗口,也就是我们常说的托盘通知或系统托盘气泡。这个功能常见于各种应用程序,如QQ、微信等,用于在不打扰用户主界面的情况下...
本项目"**C#实现类似QQ的任务栏通知窗口**"是利用C#语言来创建一个模仿QQ应用程序任务栏通知的功能。这样的功能在现代软件中非常常见,用于向用户展示重要的信息或提示,而不会打扰到他们的主要工作流程。 任务栏...
本源码实例是关于如何利用C#实现这样的任务栏通知窗口。 首先,任务栏通知窗口的核心在于Windows API的调用,尤其是`Shell_NotifyIcon`函数。这个函数允许开发者在任务栏图标上显示自定义的通知。在C#中,我们通常...
在IT领域,尤其是在Windows操作系统开发或者桌面应用编程中,有时候我们需要对系统进行更深...在实际开发中,除非有特殊需求,否则通常不建议直接操作任务栏,而是利用.NET Framework提供的高级抽象来实现类似功能。
这个场景可以通过创建一个任务栏通知窗口(也称为“气泡通知”或“托盘通知”)来实现。本教程将指导你如何使用C#在Visual Studio 2010中创建这样一个功能。 首先,让我们了解任务栏通知窗口的基础知识。任务栏通知...
从右下角弹出小窗口,2秒后自动缩回去消失关闭,主窗体在这个过程不会失去焦点,因为用了P/Invoke平台调用!弹出的小窗体还可以根据图片改变形状(不规则窗体!),还可以拖动,关闭!小程序大功能!
C#作为.NET框架的一部分,提供了丰富的API来帮助开发者利用任务栏状态区的功能。本文将深入探讨如何在C#中实现任务栏状态区的使用。 首先,我们需要导入`System.Windows.Forms`命名空间,因为它包含了与任务栏状态...
这个类提供了在任务栏通知区域显示图标的能力,同时可以添加右键菜单和自定义提示信息。为了实现任务栏状态的更新,我们需要创建一个`NotifyIcon`对象,并设置其图标和点击事件。 接着,为了显示下载进度,我们需要...
在本文中,我们将深入探讨如何通过编程来定制任务栏时间的显示样式,特别是利用Visual Studio 2008这样的开发工具来实现这一目标。 首先,我们需要了解Windows API(应用程序接口)的概念。API是一组预定义的函数、...
"C#任务栏滑出式提醒框无乱码汉化版代码"就是一个针对此类需求的解决方案。这个项目提供了一个经过汉化处理的提醒框控件,它能够在任务栏底部滑出,显示信息,而且不会出现常见的字符编码问题,确保了中文字符的正确...
3. 利用Windows API调用:更复杂的解决方案可能涉及到编写VBScript或C#等编程语言的小程序,通过调用Windows API函数(如`FindWindow`和`ShowWindow`)来定位任务栏窗口并改变其可见性状态。 4. 自动化工具:此外,...
在这种情况下,可能是创建一个无窗口的托盘应用,将图标固定在任务栏通知区域,并处理用户的鼠标点击事件。 3. 实时数据更新:通过定时器(`System.Timers.Timer`或`System.Windows.Forms.Timer`)定期检查CPU使用...
总的来说,利用C#和Windows API实现任务栏弹出窗体的效果,需要对Windows编程有深入的理解,包括窗口管理、API调用以及资源管理等方面的知识。通过这种方式,你可以创建出具有专业特性的应用程序,提升用户体验。
总的来说,创建停放在任务栏上的图标程序涉及对Windows API的理解和C#语言的运用,而`System.Windows.Forms.NotifyIcon`类提供了方便的接口来实现这一目标。熟练掌握这些知识,可以帮助开发者创建更符合用户需求的...
综上所述,`WimForm`是一个利用C#实现的具有动画效果的窗口,它可能包含了淡入淡出、滑动以及系统托盘图标闪烁等功能。通过巧妙地运用`Timer`控件、`Opacity`属性、窗口位置和大小的变化,以及`NotifyIcon`的`Flash`...
本文将深入解析如何利用C#.NET来构建这样一个弹出窗口,并确保其在各种屏幕配置下都能正确显示,特别是当任务栏高度变化时。 ### 关键知识点 #### 1. 窗口属性设置 为了创建一个右下角弹出窗口,首先需要创建一个...
6. **运行和测试**:编译并运行项目,你将看到任务栏通知区域有一个新的图标。点击图标或触发其他事件,可以观察到预设的行为。 通过以上步骤,初学者可以理解并实现基本的任务栏提醒功能。进一步学习,可以探索更...