我的 WinClock 项目系列之二 (功能细节,在Windows API 里面查找需要的功能)

1. 不规则窗口的创建

    方法一:
    让图片的背景色与显示部分的颜色明显不同,将 FormBorderStyle 属性设置为 None。
    将窗体的 BackgroundImage 属性设置为先前创建的位图文件。 设置窗体的 BackColor 图片
    背景色,在窗体的构造函数里添加 this.TransparencyKey = this.BackColor; 一切OK。
   
    缺点:1) 不能胜任24位色以上环境。实际上,即使16色的环境,效果也不理想,图片边缘的阴影
             显示为窗体背景。不可能对图片进行任意放大。
          2) 图片边缘锯齿明显。
         
    方法二:
    采用无Alpha通道的位图图片,通过扫描图片的每一点,取出与边缘颜色不同的所以像素,合并到
    GraphicsPath中,然后使用这个 GraphicsPath 创建一个 Region并赋给窗体。代码如下:

 1    public static class WindowsRegionService {
 2        public static void SetWindowRegion(Form mainForm, Bitmap bmpBack) {
 3            Color TransparentColor = bmpBack.GetPixel(11);
 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 (++< 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    }
    这种方法除了可以解决方法一中的不胜任24色以上环境的问题外,与方法一的缺点是一样的。想使用带有阴影
    的图片也是不可能的。
   
    方法三(最优解):
    这种方法是这个软件最后采用的方法。主要利用 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事件了,如果你这样做,你
    会发现是没有效果的。所以干脆把相关的东西先列出来吧,这里面可能有一些东西跟这个主题无关,但是也不删除了:

  1    // PInvokeService.cs
  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, falseref tp, 0, IntPtr.Zero, IntPtr.Zero);
136            ok = ExitWindowsEx(flg, 0);
137        }

138    }
    下面的 WindowShapeMaker 类负责窗体形状的创建,实际中,RefreshWindow每隔一秒钟就会调用
    一次,以刷新时间。图片是具有 Alpha 通过的 32bpp bitmap, 一般为 PNG 格式。在刷新前,首先
    处理这个图片,将传入的图片做一个拷贝,这样一方便是可以根据程序设置缩放图片,一方面是保证
    源图片不会被更改。从这个图片创建一个 Graphics 对象,然后在上面画出指针以及其他必要的内容,
    最后调用 UpdateLayeredWindow 更新窗体。这里面用到很多 GDI 的操作,如下:

  1    // WindowShapeMaker.cs
  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(00);
 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, 0ref 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    }
    上面用到的 ClockHand 类专门负责绘制时钟的指针,我们假定所有的背景图片都是对称图形,也就是中心一定在图片中心。
    指针一般要启用反锯齿,因为除了水平或者垂直的线段外,不启用反锯齿的话效果是相当差的。如下:
 1    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;            // 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 类保存的是应用程序的设置,如下:
  1    [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(100100);
 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    }
    IMementoCapable 接口是很明显是一个备忘录,有 CreateMemento() 和 SetMemento(Properties properties)
    两个方法,这次先不讲这个内容。Properties 类也有些复杂,它和IMementoCapable合起来是持久化存储的基础,
    实现比.Net序列化更为灵活的持久化存储方式。熟悉SharpDevelop的朋友可能比较清楚,这也不是本次要讨论
    的内容。

2. 总在最前
    这个比较简单,直接设置 Form 的 TopMost 属性即可。

3.使用鼠标移动钟面。
   方法一:消息方式:

 1   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 code
22    }

   缺点是不易控制窗体移动的范围,因此不能提供钟面只在屏幕范围内活动的选项。没有采用这种方法。
  
   方法二:重载 OnMouseDown 和 OnMouseMove(这是最后采用的方法):

 1       public class MainForm : Form, IMementoCapable {
 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.鼠标穿透

 1    // PenetrateService.cs
 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+做不规则透明窗体

posted on 2008-06-01 23:47  优哉@游哉  阅读(3873)  评论(6编辑  收藏  举报

导航