using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using System.Runtime.InteropServices; namespace Asztal.Szótár { public enum ToolbarTheme { Toolbar, MediaToolbar, CommunicationsToolbar, BrowserTabBar } /// /// Renders a toolstrip using the UxTheme API via VisualStyleRenderer. Visual styles must be supported for this to work; if you need to support other operating systems use /// class UXThemeToolStripRenderer : ToolStripSystemRenderer { /// /// It shouldn't be necessary to P/Invoke like this, however a bug in VisualStyleRenderer.GetMargins forces my hand. /// static internal class NativeMethods { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MARGINS { public int cxLeftWidth; public int cxRightWidth; public int cyTopHeight; public int cyBottomHeight; } [DllImport("uxtheme", ExactSpelling = true)] public extern static Int32 GetThemeMargins(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, int iPropId, IntPtr rect, out MARGINS pMargins); } //See http://msdn2.microsoft.com/en-us/library/bb773210.aspx - "Parts and States" #region Parts and States enum MenuParts : int { MENU_MENUITEM_TMSCHEMA = 1, MENU_MENUDROPDOWN_TMSCHEMA = 2, MENU_MENUBARITEM_TMSCHEMA = 3, MENU_MENUBARDROPDOWN_TMSCHEMA = 4, MENU_CHEVRON_TMSCHEMA = 5, MENU_SEPARATOR_TMSCHEMA = 6, MENU_BARBACKGROUND = 7, MENU_BARITEM = 8, MENU_POPUPBACKGROUND = 9, MENU_POPUPBORDERS = 10, MENU_POPUPCHECK = 11, MENU_POPUPCHECKBACKGROUND = 12, MENU_POPUPGUTTER = 13, MENU_POPUPITEM = 14, MENU_POPUPSEPARATOR = 15, MENU_POPUPSUBMENU = 16, MENU_SYSTEMCLOSE = 17, MENU_SYSTEMMAXIMIZE = 18, MENU_SYSTEMMINIMIZE = 19, MENU_SYSTEMRESTORE = 20 } enum MenuBarStates : int { MB_ACTIVE = 1, MB_INACTIVE = 2 } enum MenuBarItemStates : int { MBI_NORMAL = 1, MBI_HOT = 2, MBI_PUSHED = 3, MBI_DISABLED = 4, MBI_DISABLEDHOT = 5, MBI_DISABLEDPUSHED = 6 } enum MenuPopupItemStates : int { MPI_NORMAL = 1, MPI_HOT = 2, MPI_DISABLED = 3, MPI_DISABLEDHOT = 4 } enum MenuPopupCheckStates : int { MC_CHECKMARKNORMAL = 1, MC_CHECKMARKDISABLED = 2, MC_BULLETNORMAL = 3, MC_BULLETDISABLED = 4 } enum MenuPopupCheckBackgroundStates : int { MCB_DISABLED = 1, MCB_NORMAL = 2, MCB_BITMAP = 3 } enum MenuPopupSubMenuStates : int { MSM_NORMAL = 1, MSM_DISABLED = 2 } enum MarginTypes : int { TMT_SIZINGMARGINS = 3601, TMT_CONTENTMARGINS = 3602, TMT_CAPTIONMARGINS = 3603 } const int RP_BACKGROUND = 6; #endregion #region Theme helpers Padding GetThemeMargins(IDeviceContext dc, MarginTypes marginType) { NativeMethods.MARGINS margins; try { IntPtr hDC = dc.GetHdc(); if (0 == NativeMethods.GetThemeMargins(renderer.Handle, hDC, renderer.Part, renderer.State, (int)marginType, IntPtr.Zero, out margins)) return new Padding(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight); return new Padding(-1); } finally { dc.ReleaseHdc(); } } private static int GetItemState(ToolStripItem item) { bool pressed = item.Pressed; bool hot = item.Selected; if (item.Owner.IsDropDown) { if (item.Enabled) return hot ? (int)MenuPopupItemStates.MPI_HOT : (int)MenuPopupItemStates.MPI_NORMAL; return hot ? (int)MenuPopupItemStates.MPI_DISABLEDHOT : (int)MenuPopupItemStates.MPI_DISABLED; } else { if (pressed) return item.Enabled ? (int)MenuBarItemStates.MBI_PUSHED : (int)MenuBarItemStates.MBI_DISABLEDPUSHED; if (item.Enabled) return hot ? (int)MenuBarItemStates.MBI_HOT : (int)MenuBarItemStates.MBI_NORMAL; return hot ? (int)MenuBarItemStates.MBI_DISABLEDHOT : (int)MenuBarItemStates.MBI_DISABLED; } } #endregion #region Theme subclasses public ToolbarTheme Theme { get; set; } private string RebarClass { get { return SubclassPrefix + "Rebar"; } } private string ToolbarClass { get { return SubclassPrefix + "ToolBar"; } } private string MenuClass { get { return SubclassPrefix + "Menu"; } } private string SubclassPrefix { get { switch (Theme) { case ToolbarTheme.MediaToolbar: return "Media::"; case ToolbarTheme.CommunicationsToolbar: return "Communications::"; case ToolbarTheme.BrowserTabBar: return "BrowserTabBar::"; default: return string.Empty; } } } private VisualStyleElement Subclass(VisualStyleElement element) { return VisualStyleElement.CreateElement(SubclassPrefix + element.ClassName, element.Part, element.State); } #endregion VisualStyleRenderer renderer; public UXThemeToolStripRenderer(ToolbarTheme theme) { Theme = theme; renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal); } #region Borders protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPBORDERS, 0); if (e.ToolStrip.IsDropDown) { Region oldClip = e.Graphics.Clip; //Tool strip borders are rendered *after* the content, for some reason. //So we have to exclude the inside of the popup otherwise we'll draw over it. Rectangle insideRect = e.ToolStrip.ClientRectangle; insideRect.Inflate(-1, -1); e.Graphics.ExcludeClip(insideRect); renderer.DrawBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.AffectedBounds); //Restore the old clip in case the Graphics is used again (does that ever happen?) e.Graphics.Clip = oldClip; } } #endregion #region Backgrounds protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { int partId = e.Item.Owner.IsDropDown ? (int)MenuParts.MENU_POPUPITEM : (int)MenuParts.MENU_BARITEM; renderer.SetParameters(MenuClass, partId, GetItemState(e.Item)); Rectangle bgRect = e.Item.ContentRectangle; Padding content = GetThemeMargins(e.Graphics, MarginTypes.TMT_CONTENTMARGINS), sizing = GetThemeMargins(e.Graphics, MarginTypes.TMT_SIZINGMARGINS), caption = GetThemeMargins(e.Graphics, MarginTypes.TMT_CAPTIONMARGINS); if (!e.Item.Owner.IsDropDown) { bgRect.Y = 0; bgRect.Height = e.ToolStrip.Height; bgRect.Inflate(-1, -1); //GetMargins here perhaps? } renderer.DrawBackground(e.Graphics, bgRect, bgRect); } protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e) { //Draw the background using Rebar & RP_BACKGROUND (or, if that is not available, fall back to //Rebar.Band.Normal) if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement(RebarClass, RP_BACKGROUND, 0))) { renderer.SetParameters(RebarClass, RP_BACKGROUND, 0); } else { renderer.SetParameters(RebarClass, 0, 0); //renderer.SetParameters(VisualStyleElement.Taskbar.BackgroundBottom.Normal); //renderer.SetParameters(Subclass(VisualStyleElement.Rebar.Band.Normal)); } if (renderer.IsBackgroundPartiallyTransparent()) renderer.DrawParentBackground(e.Graphics, e.ToolStripPanel.ClientRectangle, e.ToolStripPanel); renderer.DrawBackground(e.Graphics, e.ToolStripPanel.ClientRectangle); //Draw the etched edges of each row. //renderer.SetParameters(Subclass(VisualStyleElement.Rebar.Band.Normal)); //foreach (ToolStripPanelRow row in e.ToolStripPanel.Rows) { // Rectangle rowBounds = row.Bounds; // rowBounds.Offset(0, -1); // renderer.DrawEdge(e.Graphics, rowBounds, Edges.Top, EdgeStyle.Etched, EdgeEffects.None); //} e.Handled = true; } //Render the background of an actual menu bar, dropdown menu or toolbar. protected override void OnRenderToolStripBackground(System.Windows.Forms.ToolStripRenderEventArgs e) { if (e.ToolStrip.IsDropDown) { renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPBACKGROUND, 0); } else { //It's a MenuStrip or a ToolStrip. If it's contained inside a larger panel, it should have a //transparent background, showing the panel's background. if (e.ToolStrip.Parent is ToolStripPanel) { //The background should be transparent, because the ToolStripPanel's background will be visible. //(Of course, we assume the ToolStripPanel is drawn using the same theme, but it's not my fault //if someone does that.) return; } else { //A lone toolbar/menubar should act like it's inside a toolbox, I guess. //Maybe I should use the MenuClass in the case of a MenuStrip, although that would break //the other themes... if(VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement(RebarClass, RP_BACKGROUND, 0))) renderer.SetParameters(RebarClass, RP_BACKGROUND, 0); else renderer.SetParameters(RebarClass, 0, 0); } } if (renderer.IsBackgroundPartiallyTransparent()) renderer.DrawParentBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.ToolStrip); renderer.DrawBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.AffectedBounds); } protected override void OnRenderToolStripContentPanelBackground(ToolStripContentPanelRenderEventArgs e) { //e.Graphics.FillRectangle(Brushes.RosyBrown, e.ToolStripContentPanel.ClientRectangle); //base.OnRenderToolStripContentPanelBackground(e); } //Some sort of chevron thing? //protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e) { // base.OnRenderOverflowButtonBackground(e); //} #endregion #region Text protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { int partId = e.Item.Owner.IsDropDown ? (int)MenuParts.MENU_POPUPITEM : (int)MenuParts.MENU_BARITEM; renderer.SetParameters(MenuClass, partId, GetItemState(e.Item)); Color color = renderer.GetColor(ColorProperty.TextColor); if(e.Item.Owner.IsDropDown || e.Item.Owner is MenuStrip) e.TextColor = color; base.OnRenderItemText(e); } #endregion #region Glyphs //protected override void OnRenderGrip(ToolStripGripRenderEventArgs e) { // if (e.GripStyle == ToolStripGripStyle.Visible) { // renderer.SetParameters(VisualStyleElement.Rebar.Gripper.Normal); // renderer.DrawBackground(e.Graphics, e.GripBounds, e.AffectedBounds); // } //} protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) { if (e.ToolStrip.IsDropDown) { renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPGUTTER, 0); Rectangle displayRect = e.ToolStrip.DisplayRectangle, marginRect = new Rectangle(0, displayRect.Top, displayRect.Left, displayRect.Height); //e.Graphics.DrawRectangle(Pens.Black, marginRect); renderer.DrawBackground(e.Graphics, marginRect, marginRect); } } protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) { if (e.ToolStrip.IsDropDown) { renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPSEPARATOR, 0); Rectangle rect = new Rectangle(e.ToolStrip.DisplayRectangle.Left, 0, e.ToolStrip.DisplayRectangle.Width, e.Item.Height); renderer.DrawBackground(e.Graphics, rect, rect); } else { base.OnRenderSeparator(e); } } protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) { ToolStripMenuItem item = e.Item as ToolStripMenuItem; if (item != null) { if (item.Checked) { Rectangle rect = e.Item.ContentRectangle; rect.Width = rect.Height; //Center the checkmark horizontally in the gutter (looks ugly, though) //rect.X = (e.ToolStrip.DisplayRectangle.Left - rect.Width) / 2; renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPCHECKBACKGROUND, e.Item.Enabled ? (int)MenuPopupCheckBackgroundStates.MCB_NORMAL : (int)MenuPopupCheckBackgroundStates.MCB_DISABLED); renderer.DrawBackground(e.Graphics, rect); Padding margins = GetThemeMargins(e.Graphics, MarginTypes.TMT_SIZINGMARGINS); rect = new Rectangle(rect.X + margins.Left, rect.Y + margins.Top, rect.Width - margins.Horizontal, rect.Height - margins.Vertical); //I don't think ToolStrip even supports radio box items. So no need to render them. renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPCHECK, e.Item.Enabled ? (int)MenuPopupCheckStates.MC_CHECKMARKNORMAL : (int)MenuPopupCheckStates.MC_CHECKMARKDISABLED); renderer.DrawBackground(e.Graphics, rect); } } else { base.OnRenderItemCheck(e); } } //This is broken for RTL protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { int stateId = e.Item.Enabled ? (int)MenuPopupSubMenuStates.MSM_NORMAL : (int)MenuPopupSubMenuStates.MSM_DISABLED; renderer.SetParameters(MenuClass, (int)MenuParts.MENU_POPUPSUBMENU, stateId); renderer.DrawBackground(e.Graphics, e.ArrowRectangle); } protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e) { renderer.SetParameters(RebarClass, VisualStyleElement.Rebar.Chevron.Normal.Part, VisualStyleElement.Rebar.Chevron.Normal.State); renderer.DrawBackground(e.Graphics, e.Item.ContentRectangle); //base.OnRenderOverflowButtonBackground(e); } #endregion public static bool IsSupported { get { if (!VisualStyleRenderer.IsSupported) return false; return VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement("MENU", (int)MenuParts.MENU_BARBACKGROUND, (int)MenuBarStates.MB_ACTIVE)); } } } /// /// Renders a toolstrip using UXTheme if possible, and switches back to the default /// ToolStripRenderer when UXTheme-based rendering is not available. /// Designed for menu bars and context menus - it is not guaranteed to work with anything else. /// /// /// NativeToolStripRenderer.SetToolStripRenderer(toolStrip1, toolStrip2, contextMenuStrip1); /// /// /// toolStrip1.Renderer = new NativeToolStripRenderer(); /// public class NativeToolStripRenderer : ToolStripRenderer { UXThemeToolStripRenderer nativeRenderer; ToolStripRenderer defaultRenderer; ToolStrip toolStrip; //NativeToolStripRenderer looks best with no padding - but keep the old padding in case the //visual styles become unsupported again (e.g. user changes to windows classic skin) Padding defaultPadding; #region Constructors /// /// Creates a NativeToolStripRenderer for a particular ToolStrip. NativeToolStripRenderer will subscribe to some events /// of this ToolStrip. /// /// The toolstrip for this NativeToolStripRenderer. NativeToolStripRenderer will subscribe to some events /// of this ToolStrip. public NativeToolStripRenderer(ToolStrip toolStrip, ToolbarTheme theme) { if (toolStrip == null) throw new ArgumentNullException("toolStrip", "ToolStrip cannot be null."); Theme = theme; this.toolStrip = toolStrip; defaultRenderer = toolStrip.Renderer; defaultPadding = toolStrip.Padding; toolStrip.SystemColorsChanged += new EventHandler(toolStrip_SystemColorsChanged); //Can't initialize here - constructor throws if visual styles not enabled //nativeRenderer = new NativeToolStripRenderer(); } public NativeToolStripRenderer(ToolStripPanel panel, ToolbarTheme theme) { if (panel == null) throw new ArgumentNullException("panel", "Panel cannot be null."); Theme = theme; this.toolStrip = null; defaultRenderer = panel.Renderer; } #endregion public ToolbarTheme Theme { get; set; } void toolStrip_SystemColorsChanged(object sender, EventArgs e) { if (toolStrip == null) return; if (UXThemeToolStripRenderer.IsSupported) toolStrip.Padding = Padding.Empty; else toolStrip.Padding = defaultPadding; } //This is indeed called every time a menu part is rendered, but I can't //find a way of caching it that I can be sure has no race conditions. //The check is no longer very costly, anyway. protected ToolStripRenderer ActualRenderer { get { bool nativeSupported = UXThemeToolStripRenderer.IsSupported; if (nativeSupported) { if (nativeRenderer == null) nativeRenderer = new UXThemeToolStripRenderer(Theme); return nativeRenderer; } return defaultRenderer; } } #region InitializeXXX protected override void Initialize(ToolStrip toolStrip) { base.Initialize(toolStrip); toolStrip.Padding = Padding.Empty; if (/*!(toolStrip is MenuStrip) &&*/ toolStrip.Parent is ToolStripPanel) { toolStrip.BackColor = Color.Transparent; } } protected override void InitializePanel(ToolStripPanel toolStripPanel) { base.InitializePanel(toolStripPanel); } protected override void InitializeItem(ToolStripItem item) { base.InitializeItem(item); } #endregion #region SetToolStripRenderer /// /// Sets the renderer of each ToolStrip to a NativeToolStripRenderer. A convenience method. /// /// A parameter list of ToolStrips. [SuppressMessage("Microsoft.Design", "CA1062")] //The parameter array is actually checked. public static void SetToolStripRenderer(ToolbarTheme theme, params Control[] toolStrips) { foreach (Control ts in toolStrips) { if (ts == null) throw new ArgumentNullException("toolStrips", "ToolStrips cannot contain a null reference."); } foreach (Control ts in toolStrips) { if (ts is ToolStrip) { ToolStrip t = (ToolStrip)ts; t.Renderer = new NativeToolStripRenderer(t, theme); } else if (ts is ToolStripPanel) { ToolStripPanel t = (ToolStripPanel)ts; t.Renderer = new NativeToolStripRenderer(t, theme); } else throw new ArgumentException("Can't set the renderer for a " + ts.GetType().Name); } } public static void SetToolStripRenderer(params Control[] toolStrips) { SetToolStripRenderer(ToolbarTheme.Toolbar, toolStrips); } #endregion #region Overridden Methods - Deferred to actual renderer protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { ActualRenderer.DrawArrow(e); } protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { ActualRenderer.DrawButtonBackground(e); } protected override void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e) { ActualRenderer.DrawDropDownButtonBackground(e); } protected override void OnRenderGrip(ToolStripGripRenderEventArgs e) { ActualRenderer.DrawGrip(e); } protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) { ActualRenderer.DrawImageMargin(e); } protected override void OnRenderItemBackground(ToolStripItemRenderEventArgs e) { ActualRenderer.DrawItemBackground(e); } protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) { ActualRenderer.DrawItemCheck(e); } protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) { ActualRenderer.DrawItemImage(e); } protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { ActualRenderer.DrawItemText(e); } protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { ActualRenderer.DrawMenuItemBackground(e); } protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) { ActualRenderer.DrawSeparator(e); } protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) { ActualRenderer.DrawToolStripBackground(e); } protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { ActualRenderer.DrawToolStripBorder(e); } protected override void OnRenderToolStripContentPanelBackground(ToolStripContentPanelRenderEventArgs e) { ActualRenderer.DrawToolStripContentPanelBackground(e); } protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e) { ActualRenderer.DrawToolStripPanelBackground(e); } #endregion } }