调用api函数,在窗口非客户区绘图
gdi+的graphics类里有个fromhdc函数,这个函数可以根据窗口设备上下文(dc)创建graphics对象,在vc++中,窗口客户区与非客户区的绘图无非就是getwindowdc和getdc函数的不同调用。前者获得整个窗口dc,后者获得窗口客户区dc。
那么我们就可以在c#里,调用getwindowdc函数获取整个窗口dc,然后通过fromhdc加载进去,这样我们就能针对整个窗口绘图了。
c#要如何调用windows api呢,或者说如何调用动态链接库(dll)里的函数。
跟vc++的大同小异,先导入动态链接库,然后再声明api函数,如下:
[system.runtime.interopservices.dllimport(user32.dll)]
private static extern intptr getwindowdc(intptr hwnd);
当然上面是最简单的,还有一些细节没有讲,先就这样吧,会基本使用就行了,那些细节问题以后再详细说明。
在c#中,我们发现api函数的参数类型都不一样了,比如在vc++中的句柄hdc,hwnd。在这里声明时,都用了intptr代替,这是没有办法的事,因为c#没有指针这个概念,而我们通过查hdc,和hwnd类型定义时发现,它们都是指针类型。
所以在c#中,这些“句柄”类型都用intptr代替,包括区域句柄hrgn,hicon图标,hfont字体句柄等。
看一个示例吧,(接着上一章的)
public partial class form1 : form
{
//导入动态链接库,声明函数,这个函数是声明在form1类里的。
[system.runtime.interopservices.dllimport(user32.dll)]
private static extern intptr getwindowdc(intptr hwnd);
//存储png非透明部分的路径
private graphicspath path = new graphicspath();
//加载png图片
bitmap bmp = new bitmap(d:\\image\\win.png);
public form1()
{
initializecomponent();
//判断每个像素的颜色值,获取图片的显示区域
for (int y = 0; y < bmp.height; y++)
for (int x = 0; x < bmp.width; x++)
{
color cor = bmp.getpixel(x, y);
int argb = cor.toargb();
byte[] bargb = bitconverter.getbytes(argb);
//像素颜色值不是透明的
if (bargb[3] != 0)
{
//把这个像素点区域添加到路径里去
path.addrectangle(new rectangle(x, y, 1, 1));
}
}
//设置窗口显示区域,通过路径创建区域
this.region = new region(path);
this.paint += formpaint;
}
private void formpaint(object sender, painteventargs e)
{
onpaintbackground(e);
//handle是窗口句柄,它是一个intptr类型
intptr hdc = getwindowdc(this.handle);
//根据窗口dc创建graphics对象
graphics gr = graphics.fromhdc(hdc);
//绘制图片
gr.drawimage(bmp, new rectangle(0, 0, bmp.width, bmp.height));
}
protected override void onpaintbackground(painteventargs e)
{
//透明画刷填充
//base.onpaintbackground(e);
e.graphics.fillrectangle(brushes.transparent, this.clientrectangle);
}
}
怎么样,效果不错吧,但一拖动窗口就原形毕露了,注意到苹果下方的阴影了么,就是为了实现这个效果才会带来一些问题,或者说麻烦了许多吧。只是我没去解决。移动窗口,或者最大化窗口,都没有完全刷新整个窗口,才会导致这种问题出现。这个问题留待以后解决吧,
在兴趣的朋友也可以去解决一下这个问题。
另外,我用透明画刷填充的只是窗口的客户区,如果想填充整个窗口(包括标题栏),方法跟在整个窗口绘图一样,获得windowdc,然后
创建graphics对象,绘制窗口背景。
(题外话:在vc++中,客户区与非客户区有着不同的重绘消息,wm_paint和wm_ncpaint,这一点要注意了,在刷新非客户区的时候,别重绘客户区,虽说不会出什么问题,但影响了效率总是不好的,能避免就避免)
自绘窗口非客户区(包括标题栏,最大,最小化,关闭按钮)
重写消息处理函数wndproc
public partial class form1 : form
{
public form1()
{
initializecomponent();
}
protected override void wndproc(ref message m)
{
if (m.msg == 0xa3)//wm_nclbuttondblclk 双击标题消息
messagebox.show(你双击了标题栏);
//默认消息处理
base.wndproc(ref m);
}
}
这样双击标题栏的时候就会给出一个提示,然后再默认处理。
查消息对应的数值,可以到vc++编译器里去查,比如打上wm_lbuttondown然后右击,选择转到定义就可以查看了。
m.hwnd存储有窗口句柄,m.lparam和m.wparam是消息的附带信息,可以参考createwindow函数里的wparam和lparam参数解释。
自绘非客户区工作量实在是太大了,这里我只给个大概的思路,方向,以后有空再来做吧。
前提当然是把各项数据计算出来,比如窗口有无边框,如果有的话,获取边框宽度,高度,然后计算四个边框的矩形区域。
最后就判断窗口有无最大,最大小化属性,然后获得三个按钮的区域。
而systeminformation类里就存储有这些数据,比如systeminformation.captionbuttonsize存储有标题栏按钮的大小,得到了大小,就可以
确定按钮的区域了,因为这三个按钮都在窗口的右上角,除去边框的高宽。
而systeminformation.captionheight存储有标题栏的高度,边框的高宽存储在systeminformation.bordersize或者systeminformation.border3dsize,这个根据窗口的formborderstyle决定。窗口的是否处于最大化可以判断maximizebox,为true最大化。
得到了上面那些数据,就响应非客户区的各种消息,如鼠标左键消息wm_nclbuttondown和wm_nclbuttonup。
鼠标移动消息wm_ncmousemove,接着就开始自绘了。
另rectangle类里的contains函数,可以判断一个点是否在一个矩形区域内。
更多c# gdi+编程(五)。