我的 WinClock 项目系列之二 (功能细节,在Windows API 里面查找需要的功能)
1. 不规则窗口的创建
方法一:
让图片的背景色与显示部分的颜色明显不同,将 FormBorderStyle 属性设置为 None。
将窗体的 BackgroundImage 属性设置为先前创建的位图文件。 设置窗体的 BackColor 图片
背景色,在窗体的构造函数里添加 this.TransparencyKey = this.BackColor; 一切OK。
缺点:1) 不能胜任24位色以上环境。实际上,即使16色的环境,效果也不理想,图片边缘的阴影
显示为窗体背景。不可能对图片进行任意放大。
2) 图片边缘锯齿明显。
方法二:
采用无Alpha通道的位图图片,通过扫描图片的每一点,取出与边缘颜色不同的所以像素,合并到
GraphicsPath中,然后使用这个 GraphicsPath 创建一个 Region并赋给窗体。代码如下:
2 public static void SetWindowRegion(Form mainForm, Bitmap bmpBack) {
3 Color TransparentColor = bmpBack.GetPixel(1, 1);
4 SetWindowRegion(mainForm, bmpBack, TransparentColor);
5 }
6
7 private static void SetWindowRegion(Form mainForm, Bitmap bitmap, Color transparentColor) {
8 mainForm.FormBorderStyle = FormBorderStyle.None;
9 mainForm.BackgroundImageLayout = ImageLayout.None;
10 mainForm.SetBounds(mainForm.Location.X, mainForm.Location.Y, bitmap.Width, bitmap.Height);
11 mainForm.BackgroundImage = bitmap;
12
13 int width = bitmap.Width;
14 int height = bitmap.Height;
15 GraphicsPath gp = new GraphicsPath();
16 for (int y = 0; y < height; ++y) {
17 for (int x = 0; x < width; ++x) {
18 if (bitmap.GetPixel(x, y) != transparentColor) {
19 int x0 = x;
20 while (++x < width && bitmap.GetPixel(x, y) != transparentColor) {
21 }
22 Rectangle rect = new Rectangle(x0, y, x - x0, 1);
23 gp.AddRectangle(rect);
24 }
25 }
26 }
27 mainForm.Region = new Region(gp);
28 }
29 }
的图片也是不可能的。
方法三(最优解):
这种方法是这个软件最后采用的方法。主要利用 Win32 API 函数 UpdateLayeredWindow 来完成。听起来很简单的
样子,实际上要做的工作是不少的。首先要设置 Window 的ExStyle支持 WS_EX_LAYERED,这可以通过 GetWindowLog
和 SetWindowLong API实现,也可以重载 Form 的 CreateParams 属性。如下:
protected override CreateParams CreateParams {
get {
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= PInvokeService.WS_EX_LAYERED;
return createParams;
}
}
其中 PInvokeService.WS_EX_LAYERED 的值是 0x80000
UpdateLayeredWindow API 也比较复杂,在 C#里调用也不方便,所以还是写在一个 class 里面吧,另外还要绘制
时钟的指针和其他一些东西,这个是不能在 直接重载 Form 的 OnPaint或者处理 Paint事件了,如果你这样做,你
会发现是没有效果的。所以干脆把相关的东西先列出来吧,这里面可能有一些东西跟这个主题无关,但是也不删除了:
2 public static class PInvokeService {
3 public static readonly int SE_PRIVILEGE_ENABLED = 0x00000002;
4 public static readonly int TOKEN_QUERY = 0x00000008;
5 public static readonly int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
6 public static readonly string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
7 public static readonly int EWX_LOGOFF = 0x00000000;
8 public static readonly int EWX_SHUTDOWN = 0x00000001;
9 public static readonly int EWX_REBOOT = 0x00000002;
10 public static readonly int EWX_FORCE = 0x00000004;
11 public static readonly int EWX_POWEROFF = 0x00000008;
12 public static readonly int EWX_FORCEIFHUNG = 0x00000010;
13 public static readonly int ULW_ALPHA = 0x02;
14 public static readonly byte AC_SRC_OVER = 0x00;
15 public static readonly byte AC_SRC_ALPHA = 0x01;
16 public static readonly int WS_EX_LAYERED = 0x80000;
17
18 public static bool ShouldExitWindows = false;
19
20 [StructLayout(LayoutKind.Sequential)]
21 public struct POINT {
22 public Int32 x;
23 public Int32 y;
24
25 public POINT(Int32 x, Int32 y) {
26 this.x = x;
27 this.y = y;
28 }
29 }
30
31 [StructLayout(LayoutKind.Sequential)]
32 public struct SIZE {
33 public Int32 cx;
34 public Int32 cy;
35
36 public SIZE(Int32 cx, Int32 cy) {
37 this.cx = cx;
38 this.cy = cy;
39 }
40 }
41
42 [StructLayout(LayoutKind.Sequential, Pack = 1)]
43 public struct _BLENDFUNCTION {
44 public byte BlendOp;
45 public byte BlendFlags;
46 public byte SourceConstantAlpha;
47 public byte AlphaFormat;
48 }
49
50 [StructLayout(LayoutKind.Sequential, Pack = 1)]
51 public struct TokPriv1Luid {
52 public int Count;
53 public long Luid;
54 public int Attr;
55 }
56
57 [StructLayout(LayoutKind.Sequential)]
58 public struct MEMORY_INFO {
59 public uint dwLength;
60 public uint dwMemoryLoad;
61 public uint dwTotalPhys;
62 public uint dwAvailPhys;
63 public uint dwTotalPageFile;
64 public uint dwAvailPageFile;
65 public uint dwTotalVirtual;
66 public uint dwAvailVirtual;
67 }
68
69 [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
70 public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref POINT pptDst, ref SIZE psize,
71 IntPtr hdcSrc, ref POINT pprSrc, Int32 crKey, ref _BLENDFUNCTION pblend, Int32 dwFlags);
72
73 [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
74 public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
75
76 [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
77 public static extern IntPtr GetDC(IntPtr hWnd);
78
79 [DllImport("user32.dll", ExactSpelling = true)]
80 public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
81
82 [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
83 public static extern bool DeleteDC(IntPtr hdc);
84
85 [DllImport("gdi32.dll", ExactSpelling = true)]
86 public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
87
88 [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
89 public static extern bool DeleteObject(IntPtr hObject);
90
91 [DllImport("user32.dll", ExactSpelling = true, SetLastError = false)]
92 private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
93
94 [DllImport("user32.dll", ExactSpelling = true, SetLastError = false)]
95 public static extern IntPtr SetForegroundWindow(IntPtr hWnd);
96
97 [DllImport("user32.dll", CharSet = CharSet.Auto)]
98 public static extern uint GetWindowLong(IntPtr hwnd, int nIndex);
99
100 [DllImport("user32.dll", CharSet = CharSet.Auto)]
101 public static extern uint SetWindowLong(IntPtr hwnd, int nIndex, uint dwNewLong);
102
103 [DllImport("kernel32.dll", ExactSpelling = true)]
104 public static extern void GlobalMemoryStatus(ref MEMORY_INFO meminfo);
105
106 [DllImport("kernel32.dll", ExactSpelling = true)]
107 public static extern IntPtr GetCurrentProcess();
108
109 [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
110 public static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
111
112 [DllImport("advapi32.dll", SetLastError = true)]
113 public static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
114
115 [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
116 public static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
117 ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
118
119 [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
120 public static extern bool ExitWindowsEx(int flg, int rea);
121
122 [DllImport("CPPCode.Shutdown.dll", ExactSpelling = true, SetLastError = true)]
123 public static extern void ShowShutdownDialog();
124
125 public static void DoExitWin(int flg) {
126 bool ok;
127 TokPriv1Luid tp;
128 IntPtr hproc = GetCurrentProcess();
129 IntPtr htok = IntPtr.Zero;
130 ok = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
131 tp.Count = 1;
132 tp.Luid = 0;
133 tp.Attr = SE_PRIVILEGE_ENABLED;
134 ok = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid);
135 ok = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
136 ok = ExitWindowsEx(flg, 0);
137 }
138 }
一次,以刷新时间。图片是具有 Alpha 通过的 32bpp bitmap, 一般为 PNG 格式。在刷新前,首先
处理这个图片,将传入的图片做一个拷贝,这样一方便是可以根据程序设置缩放图片,一方面是保证
源图片不会被更改。从这个图片创建一个 Graphics 对象,然后在上面画出指针以及其他必要的内容,
最后调用 UpdateLayeredWindow 更新窗体。这里面用到很多 GDI 的操作,如下:
2 public class WindowShapeMaker : IDisposable {
3 private Form mainForm;
4 private ClockOption clockOpt;
5 private ClockHand clockHand;
6
7 public WindowShapeMaker(Form mainForm, ClockOption clockOpt) {
8 this.mainForm = mainForm;
9 this.clockOpt = clockOpt;
10 this.clockHand = new ClockHand(mainForm.ClientSize);
11 this.mainForm.SizeChanged += MainFormOnSizeChanged;
12 }
13
14 ~WindowShapeMaker() {
15 Dispose(false);
16 }
17
18 public void RefreshWindow(Bitmap bitmap) {
19 IntPtr screenDc = PInvokeService.GetDC(IntPtr.Zero);
20 IntPtr memDc = PInvokeService.CreateCompatibleDC(screenDc);
21 IntPtr hBitmap = IntPtr.Zero;
22 IntPtr hOldBitmap = IntPtr.Zero;
23
24 try {
25 int bitmapWidth = (int)(bitmap.Width * clockOpt.SizeFactor);
26 int bitmapHeight = (int)(bitmap.Height * clockOpt.SizeFactor);
27
28 bitmap = new Bitmap(bitmap, new Size(bitmapWidth, bitmapHeight));
29 mainForm.ClientSize = bitmap.Size;
30 using (Graphics g = Graphics.FromImage(bitmap)) {
31 Draw(g);
32 }
33
34 hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
35 hOldBitmap = PInvokeService.SelectObject(memDc, hBitmap);
36
37 PInvokeService.SIZE newSize = new PInvokeService.SIZE(bitmap.Width, bitmap.Height);
38 PInvokeService.POINT sourceLocation = new PInvokeService.POINT(0, 0);
39 PInvokeService.POINT newLocation = new PInvokeService.POINT(mainForm.Location.X, mainForm.Location.Y);
40 PInvokeService._BLENDFUNCTION blend = new PInvokeService._BLENDFUNCTION();
41 blend.BlendOp = PInvokeService.AC_SRC_OVER; // Only works with a 32bpp bitmap
42 blend.BlendFlags = 0;
43 blend.SourceConstantAlpha = clockOpt.PreviewOpacity;
44 blend.AlphaFormat = PInvokeService.AC_SRC_ALPHA;
45
46 PInvokeService.UpdateLayeredWindow(mainForm.Handle, screenDc, ref newLocation, ref newSize,
47 memDc, ref sourceLocation, 0, ref blend, PInvokeService.ULW_ALPHA);
48 } finally {
49 PInvokeService.ReleaseDC(IntPtr.Zero, screenDc);
50 if (hBitmap != IntPtr.Zero) {
51 PInvokeService.SelectObject(memDc, hOldBitmap);
52 PInvokeService.DeleteObject(hBitmap);
53 }
54 PInvokeService.DeleteDC(memDc);
55 bitmap.Dispose();
56 }
57 }
58
59 protected virtual void Dispose(bool disposing) {
60 if (disposing) {
61 this.mainForm.SizeChanged -= MainFormOnSizeChanged;
62 }
63 }
64
65 private void Draw(Graphics g) {
66 StringFormat format = new StringFormat();
67 format.Alignment = StringAlignment.Center;
68 format.LineAlignment = StringAlignment.Center;
69 int width = mainForm.ClientSize.Width;
70 int height = mainForm.ClientSize.Height / 7;
71
72 if (clockOpt.ShowDate) {
73 Rectangle rect = new Rectangle(0, height * 2, width, height);
74 g.DrawString(DateTime.Now.ToString("yyyy-MM-dd"), mainForm.Font, Brushes.Black, rect, format);
75 }
76
77 if (clockOpt.ShowAmPm) {
78 float fltX = clockHand.CentrePointF.X - 8;
79 float fltY = clockHand.CentrePointF.Y + 18;
80 string strAMPM = string.Empty;
81 if (DateTime.Now.Hour > 12) {
82 strAMPM = "PM";
83 } else {
84 strAMPM = "AM";
85 }
86
87 Rectangle rect = new Rectangle(0, height * 4, width, height);
88 g.DrawString(strAMPM, mainForm.Font, Brushes.Black, rect, format);
89 }
90
91 clockHand[0] = (int)(ClockHand.SECONDLENTH * clockOpt.SizeFactor);
92 clockHand[1] = (int)(ClockHand.MIMUTELENTH * clockOpt.SizeFactor);
93 clockHand[2] = (int)(ClockHand.HOURLENTH * clockOpt.SizeFactor);
94 clockHand.DrawClockHand(g);
95 }
96
97 private void MainFormOnSizeChanged(object sender, EventArgs args) {
98 this.clockHand.CentrePointF = new PointF(mainForm.ClientSize.Width / 2f, mainForm.ClientSize.Height / 2f);
99 }
100
101 IDisposable Members
108 }
指针一般要启用反锯齿,因为除了水平或者垂直的线段外,不启用反锯齿的话效果是相当差的。如下:
2 private float[] handLength;
3 private PointF centrePointF;
4
5 public static readonly float SECONDLENTH = 48f;
6 public static readonly float MIMUTELENTH = 40f;
7 public static readonly float HOURLENTH = 30f;
8 public static readonly int Size = 128;
9
10 public ClockHand(Size clientSize) {
11 double factor = (double)clientSize.Width / Size;
12 handLength = new float[] {
13 (int)(SECONDLENTH * factor),
14 (int)(MIMUTELENTH * factor),
15 (int)(HOURLENTH * factor)
16 };
17 centrePointF = new PointF(clientSize.Width / 2f, clientSize.Height / 2f);
18 }
19
20 public void DrawClockHand(Graphics graphics) {
21 float handAngle;
22 using (Pen pen = new Pen(Color.Red, 0.1f)) {
23 pen.EndCap = LineCap.Round;
24
25 SmoothingMode savedMode = graphics.SmoothingMode;
26 graphics.SmoothingMode = SmoothingMode.AntiAlias; // Antialias
27
28 // Draw second hand
29 handAngle = (float)(DateTime.Now.Second * Math.PI / 30f); // Angle
30 PointF endPointF = new PointF(CentrePointF.X + (float)(handLength[0] * Math.Sin(handAngle)),
31 CentrePointF.Y - (float)(handLength[0] * Math.Cos(handAngle)));
32
33 graphics.DrawLine(pen, CentrePointF, endPointF);
34
35 // Draw minute hand
36 handAngle = (float)(DateTime.Now.Minute * Math.PI / 30f);
37 endPointF = new PointF(CentrePointF.X + (float)(handLength[1] * Math.Sin(handAngle)),
38 CentrePointF.Y - (float)(handLength[1] * Math.Cos(handAngle)));
39
40 pen.Color = Color.Blue;
41 pen.Width = 1.2f;
42 graphics.DrawLine(pen, CentrePointF, endPointF);
43
44 // Draw hour hand
45 handAngle = (float)((DateTime.Now.Hour + DateTime.Now.Minute / 60f) * Math.PI / 6f);
46 endPointF = new PointF(CentrePointF.X + (float)(handLength[2] * Math.Sin(handAngle)),
47 CentrePointF.Y - (float)(handLength[2] * Math.Cos(handAngle)));
48
49 pen.Width = 2f;
50 graphics.DrawLine(pen, CentrePointF, endPointF);
51
52 graphics.SmoothingMode = savedMode;
53 }
54 }
55
56 public float this[int index] {
57 get {
58 if (index >= 0 && index <= 2) {
59 return handLength[index];
60 }
61
62 return -1;
63 }
64 set {
65 if (index >= 0 && index <= 2) {
66 handLength[index] = value;
67 } else {
68 throw new IndexOutOfRangeException();
69 }
70 }
71 }
72
73 public PointF CentrePointF {
74 get {
75 return centrePointF;
76 }
77 set {
78 centrePointF = value;
79 }
80 }
81 }
上面的 ClockOption 类保存的是应用程序的设置,如下:
2 public class ClockOption : IMementoCapable {
3 [NonSerialized()]
4 public static readonly string AppPath;
5
6 private bool canMove = true;
7 private bool showAmPm = false;
8 private bool showDate = false;
9 private bool penetrate = false;
10 private bool haveRemind = false;
11 private bool checkBounds = true;
12 private string filename = "default.bmp";
13 private byte mouseEnterOpacity = 255;
14 private byte opacity = 255;
15 private double sizeFactor = 1.0;
16 private Point location = new Point(100, 100);
17 private byte previewOpacity = 255;
18 private string language = "en-US";
19
20 static ClockOption() {
21 AppPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
22 }
23
24 public bool CanMove {
25 get {
26 return this.canMove;
27 }
28 set {
29 this.canMove = value;
30 }
31 }
32
33 public bool ShowAmPm {
34 get {
35 return this.showAmPm;
36 }
37 set {
38 this.showAmPm = value;
39 }
40
41 }
42
43 public bool ShowDate {
44 get {
45 return this.showDate;
46 }
47 set {
48 this.showDate = value;
49 }
50 }
51
52 public bool Penetrate {
53 get {
54 return this.penetrate;
55 }
56 set {
57 this.penetrate = value;
58 }
59 }
60
61 public bool HaveRemind {
62 get {
63 return this.haveRemind;
64 }
65 set {
66 this.haveRemind = value;
67 }
68 }
69
70 public bool CheckBounds {
71 get {
72 return this.checkBounds;
73 }
74 set {
75 this.checkBounds = value;
76 }
77 }
78
79 public string Filename {
80 get {
81 return this.filename;
82 }
83 set {
84 this.filename = value;
85 }
86 }
87
88 public byte MouseEnterOpacity {
89 get {
90 return this.mouseEnterOpacity;
91 }
92 set {
93 this.mouseEnterOpacity = value;
94 }
95 }
96
97 public byte Opacity {
98 get {
99 return this.opacity;
100 }
101 set {
102 this.opacity = value;
103 }
104 }
105
106 public double SizeFactor {
107 get {
108 return this.sizeFactor;
109 }
110 set {
111 this.sizeFactor = value;
112 }
113 }
114
115 // This two properties are not saved
116 public Point Location {
117 get {
118 return this.location;
119 }
120 set {
121 this.location = value;
122 }
123 }
124
125 public byte PreviewOpacity {
126 get {
127 return this.previewOpacity;
128 }
129 set {
130 this.previewOpacity = value;
131 }
132 }
133
134 public string Language {
135 get {
136 return this.language;
137 }
138 set {
139 this.language = value;
140 }
141 }
142
143 public ClockOption() {
144 }
145
146 IMementoCapable Members
182 }
两个方法,这次先不讲这个内容。Properties 类也有些复杂,它和IMementoCapable合起来是持久化存储的基础,
实现比.Net序列化更为灵活的持久化存储方式。熟悉SharpDevelop的朋友可能比较清楚,这也不是本次要讨论
的内容。
2. 总在最前
这个比较简单,直接设置 Form 的 TopMost 属性即可。
3.使用鼠标移动钟面。
方法一:消息方式:
2 private static readonly int WM_SYSCOMMAND = 0x112;
3 private static readonly int SC_MOVE = 0xF010;
4 private static readonly int HTCAPTION = 0x2;
5
6 protected override void OnMouseDown(MouseEventArgs args) {
7 this.Capture = false;
8 MoveTheWindow();
9
10 base.OnMouseDown(args);
11 }
12
13 private void MoveTheWindow() {
14 Message m = new Message();
15 m.HWnd = this.Handle;
16 m.Msg = WM_SYSCOMMAND;
17 m.WParam = new IntPtr(SC_MOVE | HTCAPTION);
18 this.WndProc(ref m);
19 }
20
21 // Other code
22 }
缺点是不易控制窗体移动的范围,因此不能提供钟面只在屏幕范围内活动的选项。没有采用这种方法。
方法二:重载 OnMouseDown 和 OnMouseMove(这是最后采用的方法):
2 private ClockOption clockOpt;
3 private Point mousePosition;
4 // Other fileds
5
6 public MainForm(ClockOption clockOpt) {
7 this.clockOpt = clockOpt;
8 this.mousePosition = Point.Empty;
9 // Other code
10 }
11
12 // Other code
13
14 internal void CheckBounds(ref Point location) {
15 if (clockOpt.CheckBounds) {
16 Rectangle rectScreen = Screen.GetWorkingArea(this);
17 if (location.X < rectScreen.Left) {
18 location.X = rectScreen.Left;
19 } else if (location.X + this.ClientSize.Width > rectScreen.Right) {
20 location.X = rectScreen.Right - this.ClientSize.Width;
21 }
22
23 if (location.Y < rectScreen.Top) {
24 location.Y = rectScreen.Top;
25 } else if (location.Y + this.ClientSize.Height > rectScreen.Bottom) {
26 location.Y = rectScreen.Bottom - this.ClientSize.Height;
27 }
28 }
29 }
30
31 protected override void OnMouseMove(MouseEventArgs e) {
32 if (e.Button == MouseButtons.Left) {
33 // The clock is fixed up on the desktop
34 if (!clockOpt.CanMove)
35 return;
36
37 int left = this.Location.X + e.Location.X - this.mousePosition.X;
38 int top = this.Location.Y + e.Location.Y - this.mousePosition.Y;
39 Point location = new Point(left, top);
40 CheckBounds(ref location);
41 this.SetBounds(location.X, location.Y, this.ClientSize.Width, this.ClientSize.Height);
42 clockOpt.Location = this.Location;
43 }
44
45 base.OnMouseMove(e);
46 }
47
48 protected override void OnMouseDown(MouseEventArgs e) {
49 if (e.Button == MouseButtons.Left) {
50 this.mousePosition = e.Location;
51 }
52
53 base.OnMouseDown(e);
54 }
55
56 // Other code
57 }
clockOpt.CheckBounds 表示是否要检查屏幕边界,即是否只允许在屏幕范围内移动钟面。
4.鼠标穿透
2 public static class PenetrateService {
3 private static readonly uint WS_EX_LAYERED = 0x80000;
4 private static readonly uint WS_EX_TRANSPARENT = 0x20;
5 private static readonly int GWL_EXSTYLE = -20;
6 //private static readonly int LWA_ALPHA = 0x2;
7
8 [DllImport("user32", EntryPoint = "SetLayeredWindowAttributes")]
9 private static extern int SetLayeredWindowAttributes(
10 IntPtr hwnd,
11 int crKey,
12 int bAlpha,
13 int dwFlags
14 );
15
16 public static void MousePenetrate(Form mainForm, byte alpha) {
17 uint intExTemp = PInvokeService.GetWindowLong(mainForm.Handle, GWL_EXSTYLE);
18 PInvokeService.SetWindowLong(mainForm.Handle, GWL_EXSTYLE, intExTemp | WS_EX_TRANSPARENT | WS_EX_LAYERED);
19 //SetLayeredWindowAttributes(mainForm.Handle, 0, alpha, LWA_ALPHA);
20 }
21
22 public static void MouseNotPenetrate(Form mainForm, byte alpha) {
23 PInvokeService.SetWindowLong(mainForm.Handle, GWL_EXSTYLE, WS_EX_LAYERED);
24 //SetLayeredWindowAttributes(mainForm.Handle, 0, alpha, LWA_ALPHA);
25 }
26 }
注释掉的几行代码是有原因的,在设置了窗体的 WS_EX_LAYERED Style 以后,不能再要这两句,否则这个 Style 失去作用。
如果没有采用这种方式,则需要加上这两句代码。
5. 窗体透明度
你可能最快想到的是直接设置 Form的 Opacity 属性,但是在这里他失效了,不但不起作用,还会使WS_EX_LAYERED失效。
其实在 UpdateLayeredWindow 的调用中,就有透明度的选项的。那句
blend.SourceConstantAlpha = clockOpt.PreviewOpacity;
正是这个作用。由于要支持鼠标经过时的透明度和 正常的透明度,所以ClockOption 里面还有 PreviewOpacity 这个属性。
最后补充一点,今天对源代码做了一些修改,今天添加了多国语言支持, 添加了中文资源,修正了农历算法问题. 添加了对允许
拖动到屏幕以外的选项. Fix了一些小的Bug. 如果你感兴趣,可以重新下载。
好了,至此这次写的也差不多了,好累, 不知道有没有漏写什么东西,唉, 时间也不早了,休息吧^_^。
参考资料:
C# winform中不规则窗体制作的解决方案(已经解决24位色以上不能正常显示问题)用PNG透明图片和GDI+做不规则透明窗体