我的 WinClock 项目系列之二 (功能细节,在Windows API 里面查找需要的功能)
1. 不规则窗口的创建
方法一:
让图片的背景色与显示部分的颜色明显不同,将 FormBorderStyle 属性设置为 None。
将窗体的 BackgroundImage 属性设置为先前创建的位图文件。 设置窗体的 BackColor 图片
背景色,在窗体的构造函数里添加 this.TransparencyKey = this.BackColor; 一切OK。
缺点:1) 不能胜任24位色以上环境。实际上,即使16色的环境,效果也不理想,图片边缘的阴影
显示为窗体背景。不可能对图片进行任意放大。
2) 图片边缘锯齿明显。
方法二:
采用无Alpha通道的位图图片,通过扫描图片的每一点,取出与边缘颜色不同的所以像素,合并到
GraphicsPath中,然后使用这个 GraphicsPath 创建一个 Region并赋给窗体。代码如下:
public static class WindowsRegionService {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事件了,如果你这样做,你
会发现是没有效果的。所以干脆把相关的东西先列出来吧,这里面可能有一些东西跟这个主题无关,但是也不删除了:
// PInvokeService.cs2
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 的操作,如下:
// WindowShapeMaker.cs2
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 bitmap42
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 Members108
}指针一般要启用反锯齿,因为除了水平或者垂直的线段外,不启用反锯齿的话效果是相当差的。如下:
public class ClockHand {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; // Antialias27

28
// Draw second hand29
handAngle = (float)(DateTime.Now.Second * Math.PI / 30f); // Angle30
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 hand36
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 hand45
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 类保存的是应用程序的设置,如下:
[Serializable()]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 saved116
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 Members182
}两个方法,这次先不讲这个内容。Properties 类也有些复杂,它和IMementoCapable合起来是持久化存储的基础,
实现比.Net序列化更为灵活的持久化存储方式。熟悉SharpDevelop的朋友可能比较清楚,这也不是本次要讨论
的内容。
2. 总在最前
这个比较简单,直接设置 Form 的 TopMost 属性即可。
3.使用鼠标移动钟面。
方法一:消息方式:
public class MainForm : Form {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 code22
} 缺点是不易控制窗体移动的范围,因此不能提供钟面只在屏幕范围内活动的选项。没有采用这种方法。
方法二:重载 OnMouseDown 和 OnMouseMove(这是最后采用的方法):
public class MainForm : Form, IMementoCapable {2
private ClockOption clockOpt;3
private Point mousePosition;4
// Other fileds5
6
public MainForm(ClockOption clockOpt) {7
this.clockOpt = clockOpt;8
this.mousePosition = Point.Empty;9
// Other code10
}11
12
// Other code13

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 desktop34
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 code57
}clockOpt.CheckBounds 表示是否要检查屏幕边界,即是否只允许在屏幕范围内移动钟面。
4.鼠标穿透
// PenetrateService.cs2
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 dwFlags14
);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+做不规则透明窗体


浙公网安备 33010602011771号