網站首頁 編程語言 正文
前言
在一般能搜到的所有實現圓角窗體的示例中,都是通過繪制圓角的路徑,并創建對應的窗體Region
區域實現。
目前所知,重新創建Region的所有方法,產生的Region都是有鋸齒的【估計要通過消除鋸齒的算法額外處理才可能解決】,也就是說,幾乎所有圓角窗體的示例都是有鋸齒的,其效果幾乎不能看,慘不忍睹。
后面看到Creating Smooth Rounded Corners in WinForm Applications介紹了繪制無鋸齒的光滑圓角窗體的實現。了解了下,總體非常不錯,因此對其進行借鑒并進行了修改。
根據后續的了解,其實現原理是通過LayeredWindow
進行繪制(CreateParams樣式要添加CreateParams.ExStyle |= 0x00080000
),而其理論上,可以在Layered Window上繪制更復雜的(任意形狀)圖形,并且沒有閃爍、邊界鋸齒的問題。
關于Layered Windows(分層窗體)
Layered Windows:使用一個分層窗口可以顯著提高復雜形狀、動畫特效、透明通道混合效果等類型的窗體的性能和視覺效果。
系統自動構造和繪制分層的窗口以及基礎應用的窗體,分層窗體光滑流暢的渲染、沒有復雜窗體區域的典型閃爍,同時支持半透明。
在窗體創建后通過調用CreateWindowEx
或SetWindowLong
函數指定WS_EX_LAYERED
額外窗口樣式,然后通過調用 SetLayeredWindowAttributes
或 UpdateLayeredWindow
使分層窗口可見。
幾個關于LayeredWindow的資料:
用UpdateLayeredWindow實現任意異形窗口
UpdateLayeredWindowIndirect function
這就是引用的stackoverflow中無鋸齒圓角窗體的實現的基本原理。
關于同樣的實現使用Layered Windows與使用透明窗體的區別
不使用Layered Windows時,如果在設置窗體Form透明的情況下,在OnPaint
中繪制,無論執行或不執行SetBitmap()
設置透明通道,在圓角邊緣處都會有白邊出現。
// 設置窗體透明 this.BackColor = Color.Empty; this.TransparencyKey = BackColor;
注意:繼承窗體,在設計器中
Control.DrawToBitmap()將控件繪制到Bitmap
啟用分層窗體后,原本的窗體將會隱藏,因此需要在Layered Windows上進行新形狀(如圓角)窗體的繪制,才會顯示看到,結合透明混合通道的處理,實現正確顯示繪制的圖形窗體和無鋸齒的效果。
顯示繪制的分層窗體后,會同樣覆蓋原窗體上控件,導致只有一個窗體,不會顯示內部的控件。
因此,除了繪制分層窗體圖形,還需要將原窗體的控件繪制上去。
Control的DrawToBitmap
方法:Control.DrawToBitmap(bitmap, Rectangle targetBounds)
,用于將控件的targetBounds
范圍繪制到bitmap
,通常指定控件的ClientRectangle
,將控件整體繪制到bitmap
。
然后,繪圖對象將控件的bitmap
繪制到圖像的指定位置(對應于原空間位置)。
ctrl.DrawToBitmap(bmp, ctrl.ClientRectangle); graphics.DrawImage(bmp, ctrl.Location);
這樣可以實現控件繪制到圖像上,其顯示的渲染效果和使用,與正??丶]有任何區別。
注意在OnPaint方法中,對背景色進行與分層窗體相同的繪制。
最終效果
下面是稍微修改后,通過繼承窗體,在設計器和生成結果中,不同的顯示效果
public class RoundedFormTest : RoundedForm { // 其他相關代碼 }
幾個小問題
StartPosition設置窗體初始位置設置無效
不知為何,設置窗體初始位置無效StartPosition
,具體原因未知。
比如,直接使用StartPosition = FormStartPosition.CenterScreen
并不能設置窗體居中
public RoundedForm() { this.FormBorderStyle = FormBorderStyle.None; // 居中無效 //StartPosition = FormStartPosition.CenterScreen; }
構造函數中設置Location位置無效
Location位置的設置應該放在窗體的Load事件方法中,提前設置并無效果。
public RoundedForm() { this.FormBorderStyle = FormBorderStyle.None; // 構造函數中設置Location無效 // StartPosition = FormStartPosition.Manual; // Location = new Point(200, 200); // 無效 //Left = 200; //Top = 200; }
繼承窗體RoundedFormTest
的Load事件處理程序:
private void RoundedFormTest_Load(object sender, EventArgs e) { // 有效 Location = new Point(300, 300); }
在設計器中,右鍵窗體無法顯示菜單
這也是一個很奇怪的問題,原因不知。只能右鍵窗體以外的部分來顯示菜單,查看屬性或代碼等。
代碼實現
修改部分
- 添加了設置背景顏色的屬性、背景漸變方向的屬性、是否可以調整窗體大小的屬性
[Category("高級"), DefaultValue(true), Description("窗體是否固定大小,為true時,無法拖動邊角調整窗體大小,默認true")] public bool FixedSize { get; set; } = true; [Category("高級"), DefaultValue(typeof(Color), "DarkSlateBlue"), Description("漸變背景開始的顏色,如果BgStartColor和BgEndColor顏色一樣,則無漸變")] public Color BgStartColor { get => bgStartColor; set { bgStartColor = value; Validate(); } } [Category("高級"), DefaultValue(typeof(Color), "MediumPurple"), Description("漸變背景結束的顏色,如果BgStartColor和BgEndColor顏色一樣,則無漸變")] public Color BgEndColor { get => bgEndColor; set { bgEndColor = value; Validate(); } } [Category("高級"), DefaultValue(0f), Description("背景顏色的漸變方向,默認0度,水平方向漸變")] public float LinearGradient { get => linearGradient; set { linearGradient = value; Validate(); } }
- 去除了原本通過計時器定時執行Layered Windows繪制的實現,改為在OnResize、OnShown方法中繪制
原本的代碼在Load加載后,通過定時器定時執行Layered Windows的繪制,感覺這樣實現太費性能,且不高效。改為將其繪制放在需要繪制的OnResize、OnShown方法中。
- 添加拖動窗體、創拽調整窗體大小的代碼
正常的窗體都應該支持拖動窗體,拖拽調整窗體大小、標題欄等基本功能,此處只添加之前介紹過的拖動和拖拽。其他可根據需要再行修改。
- 添加
RoundRadius
屬性,圓角半徑可以根據需要指定和修改。默認圓角大小為35。
[CategoryAttribute("高級"), DefaultValue(35), Description("圓角半徑的大小")] public int RoundRadius { set { roundRadius = value; this.Invalidate(); } get { return roundRadius; } }
全部代碼
全部的代碼200多行,可根據需要進行精簡。
public class RoundedForm : Form { private Color bgStartColor = Color.DarkSlateBlue; private Color bgEndColor = Color.MediumPurple; private float linearGradient; private int roundRadius;//圓角半徑 // 上面文章列出的屬性代碼,此處不再重復 public RoundedForm() { this.FormBorderStyle = FormBorderStyle.None; roundRadius = 35; } // OnResize、OnShown后繪制Layered Windows protected override void OnResize(EventArgs e) { DrawRoundForm(); base.OnResize(e); } protected override void OnShown(EventArgs e) { DrawRoundForm(); base.OnShown(e); } private void DrawRoundForm() { if (DesignMode) return; if (ClientRectangle.Width == 0 || ClientRectangle.Height == 0) { return; } using (Bitmap backImage = new Bitmap(this.Width, this.Height)) { using (Graphics graphics = Graphics.FromImage(backImage)) { Rectangle gradientRectangle = ClientRectangle; using (Brush b = new LinearGradientBrush(gradientRectangle, BgStartColor, BgEndColor, LinearGradient)) { graphics.FillRoundRectangle(gradientRectangle, b, 35); foreach (Control ctrl in this.Controls) { using (Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height, PixelFormat.Format32bppArgb)) { ctrl.DrawToBitmap(bmp, ctrl.ClientRectangle); // 結合OnPaint中的繪制,能完美實現ctrl圓角的邊角透明底層,原因(猜測可能是)Bitmap沒有指定顏色,控件之外的部分透明 graphics.DrawImage(bmp, ctrl.Location); } } PerPixelAlphaBlend.SetBitmap(backImage, Left, Top, Handle);//不執行將無法顯示窗體 } } } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (ClientRectangle.Width == 0 || ClientRectangle.Height == 0) { return; } using (Graphics graphics = e.Graphics) { //Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1); Rectangle gradientRectangle = ClientRectangle; using (Brush b = new LinearGradientBrush(gradientRectangle, BgStartColor, BgEndColor, LinearGradient)) { graphics.FillRoundRectangle(gradientRectangle, b, 35); }; } } protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; if (!DesignMode) cp.ExStyle |= 0x00080000; // Form 添加 WS_EX_LAYERED 擴展樣式 return cp; } } // 通過重寫 WndProc 實現拖拽調整窗體大小、拖拽移動窗體 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); if (m.Msg == 0x84) { if (!FixedSize) { // 拖拽調整窗體大小 Point vPoint = new Point((int)m.LParam & 0xFFFF, (int)m.LParam >> 16 & 0xFFFF); vPoint = PointToClient(vPoint); if (vPoint.X <= 5) if (vPoint.Y <= 5) m.Result = (IntPtr)HTTOPLEFT; else if (vPoint.Y >= ClientSize.Height - 5) m.Result = (IntPtr)HTBOTTOMLEFT; else m.Result = (IntPtr)HTLEFT; else if (vPoint.X >= ClientSize.Width - 5) if (vPoint.Y <= 5) m.Result = (IntPtr)HTTOPRIGHT; else if (vPoint.Y >= ClientSize.Height - 5) m.Result = (IntPtr)HTBOTTOMRIGHT; else m.Result = (IntPtr)HTRIGHT; else if (vPoint.Y <= 5) m.Result = (IntPtr)HTTOP; else if (vPoint.Y >= ClientSize.Height - 5) m.Result = (IntPtr)HTBOTTOM; } // 鼠標左鍵按下實現拖動窗口功能 if (m.Result.ToInt32() == 1) { m.Result = new IntPtr(2); } } } } public static class PerPixelAlphaBlend { public static void SetBitmap(Bitmap bitmap, int left, int top, IntPtr handle) { SetBitmap(bitmap, 255, left, top, handle); } public static void SetBitmap(Bitmap bitmap, byte opacity, int left, int top, IntPtr handle) { if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) throw new ApplicationException("The bitmap must be 32ppp with alpha-channel."); 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)); 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); } } } internal class Win32 { public enum Bool { False = 0, True }; [StructLayout(LayoutKind.Sequential)] public struct Point { public Int32 x; public Int32 y; public Point(Int32 x, Int32 y) { this.x = x; this.y = y; } } [StructLayout(LayoutKind.Sequential)] public struct Size { public Int32 cx; public Int32 cy; public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ARGB { public byte Blue; public byte Green; public byte Red; public byte Alpha; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BLENDFUNCTION { public byte BlendOp; public byte BlendFlags; public byte SourceConstantAlpha; public byte AlphaFormat; } public const Int32 ULW_COLORKEY = 0x00000001; public const Int32 ULW_ALPHA = 0x00000002; public const Int32 ULW_OPAQUE = 0x00000004; public const byte AC_SRC_OVER = 0x00; public const byte AC_SRC_ALPHA = 0x01; [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags); [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] public static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll", ExactSpelling = true)] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] public static extern IntPtr CreateCompatibleDC(IntPtr hDC); [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] public static extern Bool DeleteDC(IntPtr hdc); [DllImport("gdi32.dll", ExactSpelling = true)] public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] public static extern Bool DeleteObject(IntPtr hObject); }
原文鏈接:https://juejin.cn/post/7138060957980295175
相關推薦
- 2023-04-12 詳解在SpringBoot如何優雅的使用多線程_python
- 2022-04-18 C#繪制餅狀圖和柱狀圖的方法_C#教程
- 2022-07-21 微信小程序--動態設置導航欄顏色
- 2022-10-15 C++中?Sort函數詳細解析_C 語言
- 2022-06-01 Python使用list列表和tuple元組的方法_python
- 2022-12-19 批處理bat腳本獲取打包發布問題記錄_DOS/BAT
- 2022-12-16 C++?Boost?EnableIf函數使用介紹_C 語言
- 2022-08-19 ubuntu上設置Redis開機自啟
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支