// ********************************* // Message from Original Author: // // 2008 Jose Menendez Poo // Please give me credit if you use this code. It's all I ask. // Contact me for more info: menendezpoo@gmail.com // ********************************* // // Original project from http://ribbon.codeplex.com/ // Continue to support and maintain by http://officeribbon.codeplex.com/ using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Windows.Forms.RibbonHelpers; namespace System.Windows.Forms { /// /// This class is used to make a form able to contain a ribbon on the non-client area. /// For further instructions search "ribbon non-client" on www.menendezpoo.com /// public class RibbonFormHelper { #region Subclasses /// /// Possible results of a hit test on the non client area of a form /// public enum NonClientHitTestResult { Nowhere = 0, Client = 1, Caption = 2, GrowBox = 4, MinimizeButton = 8, MaximizeButton = 9, Left = 10, Right = 11, Top = 12, TopLeft = 13, TopRight = 14, Bottom = 15, BottomLeft = 16, BottomRight = 17 } #endregion #region Fields private FormWindowState _lastState; private bool _frameExtended; private Ribbon _ribbon; private Size _storeSize; #endregion #region Ctor /// /// Creates a new helper for the specified form /// /// public RibbonFormHelper(Form f) { Form = f; Form.Load += Form_Load; Form.ResizeEnd += _form_ResizeEnd; Form.MinimumSizeChanged += _form_ResizeEnd; Form.MaximumSizeChanged += _form_ResizeEnd; Form.Layout += _form_Layout; Form.TextChanged += _form_TextChanged; } private void _form_TextChanged(object sender, EventArgs e) { UpdateRibbonConditions(); Form.Refresh(); Form.Update(); } private void _form_Layout(object sender, LayoutEventArgs e) { if (_lastState == Form.WindowState) { return; } // in case the RibbonForm is started in WindowState.Maximized and the WindowState changes to normal // the size of the RibbonForm is set to the values of _storeSize - which has not been set yet! if (_storeSize.IsEmpty) _storeSize = Form.Size; if (WinApi.IsGlassEnabled) Form.Invalidate(); else // on XP systems Invalidate is not sufficient in case the Form contains a control with DockStyle.Fill Form.Refresh(); _lastState = Form.WindowState; } private void _form_ResizeEnd(object sender, EventArgs e) { UpdateRibbonConditions(); Form.Refresh(); } #endregion #region Properties /// /// Gets or sets the Ribbon related with the form /// public Ribbon Ribbon { get => _ribbon; set { if (_ribbon != null) { _ribbon.OrbStyleChanged -= RibbonOrbStyleChanged; } _ribbon = value; if (_ribbon != null) { _ribbon.OrbStyleChanged += RibbonOrbStyleChanged; } UpdateRibbonConditions(); } } /// /// Gets or sets the height of the caption bar relative to the form /// public int CaptionHeight { get; set; } /// /// Gets the form this class is helping /// public Form Form { get; } /// /// Gets the margins of the non-client area /// public Padding Margins { get; private set; } /// /// Gets or sets if the margins are already checked by WndProc /// private bool MarginsChecked { get; set; } /// /// Gets if the is currently in Designer mode /// private bool DesignMode => Form != null && Form.Site != null && Form.Site.DesignMode; #endregion #region Methods /// /// Checks if ribbon should be docked or floating and updates its size /// private void UpdateRibbonConditions() { if (Ribbon == null) return; if (Ribbon.Dock != DockStyle.Top) { Ribbon.Dock = DockStyle.Top; } } /// /// Called when helped form is activated /// /// Object that raised the event /// Event data public void Form_Paint(object sender, PaintEventArgs e) { if (DesignMode) return; if (WinApi.IsGlassEnabled) { WinApi.FillForGlass(e.Graphics, new Rectangle(0, 0, Form.Width, Form.Height)); using (Brush b = new SolidBrush(Form.BackColor)) { int left; int right; if (WinApi.IsWin10) { left = 0; right = Form.Width; } else { left = Margins.Left - 0; right = Form.Width - Margins.Right - 0; } e.Graphics.FillRectangle(b, Rectangle.FromLTRB( left, Margins.Top + 0, right, Form.Height - Margins.Bottom - 0)); } } else { PaintTitleBar(e); } } /// /// Draws the title bar of the form when not in glass /// /// private void PaintTitleBar(PaintEventArgs e) { int radius1 = 4, radius2 = radius1 - 0; Rectangle rPath = new Rectangle(Point.Empty, Form.Size); Rectangle rInner = new Rectangle(Point.Empty, new Size(rPath.Width - 1, rPath.Height - 1)); using (GraphicsPath path = RibbonProfessionalRenderer.RoundRectangle(rPath, radius1)) { using (GraphicsPath innerPath = RibbonProfessionalRenderer.RoundRectangle(rInner, radius2)) { if (Ribbon != null && Ribbon.ActualBorderMode == RibbonWindowMode.NonClientAreaCustomDrawn) { RibbonProfessionalRenderer renderer = Ribbon.Renderer as RibbonProfessionalRenderer; if (renderer != null) { e.Graphics.Clear(renderer.ColorTable.RibbonBackground); // draw the Form border explicitly, otherwise problems as MDI parent occur using (SolidBrush p = new SolidBrush(renderer.ColorTable.Caption1)) { e.Graphics.FillRectangle(p, new Rectangle(0, 0, Form.Width, Ribbon.CaptionBarSize)); } renderer.DrawCaptionBarBackground(new Rectangle(0, Margins.Bottom - 1, Form.Width, Ribbon.CaptionBarSize), e.Graphics); using (Region rgn = new Region(path)) { //Set Window Region Form.Region = rgn; SmoothingMode smbuf = e.Graphics.SmoothingMode; e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; using (Pen p = new Pen(renderer.ColorTable.FormBorder, 1)) { e.Graphics.DrawPath(p, innerPath); } e.Graphics.SmoothingMode = smbuf; } } } } } } private void RibbonOrbStyleChanged(object sender, EventArgs e) { if (_frameExtended) { _frameExtended = false; Form_Load(sender, e); } } /// /// Called when helped form is activated /// /// Object that raised the event /// Event data protected virtual void Form_Load(object sender, EventArgs e) { if (DesignMode) return; if (Ribbon == null) { throw new ArgumentNullException(nameof(Ribbon), "Ribbon Control was not placed to RibbonForm"); } WinApi.MARGINS dwmMargins; if (Ribbon.CaptionBarVisible) { dwmMargins = new WinApi.MARGINS( Margins.Left, Margins.Right, Margins.Bottom + Ribbon.ContextSpace + ((Ribbon.OrbStyle == RibbonOrbStyle.Office_2007) ? Ribbon.CaptionBarHeight : Ribbon.CaptionBarHeight + Ribbon.TabsMargin.Top), Margins.Bottom); } else { dwmMargins = new WinApi.MARGINS( Margins.Left, Margins.Right, Margins.Bottom + Ribbon.ContextSpace + ((Ribbon.OrbStyle == RibbonOrbStyle.Office_2007) ? 0 : Ribbon.TabsMargin.Top), Margins.Bottom); } if (WinApi.IsWin10) { dwmMargins.cxLeftWidth = 0; dwmMargins.cxRightWidth = 0; dwmMargins.cyBottomHeight = 0; // https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea } if (WinApi.IsVista && !_frameExtended) { WinApi.DwmExtendFrameIntoClientArea(Form.Handle, ref dwmMargins); _frameExtended = true; } } /// /// Reapply Glass to the form (used after changing Orb style) /// public virtual void ReapplyGlass() { _frameExtended = false; Form_Load(this, EventArgs.Empty); } /// /// Processes the WndProc for a form with a Ribbbon. Returns true if message has been handled /// /// Message to process /// true if message has been handled. false otherwise [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public virtual bool WndProc(ref Message m) { if (DesignMode) { return false; } if (Ribbon == null) { return false; } bool handled = false; if (WinApi.IsVista) { #region Checks if DWM processes the message IntPtr result; int dwmHandled = WinApi.DwmDefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam, out result); if (dwmHandled == 1) { m.Result = result; handled = true; } #endregion } //if (m.Msg == WinApi.WM_NCLBUTTONUP) //{ // UpdateRibbonConditions(); //} if (!handled) { if (m.Msg == WinApi.WM_NCCALCSIZE && (int)m.WParam == 1) //0x83 { #region Catch the margins of what the client area would be WinApi.NCCALCSIZE_PARAMS nccsp = (WinApi.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(WinApi.NCCALCSIZE_PARAMS)); if (!MarginsChecked) { //Set what client area would be for passing to DwmExtendIntoClientArea SetMargins(new Padding( nccsp.rect2.Left - nccsp.rect1.Left, nccsp.rect2.Top - nccsp.rect1.Top, nccsp.rect1.Right - nccsp.rect2.Right, nccsp.rect1.Bottom - nccsp.rect2.Bottom)); MarginsChecked = true; } if (WinApi.IsWin10) { nccsp.rect0.Left += Margins.Left; nccsp.rect0.Right -= Margins.Right; nccsp.rect0.Bottom -= Margins.Bottom; } #region Hack to get rid of the black caption bar when form is maximized on multi-monitor setups with DWM enabled // toATwork: on multi-monitor setups the caption bar when the form is maximized the caption bar is black instead of glass // * set handled to false and let the base implementation handle it, will work but then the default caption bar // is also visible - not desired // * setting the client area to some other value, e.g. descrease the size of the client area by one pixel will // cause windows to render the caption bar a glass - not correct but the lesser of the two evils if (Screen.AllScreens.Length > 1 && WinApi.IsGlassEnabled) { nccsp.rect0.Bottom -= 1; } #endregion Marshal.StructureToPtr(nccsp, m.LParam, false); m.Result = IntPtr.Zero; handled = true; #endregion } else if (m.Msg == WinApi.WM_NCACTIVATE && Ribbon != null && Ribbon.ActualBorderMode == RibbonWindowMode.NonClientAreaCustomDrawn) { Ribbon.Invalidate(); handled = true; if (m.WParam == IntPtr.Zero) // if could be removed because result is ignored if WParam is TRUE m.Result = (IntPtr)1; } else if ((m.Msg == WinApi.WM_ACTIVATE || m.Msg == WinApi.WM_PAINT) && WinApi.IsVista) //0x06 - 0x000F { m.Result = (IntPtr)1; handled = false; } else if (m.Msg == WinApi.WM_NCHITTEST && (int)m.Result == 0) //0x84 { m.Result = new IntPtr(Convert.ToInt32(NonClientHitTest(new Point(WinApi.LoWord((int)m.LParam), WinApi.HiWord((int)m.LParam))))); handled = true; } else if (m.Msg == WinApi.WM_NCRBUTTONUP) //0x00A5 { int xMouse = WinApi.Get_X_LParam((int)m.LParam); int yMouse = WinApi.Get_Y_LParam((int)m.LParam); int hitTest = WinApi.LoWord((int)m.WParam); if (hitTest == (int)WinApi.HitTest.HTCAPTION || hitTest == (int)WinApi.HitTest.HTSYSMENU) { WinApi.ShowSystemMenu(Form, xMouse, yMouse); handled = true; } } else if (m.Msg == WinApi.WM_SYSCOMMAND) { uint param = IntPtr.Size == 4 ? (uint)m.WParam.ToInt32() : (uint)m.WParam.ToInt64(); if ((param & 0xFFF0) == WinApi.SC_RESTORE) { Form.Size = _storeSize; } else if (Form.WindowState == FormWindowState.Normal) { _storeSize = Form.Size; } } else if (m.Msg == WinApi.WM_WINDOWPOSCHANGING || m.Msg == WinApi.WM_WINDOWPOSCHANGED) // needed to update the title of MDI parent (at least) { if (Ribbon != null) Ribbon.Invalidate(); } } return handled; } /// /// Performs hit test for mouse on the non client area of the form /// /// The mouse is pointing /// ///// Form to check bounds ///// Margins of non client area ///// Lparam of public virtual NonClientHitTestResult NonClientHitTest(Point hitPoint) { int leftX = 0; int rightX = 0; if (WinApi.IsWin10) { leftX = -Margins.Left; rightX = -Margins.Right; } Rectangle topleft = Form.RectangleToScreen(new Rectangle(leftX, 0, Margins.Left, Margins.Left)); if (topleft.Contains(hitPoint)) return NonClientHitTestResult.TopLeft; Rectangle topright = Form.RectangleToScreen(new Rectangle(rightX + Form.Width - Margins.Right, 0, Margins.Right, Margins.Right)); if (topright.Contains(hitPoint)) return NonClientHitTestResult.TopRight; Rectangle botleft = Form.RectangleToScreen(new Rectangle(leftX, Form.Height - Margins.Bottom, Margins.Left, Margins.Bottom)); if (botleft.Contains(hitPoint)) return NonClientHitTestResult.BottomLeft; Rectangle botright = Form.RectangleToScreen(new Rectangle(rightX + Form.Width - Margins.Right, Form.Height - Margins.Bottom, Margins.Right, Margins.Bottom)); if (botright.Contains(hitPoint)) return NonClientHitTestResult.BottomRight; Rectangle top = Form.RectangleToScreen(new Rectangle(0, 0, Form.Width, Margins.Left)); if (top.Contains(hitPoint)) return NonClientHitTestResult.Top; Rectangle cap = Form.RectangleToScreen(new Rectangle(0, Margins.Left, Form.Width, Margins.Top - Margins.Left)); if (cap.Contains(hitPoint)) return NonClientHitTestResult.Caption; Rectangle left = Form.RectangleToScreen(new Rectangle(leftX, 0, Margins.Left, Form.Height)); if (left.Contains(hitPoint)) return NonClientHitTestResult.Left; Rectangle right = Form.RectangleToScreen(new Rectangle(rightX + Form.Width - Margins.Right, 0, Margins.Right, Form.Height)); if (right.Contains(hitPoint)) return NonClientHitTestResult.Right; Rectangle bottom = Form.RectangleToScreen(new Rectangle(0, Form.Height - Margins.Bottom, Form.Width, Margins.Bottom)); if (bottom.Contains(hitPoint)) return NonClientHitTestResult.Bottom; return NonClientHitTestResult.Client; } /// /// Sets the value of the property; /// /// private void SetMargins(Padding p) { Margins = p; Padding formPadding = p; formPadding.Top = p.Bottom - 1; if (!DesignMode) { if (WinApi.IsWin10) { formPadding.Left = 0; formPadding.Right = 0; formPadding.Bottom = 0; } Form.Padding = formPadding; } } #endregion } }