• 我的 WinClock 项目系列之二


    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+做不规则透明窗体
    这是微软技术的一贯特点,使用简单。但是如果要深入的话,还是要投入不少精力的
  • 相关阅读:
    C++ ORM ODB 入门(三)
    C++ ORM ODB 入门介绍(二)
    最简便的MySql数据库备份方法
    快速高效的破解MySQL本地和远程密码
    C++ ORM ODB 入门介绍(一)
    NeHe OpenGL教程 第三十九课:物理模拟
    NeHe OpenGL教程 第三十八课:资源文件
    NeHe OpenGL教程 第三十七课:卡通映射
    NeHe OpenGL教程 第三十六课:从渲染到纹理
    NeHe OpenGL教程 第三十五课:播放AVI
  • 原文地址:https://www.cnblogs.com/cxd4321/p/1211835.html
Copyright © 2020-2023  润新知