做.NET WinForm的开发也有一段时间了,对.NET的界面设计也有了一定的了解。自认为自己学的这些东西都是网上看高手们的东西后总结出来的,第一次把这些东西写出来。

不好的地方请大家多多指教。

    大家先看一下做出来的效果吧!

 

网上也看过很多做.NET窗体的例子,我只是把网上的这些东西综合了一下,主要有下面这些特点:

1、边框是半透明的,透明度可根据需要自己更改;

2、可以改变窗体的大小,改变后样式不变;

3、窗体的边框是不规则的;

4、重点解决了窗体会出现闪烁的问题,在窗体移动的时候也不会闪烁;

5、使用方便,只要将AlphaFormPanel拖动到一般的窗体上就可以实现换肤;

 

设计思路说明:

一、.NET下处理一个窗体部分透明我所知道的有两种方法:

      1、用一张支持Alhpa通道的图片来处理半透明,这种方式处理出来的效果会很好,甚至可以用一张动态的图片来做背景。

           相信有人看过那个游动的鱼的程序,鱼的边缘是半透明的,就是用这种方式做的。这种方式整个窗体都是通过UpdateLayeredWindow画

           出来的,如果要在上面加控件的话,所有的控件都要自己来绘制,显然在具体的项目中用这种方式的话会大大增加开发的

           难度。有兴趣的人可以看看这个程序:

          /Files/liutao409/游动的鱼.rar

          关键的代码就是根据这种支持Alhpa通道的图片来绘制窗体

              [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
              public static extern int UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize,

                                      IntPtr hdcSrc, ref Point pptSrc, Int32 crKey, ref  BLENDFUNCTION pblend, Int32 dwFlags);

         public void SetBits(Bitmap bitmap)
        {
            if (!haveHandle) return;

            if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat))
                throw new ApplicationException("图片必须是32位带Alhpa通道的图片。");

            IntPtr oldBits = IntPtr.Zero;
            IntPtr screenDC = Win32.GetDC(IntPtr.Zero);
            IntPtr hBitmap = IntPtr.Zero;
            IntPtr memDc = Win32.CreateCompatibleDC(screenDC);

            try
            {
                Win32.Point topLoc = new Win32.Point(Left, Top);
                Win32.Size bitMapSize = new Win32.Size(bitmap.Width, bitmap.Height);
                Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION();
                Win32.Point srcLoc = new Win32.Point(0, 0);

                hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
                oldBits = Win32.SelectObject(memDc, hBitmap);

                blendFunc.BlendOp = Win32.AC_SRC_OVER;
                blendFunc.SourceConstantAlpha = 255;
                blendFunc.AlphaFormat = Win32.AC_SRC_ALPHA;
                blendFunc.BlendFlags = 0;

                Win32.UpdateLayeredWindow(Handle, screenDC, ref topLoc, ref bitMapSize, memDc, ref srcLoc, 0, ref blendFunc, Win32.ULW_ALPHA);
            }
            finally
            {
                if (hBitmap != IntPtr.Zero)
                {
                    Win32.SelectObject(memDc, oldBits);
                    Win32.DeleteObject(hBitmap);
                }
                Win32.ReleaseDC(IntPtr.Zero, screenDC);
                Win32.DeleteDC(memDc);
            }
        }

 

      2、用两个窗体来实现边框半透明

                后面的窗体用来做边框,前面的窗体放其他的控件,前面的窗体跟随后面的窗体移动和改变大小,这样就很容易的控制后面的窗体

          半透明显示。这种方式虽然麻烦一点,但是不用像第一种方式那样自己绘制所有的控件,所以还是可以在项目中使用的。我的这个

          例子用的就是这种方式实现的。

 二、窗体边框的处理

               我的窗体的边框是用图片来处理的,为了使窗体的边框在改变大小后的样式不改变,上下边框做成了3段式的,中间部分平铺,两端保持不变

          这样窗体任意缩放后样式都不会改变。如果对GDI+熟悉的话,也可以不用图片来处理,直接绘制渐变填充也可以。我重写的承载图片的PictureBox,

          让PictureBox将所有的消息都传给父窗体来处理,这样就可以由父窗体统一的处理窗体的缩放和移动了,具体的代码如下:

 

      

    public partial class PictureBoxEX : PictureBox
    {
        public delegate void delSetFormSize(int intInfo);
        public event delSetFormSize evtSetFormSize;

        #region 属性
        private bool _bTopLeft = false;
        public bool BTopLeft
        {
            get { return _bTopLeft; }
            set { _bTopLeft = value; }
        }

        private bool _bTop = false;
        public bool BTop
        {
            get { return _bTop; }
            set { _bTop = value; }
        }

        private bool _bTopRight = false;
        public bool BTopRight
        {
            get { return _bTopRight; }
            set { _bTopRight = value; }
        }

        private bool _bLeft = false;
        public bool BLeft
        {
            get { return _bLeft; }
            set { _bLeft = value; }
        }

        private bool _bBottomLeft = false;
        public bool BBottomLeft
        {
            get { return _bBottomLeft; }
            set { _bBottomLeft = value; }
        }

        private bool _bBottom = false;
        public bool BBottom
        {
            get { return _bBottom; }
            set { _bBottom = value; }
        }

        private bool _bRight = false;
        public bool BRight
        {
            get { return _bRight; }
            set { _bRight = value; }
        }

        private bool _bBottomRight = false;
        public bool BBottomRight
        {
            get { return _bBottomRight; }
            set { _bBottomRight = value; }
        }
        #endregion

        #region 构造函数
        /// <summary>
        /// 构造函数
        /// </summary>
        public PictureBoxEX()
        {
            ;
        }
        #endregion

        #region 重新鼠标消息
        const int WM_NCHITTEST = 0x0084;
        const int HTTRANSPARENT = -1;
        const int HTLEFT = 10;
        const int HTRIGHT = 11;
        const int HTTOP = 12;
        const int HTTOPLEFT = 13;
        const int HTTOPRIGHT = 14;
        const int HTBOTTOM = 15;
        const int HTBOTTOMLEFT = 0x10;
        const int HTBOTTOMRIGHT = 17;

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            switch (m.Msg)
            {
                case WM_NCHITTEST:
                    Point vPoint = new Point((int)m.LParam & 0xFFFF,
                        (int)m.LParam >> 16 & 0xFFFF);
                    vPoint = PointToClient(vPoint);
                    int intInfo = -1;
                    if (_bTopLeft)
                    {
                        if (vPoint.X <= 10)
                        {
                            intInfo = HTTOPLEFT;
                        }
                    }
                    else if (_bLeft)
                    {
                        intInfo = HTLEFT;
                    }
                    else if (_bBottomLeft)
                    {
                        intInfo = HTBOTTOMLEFT;
                    }
                    else if (_bBottom)
                    {
                        intInfo = HTBOTTOM;
                    }
                    else if (_bBottomRight)
                    {
                        intInfo = HTBOTTOMRIGHT;
                    }
                    else if (_bRight)
                    {
                        intInfo = HTRIGHT;
                    }
                    else if (_bTopRight)
                    {
                        if (vPoint.X >= ClientSize.Width - 10)
                        {
                            intInfo = HTTOPRIGHT;
                        }
                    }
                    else if (_bTop)
                    {
                        if (vPoint.Y <= 5)
                        {
                            m.Result = (IntPtr)HTTOP;
                            intInfo = HTTOP;
                        }
                    }

                    if (evtSetFormSize != null && intInfo != -1)
                    {
                        evtSetFormSize(intInfo);
                    }
                    //将消息传给父窗体来处理
                    m.Result = (IntPtr)HTTRANSPARENT;
                    break;
            }
        }
        #endregion
    }

 

关键代码说明:

一、用到的消息

        private const int WM_NCLBUTTONDBLCLK = 0x00A3;
        private int _intInfo = -1;       //消息回传值
        private const int HTLEFT = 10;
        private const int HTRIGHT = 11;
        private const int HTTOP = 12;
        private const int HTTOPLEFT = 13;
        private const int HTTOPRIGHT = 14;
        private const int HTBOTTOM = 15;
        private const int HTBOTTOMLEFT = 0x10;
        private const int HTBOTTOMRIGHT = 17;
        private const int WM_NCHITTEST = 0x84;
        private const int HTCLIENT = 0x01;
        private const int HTCAPTION = 0x02;

1、改变窗体大小和移动窗体的位置    

      所有的和边框有关的消息都传到后面的窗体的处理,承载边框的PictureBoxEx 会将传给它的系统消息忽略掉

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_NCHITTEST)
            {
                this.DefWndProc(ref m);

                //移动窗体位置
                if (m.Result.ToInt32() == HTCLIENT && this.WindowState != FormWindowState.Maximized)
                {
                    m.Result = new IntPtr(HTCAPTION);
                }
                else
                {
                    base.WndProc(ref m);
                }
                //改变窗体大小
                if (ChangeFormSize && _intInfo != -1 && this.WindowState != FormWindowState.Maximized)
                {
                    m.Result = (IntPtr)_intInfo;
                }
                _intInfo = -1;
            }
            //双击鼠标左键的消息
            else if (m.Msg == WM_NCLBUTTONDBLCLK)
            {
                if (ChangeFormSize)
                {
                    //相当于单击一次最大化按钮
                    btnMax_MouseClick(null, EventArgs.Empty);
                }
            }
            else
            {
                base.WndProc(ref m);
            }
        }

2、减少窗体闪烁       

        下面的是比较常见的减少窗体闪烁的方法

        private void SetStyles()
        {
                SetStyle(
                ControlStyles.UserPaint |
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.ResizeRedraw |
                ControlStyles.SupportsTransparentBackColor, true);
            SetStyle(ControlStyles.Selectable, false);
            UpdateStyles();
        }

       还可以设置窗体双缓存在减少闪烁

         this.DoubleBuffered = true;

       在窗体控件的创建过程中,如果控件过多的话,用下面这个函数来处理控件的创建也可以减少闪烁

    public class AvoidControlFlicker
    {
        private int _paintFrozen;
        public void FreezePainting(Control toFreezeControl, bool isToFreeze)
        {
            if (null == toFreezeControl)
                throw new ArgumentNullException("toFreezeControl");

            if (isToFreeze && toFreezeControl.IsHandleCreated && toFreezeControl.Visible)
            {
                if (0 == _paintFrozen++)
                {
                    NativeMethods.SendMessage(toFreezeControl.Handle, NativeConsts.WM_SETREDRAW, 0, 0);
                }
            }
            if (!isToFreeze)
            {
                if (0 == _paintFrozen) return;
                if (0 == --_paintFrozen)
                {
                    NativeMethods.SendMessage(toFreezeControl.Handle, NativeConsts.WM_SETREDRAW, 1, 0);
                    toFreezeControl.Invalidate(true);
                }
            }
        }
    }

3、为了给初学者提供一些帮助,其它一些处理函数也写出来

        /// <summary>
        /// 窗体位置改变
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void formStyle_LocationChanged(object sender, EventArgs e)
        {
            if (PForm != null)
            {

                //同时改变前面的子窗体的位置
                CForm.Location = new Point(this.Location.X + pbLeft.Width, this.Location.Y + panelTop.Height);
                CForm.Update();
            }
        }

 

        /// <summary>
        /// 关闭按钮单击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

       private void btnExit_MouseClick(object sender, EventArgs e)
        {
            this.Close();
        }

 

       //关闭窗体时要同时关闭窗体所在的父窗体

        protected override void OnClosing(CancelEventArgs e)
        {
            e.Cancel = true;
            base.OnClosing(e);
            Owner.Close();
        }

 

        /// <summary>
        /// 最大化按钮单击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMax_MouseClick(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Maximized)
            {           
                this.WindowState = FormWindowState.Normal;
                this.Opacity = FormOpacity;
            }
            else
            {
                this.WindowState = FormWindowState.Maximized;
                this.Opacity = 1;
            }
        }

 

        /// <summary>
        /// 最小化按钮单击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMin_MouseClick(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

 

        /// <summary>
        /// 窗体大小改变
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void formStyle_SizeChanged(object sender, EventArgs e)
        {
            if (PForm != null)
            {

                //同时改变子窗体的大小
                CForm.Size = new Size(this.Size.Width - pbLeft.Width - pbRight.Width, this.Size.Height - panelTop.Height - panelBottom.Height);
            }
        }

 

最后,提供一下这个控件以及示例代码给大家下载:

/Files/liutao409/边框半透明窗体.rar

posted on 2010-11-01 16:19  想要飞翔  阅读(8658)  评论(6编辑  收藏  举报