MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Как изменить цвет фрейма?"
Answer 652269
Обрабатывать WM_NCPAINT (и еще несколько сообщений) и рисовать эту рамку самостоятельно. Вот простейший пример, основанный на OpenSource-проекте customerborderform.codeplex.com. Он не совершенный, так как нет двойной буферизации и при изменении размера окно мерцает, но думаю как основа будет полезен.
Вспомогательные классы
NativeMethods
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Drawing; using System.Windows.Forms; namespace WindowsFormsTest { public class NativeMethods { #region WindowMessages public enum WindowMessages { WM_NULL = 0x0000, WM_CREATE = 0x0001, WM_DESTROY = 0x0002, WM_MOVE = 0x0003, WM_SIZE = 0x0005, WM_ACTIVATE = 0x0006, WM_SETFOCUS = 0x0007, WM_KILLFOCUS = 0x0008, WM_ENABLE = 0x000A, WM_SETREDRAW = 0x000B, WM_SETTEXT = 0x000C, WM_GETTEXT = 0x000D, WM_GETTEXTLENGTH = 0x000E, WM_PAINT = 0x000F, WM_CLOSE = 0x0010, WM_QUIT = 0x0012, WM_ERASEBKGND = 0x0014, WM_SYSCOLORCHANGE = 0x0015, WM_SHOWWINDOW = 0x0018, WM_ACTIVATEAPP = 0x001C, WM_SETCURSOR = 0x0020, WM_MOUSEACTIVATE = 0x0021, WM_GETMINMAXINFO = 0x24, WM_WINDOWPOSCHANGING = 0x0046, WM_WINDOWPOSCHANGED = 0x0047, WM_CONTEXTMENU = 0x007B, WM_STYLECHANGING = 0x007C, WM_STYLECHANGED = 0x007D, WM_DISPLAYCHANGE = 0x007E, WM_GETICON = 0x007F, WM_SETICON = 0x0080, // non client area WM_NCCREATE = 0x0081, WM_NCDESTROY = 0x0082, WM_NCCALCSIZE = 0x0083, WM_NCHITTEST = 0x84, WM_NCPAINT = 0x0085, WM_NCACTIVATE = 0x0086, WM_GETDLGCODE = 0x0087, WM_SYNCPAINT = 0x0088, // non client mouse WM_NCMOUSEMOVE = 0x00A0, WM_NCLBUTTONDOWN = 0x00A1, WM_NCLBUTTONUP = 0x00A2, WM_NCLBUTTONDBLCLK = 0x00A3, WM_NCRBUTTONDOWN = 0x00A4, WM_NCRBUTTONUP = 0x00A5, WM_NCRBUTTONDBLCLK = 0x00A6, WM_NCMBUTTONDOWN = 0x00A7, WM_NCMBUTTONUP = 0x00A8, WM_NCMBUTTONDBLCLK = 0x00A9, // keyboard WM_KEYDOWN = 0x0100, WM_KEYUP = 0x0101, WM_CHAR = 0x0102, WM_SYSCOMMAND = 0x0112, // menu WM_INITMENU = 0x0116, WM_INITMENUPOPUP = 0x0117, WM_MENUSELECT = 0x011F, WM_MENUCHAR = 0x0120, WM_ENTERIDLE = 0x0121, WM_MENURBUTTONUP = 0x0122, WM_MENUDRAG = 0x0123, WM_MENUGETOBJECT = 0x0124, WM_UNINITMENUPOPUP = 0x0125, WM_MENUCOMMAND = 0x0126, WM_CHANGEUISTATE = 0x0127, WM_UPDATEUISTATE = 0x0128, WM_QUERYUISTATE = 0x0129, // mouse WM_MOUSEFIRST = 0x0200, WM_MOUSEMOVE = 0x0200, WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202, WM_LBUTTONDBLCLK = 0x0203, WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205, WM_RBUTTONDBLCLK = 0x0206, WM_MBUTTONDOWN = 0x0207, WM_MBUTTONUP = 0x0208, WM_MBUTTONDBLCLK = 0x0209, WM_MOUSEWHEEL = 0x020A, WM_MOUSELAST = 0x020D, WM_PARENTNOTIFY = 0x0210, WM_ENTERMENULOOP = 0x0211, WM_EXITMENULOOP = 0x0212, WM_NEXTMENU = 0x0213, WM_SIZING = 0x0214, WM_CAPTURECHANGED = 0x0215, WM_MOVING = 0x0216, WM_ENTERSIZEMOVE = 0x0231, WM_EXITSIZEMOVE = 0x0232, WM_MOUSELEAVE = 0x02A3, WM_MOUSEHOVER = 0x02A1, WM_NCMOUSEHOVER = 0x02A0, WM_NCMOUSELEAVE = 0x02A2, WM_MDIACTIVATE = 0x0222, WM_HSCROLL = 0x0114, WM_VSCROLL = 0x0115, WM_PRINT = 0x0317, WM_PRINTCLIENT = 0x0318, } #endregion //WindowMessages #region DCX enum [Flags()] internal enum DCX { DCX_CACHE = 0x2, DCX_CLIPCHILDREN = 0x8, DCX_CLIPSIBLINGS = 0x10, DCX_EXCLUDERGN = 0x40, DCX_EXCLUDEUPDATE = 0x100, DCX_INTERSECTRGN = 0x80, DCX_INTERSECTUPDATE = 0x200, DCX_LOCKWINDOWUPDATE = 0x400, DCX_NORECOMPUTE = 0x100000, DCX_NORESETATTRS = 0x4, DCX_PARENTCLIP = 0x20, DCX_VALIDATE = 0x200000, DCX_WINDOW = 0x1, } #endregion //DCX #region RECT structure [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; public RECT(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public Rectangle Rect { get { return new Rectangle(this.left, this.top, this.right - this.left, this.bottom - this.top); } } public static RECT FromXYWH(int x, int y, int width, int height) { return new RECT(x, y, x + width, y + height); } public static RECT FromRectangle(Rectangle rect) { return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom); } } #endregion RECT structure #region TRACKMOUSEEVENT structure [StructLayout(LayoutKind.Sequential)] public class TRACKMOUSEEVENT { public TRACKMOUSEEVENT() { this.cbSize = Marshal.SizeOf(typeof(NativeMethods.TRACKMOUSEEVENT)); this.dwHoverTime = 100; } public int cbSize; public int dwFlags; public IntPtr hwndTrack; public int dwHoverTime; } #endregion #region TernaryRasterOperations enum public enum TernaryRasterOperations { SRCCOPY = 0x00CC0020, /* dest = source*/ SRCPAINT = 0x00EE0086, /* dest = source OR dest*/ SRCAND = 0x008800C6, /* dest = source AND dest*/ SRCINVERT = 0x00660046, /* dest = source XOR dest*/ SRCERASE = 0x00440328, /* dest = source AND (NOT dest )*/ NOTSRCCOPY = 0x00330008, /* dest = (NOT source)*/ NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */ MERGECOPY = 0x00C000CA, /* dest = (source AND pattern)*/ MERGEPAINT = 0x00BB0226, /* dest = (NOT source) OR dest*/ PATCOPY = 0x00F00021, /* dest = pattern*/ PATPAINT = 0x00FB0A09, /* dest = DPSnoo*/ PATINVERT = 0x005A0049, /* dest = pattern XOR dest*/ DSTINVERT = 0x00550009, /* dest = (NOT dest)*/ BLACKNESS = 0x00000042, /* dest = BLACK*/ WHITENESS = 0x00FF0062, /* dest = WHITE*/ }; #endregion #region Constants public static readonly IntPtr TRUE = new IntPtr(1); public static readonly IntPtr FALSE = new IntPtr(0); public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); #endregion #region API methods [DllImport("user32.dll")] public static extern IntPtr GetDCEx(IntPtr hwnd, IntPtr hrgnclip, uint fdwOptions); [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hwnd, IntPtr hDC); [DllImport("user32.dll")] public static extern int GetWindowRect(IntPtr hwnd, ref RECT lpRect); public const int VK_LBUTTON = 0x01; public const int VK_RBUTTON = 0x02; public static int GetLastError() { return System.Runtime.InteropServices.Marshal.GetLastWin32Error(); } [DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hDC); #endregion } }
SystemMetric
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WindowsFormsTest { public enum SystemMetric : int { /// <summary> /// The width of a window border, in pixels. This is equivalent to the SM_CXEDGE value for windows with the 3-D look. /// </summary> SM_CXBORDER = 5, /// <summary> /// The width of a cursor, in pixels. The system cannot create cursors of other sizes. /// </summary> SM_CXCURSOR = 13, /// <summary> /// This value is the same as SM_CXFIXEDFRAME. /// </summary> SM_CXDLGFRAME = 7, /// <summary> /// This value is the same as SM_CXSIZEFRAME. /// </summary> SM_CXFRAME = 32, /// <summary> /// The width of a button in a window caption or title bar, in pixels. /// </summary> SM_CXSIZE = 30, /// <summary> /// The thickness of the sizing border around the perimeter of a window that can be resized, in pixels. /// SM_CXSIZEFRAME is the width of the horizontal border, and SM_CYSIZEFRAME is the height of the vertical border. /// This value is the same as SM_CXFRAME. /// </summary> SM_CXSIZEFRAME = 32, /// <summary> /// The height of a window border, in pixels. This is equivalent to the SM_CYEDGE value for windows with the 3-D look. /// </summary> SM_CYBORDER = 6, /// <summary> /// The height of a caption area, in pixels. /// </summary> SM_CYCAPTION = 4, /// <summary> /// This value is the same as SM_CYSIZEFRAME. /// </summary> SM_CYFRAME = 33, /// <summary> /// The height of a button in a window caption or title bar, in pixels. /// </summary> SM_CYSIZE = 31, /// <summary> /// The thickness of the sizing border around the perimeter of a window that can be resized, in pixels. /// SM_CXSIZEFRAME is the width of the horizontal border, and SM_CYSIZEFRAME is the height of the vertical border. /// This value is the same as SM_CYFRAME. /// </summary> SM_CYSIZEFRAME = 33, /// <summary> /// The height of a small caption, in pixels. /// </summary> SM_CYSMCAPTION = 51, /// <summary> /// The recommended height of a small icon, in pixels. Small icons typically appear in window captions and in small icon view. /// </summary> SM_CYSMICON = 50, /// <summary> /// The height of small caption buttons, in pixels. /// </summary> SM_CYSMSIZE = 53, } }
Собственно форма
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; /*https://customerborderform.codeplex.com*/ namespace WindowsFormsTest { public partial class Form1 : Form { /*Параметры отображения окна*/ Color TextColor = Color.Black;//цвет текста Color FrameColor = Color.Red;//цвет рамки bool EnableNonClientAreaPaint = true;//использовать нестандартную рамку [DllImport("user32.dll")] static extern int GetSystemMetrics(SystemMetric smIndex); public Form1() { InitializeComponent(); } /// <summary> /// Custom client area paint /// </summary> protected void OnNonClientAreaPaint(NonClientPaintEventArgs e) { /*Размеры рамки*/ int w = GetSystemMetrics(SystemMetric.SM_CXSIZEFRAME); int h = GetSystemMetrics(SystemMetric.SM_CYSIZEFRAME); /*Размеры кнопки*/ int w2 = (GetSystemMetrics(SystemMetric.SM_CXSIZE)); int h2 = GetSystemMetrics(SystemMetric.SM_CYSIZE); Rectangle rc; StringFormat fmt=StringFormat.GenericDefault; fmt.Alignment = StringAlignment.Center; fmt.LineAlignment = StringAlignment.Center; Brush br=new SolidBrush(TextColor);//brush for text Brush fbr = new SolidBrush(FrameColor);//brush for frame Font font = new Font("Arial", 14); Pen p = new Pen(FrameColor, (float)w); using(br) using(fbr) using(font) using (p) { /*border*/ e.Graphics.DrawRectangle(p, w / 2.0f, h / 2.0f, e.Bounds.Width - w, e.Bounds.Height - h ); /*title area*/ rc = new Rectangle(w, h, e.Bounds.Width, h2); e.Graphics.FillRectangle(fbr, rc); e.Graphics.DrawString(this.Text, SystemFonts.CaptionFont, br, rc); /*close butt*/ rc=new Rectangle(e.Bounds.Width-w-w2, h, w2, h2-4); e.Graphics.DrawString("x", font, br, rc ,fmt); /*max butt*/ rc = new Rectangle(e.Bounds.Width - w - w2*2, h, w2, h2-4); e.Graphics.DrawString("■", font, br, rc, fmt); /*min butt*/ rc = new Rectangle(e.Bounds.Width - w - w2*3, h, w2, h2-4); e.Graphics.DrawString("–", font, br, rc, fmt); } } protected override void WndProc(ref Message m) { if (!this.EnableNonClientAreaPaint) { base.WndProc(ref m); return; } switch (m.Msg) { case (int)NativeMethods.WindowMessages.WM_NCPAINT: { // Here should all our painting occur, but... DefWndProc(ref m); WmNCPaint(ref m); this.Refresh(); break; } case (int)NativeMethods.WindowMessages.WM_NCACTIVATE: { // ... WM_NCACTIVATE does some painting directly // without bothering with WM_NCPAINT ... WmNCActivate(ref m); break; } case (int)NativeMethods.WindowMessages.WM_SETTEXT: { // ... and some painting is required in here as well WmSetText(ref m); break; } case (int)NativeMethods.WindowMessages.WM_NCMOUSEMOVE: { //PaintNonClientArea(this.Handle, (IntPtr)1); //this.Refresh(); WmNCMouseMove(ref m); break; } case (int)NativeMethods.WindowMessages.WM_ERASEBKGND: { WmEraseBkgnd(ref m); break; } default: { base.WndProc(ref m); break; } } } #region Wm ... (Windows message...) protected void OnUpdateWindowState() { } private void WmEraseBkgnd(ref Message m) { base.WndProc(ref m); //Log(MethodInfo.GetCurrentMethod(), "{0}", WindowState); OnUpdateWindowState(); } #endregion public Point PointToWindow(Point screenPoint) { return new Point(screenPoint.X - Location.X, screenPoint.Y - Location.Y); } #region WmNC ... (Windows message... Non Client) /// <summary> /// Handle WmNCMouseMove, so buttons aren't repainted due to mouse movement /// </summary> private void WmNCMouseMove(ref Message msg) { Point clientPoint = this.PointToWindow(new Point(msg.LParam.ToInt32())); msg.Result = IntPtr.Zero; } private void PaintNonClientArea(IntPtr hWnd, IntPtr hRgn) { NativeMethods.RECT windowRect = new NativeMethods.RECT(); if (NativeMethods.GetWindowRect(hWnd, ref windowRect) == 0) return; Rectangle bounds = new Rectangle(0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top); if (bounds.Width == 0 || bounds.Height == 0) return; // The update region is clipped to the window frame. When wParam is 1, the entire window frame needs to be updated. Region clipRegion = null; if (hRgn != (IntPtr)1) clipRegion = System.Drawing.Region.FromHrgn(hRgn); // MSDN states that only WINDOW and INTERSECTRGN are needed, // but other sources confirm that CACHE is required on Win9x // and you need CLIPSIBLINGS to prevent painting on overlapping windows. IntPtr hDC = NativeMethods.GetDCEx(/*hWnd*/this.Handle, /*hRgn*/(IntPtr)0, (int)(NativeMethods.DCX.DCX_WINDOW /*| NativeMethods.DCX.DCX_INTERSECTRGN*/ | NativeMethods.DCX.DCX_CACHE | NativeMethods.DCX.DCX_CLIPSIBLINGS)); if (hDC == IntPtr.Zero) return; try { using (Graphics g = Graphics.FromHdc(hDC)) { //cliping rect is not cliping rect but actual rectangle OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion)); } //NOTE: The Graphics object would realease the HDC on Dispose. // So there is no need to call NativeMethods.ReleaseDC(msg.HWnd, hDC); } finally { NativeMethods.ReleaseDC(this.Handle, hDC); } } private void WmNCPaint(ref Message msg) { // The WParam contains handle to clipRegion or 1 if entire window should be repainted PaintNonClientArea(msg.HWnd, (IntPtr)msg.WParam); // we handled everything msg.Result = NativeMethods.TRUE; } private void WmSetText(ref Message msg) { // allow the system to receive the new window title DefWndProc(ref msg); // repaint title bar PaintNonClientArea(msg.HWnd, (IntPtr)1); } private void WmNCActivate(ref Message msg) { bool active = (msg.WParam == NativeMethods.TRUE); if (WindowState == FormWindowState.Minimized) DefWndProc(ref msg); else { // repaint title bar PaintNonClientArea(msg.HWnd, (IntPtr)1); // allow to deactivate window msg.Result = NativeMethods.TRUE; } } #endregion } public class NonClientPaintEventArgs : EventArgs { public Graphics Graphics; public Rectangle Bounds; public Region clipRegion; public NonClientPaintEventArgs(Graphics g, Rectangle bounds, Region cr) { this.Graphics = g; this.Bounds = bounds; clipRegion = cr; } } }
Вот как это выглядит
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.