闲话休絮,进入正题。从本篇文章开始,介绍 CorePlex 的窗体皮肤机制,以及简单的换肤功能。我们先来看看效果:
换一个皮肤看看:
需要实现的是圆角窗体+四周的阴影,要实现这个,大致的思路是这样的:先使用 Graphics 绘制一个 Bitmap,将需要的皮肤绘制成一个内存图,然后使用 Win32的API:UpdateLayeredWindow 将这个构造好的 Bitmap 绘制/更新到窗体上。我们来看看具体的实现吧。
第一部分,构造皮肤背景。
为了实现圆角以及四周的阴影,我将窗体背景划分成了九宫格的形式:主要思路是:除 5 之外的其他部分,都作为窗体的边框和圆角来处理。而5这个部分,则作为圆角窗体的主体背景部分。1、3、7、9四个部分,作为圆角,我使用 PathGradientBrush 来绘制扇形渐变,而2、4、6、8四个部分,作为边框,我使用 LinearGradientBrush 来绘制线性渐变。
不多说,见代码:
///
/// 圆角区域正方形的大小
///
///
///
///
private void DrawLines(Graphics g, Size corSize, Size gradientSize_LR, Size gradientSize_TB) { Rectangle rect2 = new Rectangle(new Point(corSize.Width, 0), gradientSize_TB); Rectangle rect4 = new Rectangle(new Point(0, corSize.Width), gradientSize_LR); Rectangle rect6 = new Rectangle(new Point(this.Size.Width - this.SkinOptions.ShadowWidth, corSize.Width), gradientSize_LR); Rectangle rect8 = new Rectangle(new Point(corSize.Width, this.Size.Height - this.SkinOptions.ShadowWidth), gradientSize_TB); using ( LinearGradientBrush brush2 = new LinearGradientBrush(rect2, this.SkinOptions.ShadowColor[1], this.SkinOptions.ShadowColor[0], LinearGradientMode.Vertical), brush4 = new LinearGradientBrush(rect4, this.SkinOptions.ShadowColor[1], this.SkinOptions.ShadowColor[0], LinearGradientMode.Horizontal), brush6 = new LinearGradientBrush(rect6, this.SkinOptions.ShadowColor[0], this.SkinOptions.ShadowColor[1], LinearGradientMode.Horizontal), brush8 = new LinearGradientBrush(rect8, this.SkinOptions.ShadowColor[0], this.SkinOptions.ShadowColor[1], LinearGradientMode.Vertical) ) { g.FillRectangle(brush2, rect2); g.FillRectangle(brush4, rect4); g.FillRectangle(brush6, rect6); g.FillRectangle(brush8, rect8); } } 好,到此为止,四周的圆角和渐变边框算是模拟出来了,然后就是将 5号为止的主背景绘制上去,以及添加一些高光、暗部等线条,突出质感:
///
private void DrawMain(Graphics g) { // 要显示的区域图像的大小 Rectangle destRect = new Rectangle(0, 0, this.Width - this.SkinOptions.ShadowWidth * 2 + 1, this.Height - this.SkinOptions.ShadowWidth * 2 + 1); // 建立一个临时的 bitmap,用于存放被圆角化的图像 using (Bitmap corBg = new Bitmap(destRect.Width, destRect.Height)) { using (Graphics corG = Graphics.FromImage(corBg)) { corG.SmoothingMode = SmoothingMode.HighQuality; // 创建圆角区域 using (GraphicsPath gp = CreateRoundRect(destRect, this.SkinOptions.CornerRadius)) { Region re = new System.Drawing.Region(gp); // 设置画布区域为圆角区域 corG.IntersectClip(re); Pen p = new Pen(this.SkinOptions.BorderColor); p.Width = this.SkinOptions.BorderWidth; p.Alignment = PenAlignment.Inset; switch (this.SkinOptions.BackgroundLayout) { case ImageLayout.Center: { // 创建源图上的截取区域 Rectangle srcRect = new Rectangle(new Point(0, 0), this.SkinOptions.BackgroundImage.Size); // 绘制背景图 corG.DrawImage(this.SkinOptions.BackgroundImage, (this.Width - this.SkinOptions.BackgroundImage.Width) / 2, (this.Height - this.SkinOptions.BackgroundImage.Height) / 2, srcRect, GraphicsUnit.Pixel); } break; case ImageLayout.Stretch: { // 创建源图上的截取区域 Rectangle srcRect = new Rectangle(new Point(0, 0), this.SkinOptions.BackgroundImage.Size); // 绘制背景图 corG.DrawImage(this.SkinOptions.BackgroundImage, p.Width, p.Width, srcRect, GraphicsUnit.Pixel); } break; case ImageLayout.Tile: { // 创建源图上的截取区域 TextureBrush tb = new TextureBrush(this.SkinOptions.BackgroundImage); corG.FillRectangle(tb, destRect); } break; case ImageLayout.Zoom: { // 创′建¨源′图?上?的?截?取?区?域ò Rectangle srcRect = new Rectangle(new Point(0, 0), this.SkinOptions.BackgroundImage.Size); // 绘制背景图 corG.DrawImage(this.SkinOptions.BackgroundImage, (this.Width - this.SkinOptions.BackgroundImage.Width) / 2, (this.Height - this.SkinOptions.BackgroundImage.Height) / 2, srcRect, GraphicsUnit.Pixel); } break; case ImageLayout.None: default: corG.DrawImage(this.SkinOptions.BackgroundImage, p.Width, p.Width); break; } // 构造外边框 Rectangle borderOut = new Rectangle(0, 0, destRect.Width - 1, destRect.Height - 1); // 绘制外边框 corG.DrawPath(p, CreateRoundRect(borderOut, this.SkinOptions.CornerRadius)); // 构造内边框 Rectangle borderIn = new Rectangle(1, 1, borderOut.Width - 2, borderOut.Height - 2); using (LinearGradientBrush b = new LinearGradientBrush(borderIn, this.SkinOptions.BorderHighlightColor[0], this.SkinOptions.BorderHighlightColor[1], this.SkinOptions.BorderHighlightAngle)) { // 绘制内边框高光 using (Pen lightPen = new Pen(b)) { // 绘制内边框 corG.DrawPath(lightPen, CreateRoundRect(borderIn, this.SkinOptions.CornerRadius)); } } // 将圆角图绘制到主画布 g.DrawImage(corBg, this.SkinOptions.ShadowWidth, this.SkinOptions.ShadowWidth); #region 绘制Logo using (Bitmap logo = Resources.Default.formlogo) { Rectangle paintTo = new Rectangle(25, 20, logo.Width, logo.Height); Rectangle sourceRec = new Rectangle(0, 0, logo.Width, logo.Height); g.DrawImage(logo, paintTo, sourceRec, GraphicsUnit.Pixel); } #endregion #region 绘制控制按钮 using (Bitmap cb = Resources.Default.controlboxes) { Rectangle paintTo = new Rectangle(this.Width - this.SkinOptions.ShadowWidth - cb.Width + 3, this.SkinOptions.ShadowWidth - 1, cb.Width, ControlBoxHeight); Rectangle sourceRec = new Rectangle(0, this._currentControlBoxImgY, cb.Width, ControlBoxHeight); g.DrawImage(cb, paintTo, sourceRec, GraphicsUnit.Pixel); } #endregion } } } }
///
///
///
好了,带圆角、渐变阴影的窗体背景算是构造好了,但它现在仅仅是一张内存里的 bitmap 图片,我们要如何才能将它显示到窗体上呢?
第二部分:使用 Win32的API将 bitmap 更新到窗体
这个操作使用 UpdateLayeredWindow 来进行,MSDN上是这样描述的:Updates the position, size, shape, content, and translucency of a layered window.MSDN:ms-help://MS.MSDNQTR.v90.chs/dv_vclib/html/9035dce1-8560-4ea4-94a8-f6e0ba2b2021.htm
现在我们只是用它来将我们绘制好的内存图Update到一个窗体上。你应该已经注意到MSDN的说明以及这个方法的名字了,单词Window前有一个限定词,Layered。那么我们怎么构造一个Layered的Window呢?或者说,怎么才能使我们的窗体成为Layered的呢?很简单,你可以这样:
protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; // 绘制背景必须针对具有 WS_EX_LAYERED 扩展风格的窗体进行 if (!this.DesignMode) { cp.ExStyle |= Win32.WS_EX_LAYERED; // WS_EX_LAYERED } return cp; } }
好了,我们有一个合适的Window了,下面我们使用 UpdateLayeredWindow 来讲绘制的内存图更新到窗体上:(老实说,这段代码是从网上Copy的,嘿嘿~) ///
///
private void SetBitmap(Bitmap bitmap, byte opacity = 255) { if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) throw new Exception("窗体背景图必须是 8 位/通道 RGB 颜色的图。"); // The ideia of this is very simple, // 1. Create a compatible DC with screen; // 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC; // 3. Call the UpdateLayeredWindow. IntPtr screenDc = Win32.GetDC(IntPtr.Zero); IntPtr memDc = Win32.CreateCompatibleDC(screenDc); IntPtr hBitmap = IntPtr.Zero; IntPtr oldBitmap = IntPtr.Zero; try { hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); // grab a GDI handle from this GDI+ bitmap oldBitmap = Win32.SelectObject(memDc, hBitmap); Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height); Win32.Point pointSource = new Win32.Point(0, 0); Win32.Point topPos = new Win32.Point(Left, Top); Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION(); blend.BlendOp = Win32.AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = opacity; blend.AlphaFormat = Win32.AC_SRC_ALPHA; Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA); } finally { Win32.ReleaseDC(IntPtr.Zero, screenDc); if (hBitmap != IntPtr.Zero) { Win32.SelectObject(memDc, oldBitmap); Win32.DeleteObject(hBitmap); } Win32.DeleteDC(memDc); } }
OK,到此为止,我们需要的圆角、渐变的阴影,都有了!
第三部分:简单的换肤机制
大家可能已经注意到,上面的代码有一个名为 SkinOptions 的东西。为了实现皮肤参数的统一传递和包装,我实现了 SkinOptions 这个类,用于保存当前皮肤的所有参数信息。///
///
public void ApplySkin(SkinOptions newSkin) { if (_isSkinChanging) return; this._isSkinChanging = true; Cursor oldCursor = this.Cursor; this.Cursor = Cursors.WaitCursor; SkinEventArgs e = new SkinEventArgs() { Old = this.SkinOptions == null ? null : (SkinOptions)this.SkinOptions.Clone(), New = newSkin == null ? null : (SkinOptions)newSkin.Clone() }; // 触发皮肤切换之前的事件 if (OnSkinPreChange != null) { _disibledevent=>this, e); } if (newSkin == null) { newSkin = SkinOptions.NewOne; } this.SkinOptions = newSkin; SynchronizationContext context = SynchronizationContext.Current; SendOrPostCallback check = (a) => { _isSkinChanging = false; this.Cursor = oldCursor; // 触发皮肤切换之后的事件 if (OnSkinChanged != null) { _disibledevent=>this, e); } }; // 淡入操作 SendOrPostCallback show = (a) => { byte tmp = 0; byte currentOpacity = newSkin.Opacity; bufferdBackgroundImage = CreateBackground(); while (tmp < currentOpacity) { SetBitmap(bufferdBackgroundImage, tmp); tmp += 5; Thread.Sleep(5); } new Thread((sc) => { ((SynchronizationContext)sc).Post(check, null); }) { Name = "检查切换操作是否完毕" }.Start(context); }; // 淡出操作 SendOrPostCallback hide = (a) => { // 渐变因子 byte step = 10; SkinOptions oldSkin = (SkinOptions)this.SkinOptions.Clone(); Bitmap oldbg = (Bitmap)bufferdBackgroundImage.Clone(); int currentOpacity = oldSkin.Opacity; if (oldbg != null) { while (currentOpacity > step) { if (currentOpacity - (int)step <= 0) currentOpacity = step; SetBitmap(oldbg, (byte)currentOpacity); currentOpacity -= step; Thread.Sleep(5); } } new Thread((sc) => { ((SynchronizationContext)sc).Post(show, null); }) { Name = "淡入新皮肤" }.Start(context); }; new Thread((sc) => { ((SynchronizationContext)sc).Post(hide, null); }) { Name = "淡出旧皮肤" }.Start(context); } private bool _isSkinChanging = false;
OK,到此为止,简单的换肤机制也完成了。
最新评论