zoukankan      html  css  js  c++  java
  • 我的 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+做不规则透明窗体
    这是微软技术的一贯特点,使用简单。但是如果要深入的话,还是要投入不少精力的
  • 相关阅读:
    PAT (Advanced Level) 1010. Radix (25)
    PAT (Advanced Level) 1009. Product of Polynomials (25)
    PAT (Advanced Level) 1008. Elevator (20)
    PAT (Advanced Level) 1007. Maximum Subsequence Sum (25)
    PAT (Advanced Level) 1006. Sign In and Sign Out (25)
    PAT (Advanced Level) 1005. Spell It Right (20)
    PAT (Advanced Level) 1004. Counting Leaves (30)
    PAT (Advanced Level) 1001. A+B Format (20)
    PAT (Advanced Level) 1002. A+B for Polynomials (25)
    PAT (Advanced Level) 1003. Emergency (25)
  • 原文地址:https://www.cnblogs.com/cxd4321/p/1211835.html
Copyright © 2011-2022 走看看