Created
August 1, 2015 17:51
-
-
Save Sigmanor/3ed700a4f255d9ca33fd to your computer and use it in GitHub Desktop.
Exported from https://code.google.com/p/szotar/source/browse/trunk/Client/Szotar.WindowsForms/Base/NativeToolStripRenderer.cs?edit=1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Drawing; | |
| using System.Runtime.InteropServices; | |
| using System.Windows.Forms; | |
| using System.Windows.Forms.VisualStyles; | |
| // Thanks for fixes: | |
| // * Marco Minerva, jachymko - http://www.codeplex.com/windowsformsaero | |
| // * Ben Ryves - http://www.benryves.com/ | |
| // | |
| // ** Note for anyone considering using this: ** | |
| // | |
| // A better alternative to using this class is to use the MainMenu and ContextMenu | |
| // controls instead of MenuStrip and ContextMenuStrip, as they provide true native | |
| // rendering. If you require icons, try this: | |
| // | |
| // http://wyday.com/blog/2009/making-the-menus-in-your-net-app-look-professional/ | |
| namespace Szotar.WindowsForms { | |
| public enum ToolbarTheme { | |
| Toolbar, | |
| MediaToolbar, | |
| CommunicationsToolbar, | |
| BrowserTabBar, | |
| HelpBar | |
| } | |
| /// <summary>Renders a toolstrip using the UxTheme API via VisualStyleRenderer and a specific style.</summary> | |
| /// <remarks>Perhaps surprisingly, this does not need to be disposable.</remarks> | |
| public class ToolStripAeroRenderer : ToolStripSystemRenderer { | |
| VisualStyleRenderer renderer; | |
| public ToolStripAeroRenderer(ToolbarTheme theme) { | |
| Theme = theme; | |
| } | |
| /// <summary> | |
| /// It shouldn't be necessary to P/Invoke like this, however VisualStyleRenderer.GetMargins | |
| /// misses out a parameter in its own P/Invoke. | |
| /// </summary> | |
| static internal class NativeMethods { | |
| [StructLayout(LayoutKind.Sequential)] | |
| public struct MARGINS { | |
| public int cxLeftWidth; | |
| public int cxRightWidth; | |
| public int cyTopHeight; | |
| public int cyBottomHeight; | |
| } | |
| [DllImport("uxtheme.dll")] | |
| public extern static int 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" | |
| // Only menu-related parts/states are needed here, VisualStyleRenderer handles most of the rest. | |
| enum MenuParts : int { | |
| ItemTMSchema = 1, | |
| DropDownTMSchema = 2, | |
| BarItemTMSchema = 3, | |
| BarDropDownTMSchema = 4, | |
| ChevronTMSchema = 5, | |
| SeparatorTMSchema = 6, | |
| BarBackground = 7, | |
| BarItem = 8, | |
| PopupBackground = 9, | |
| PopupBorders = 10, | |
| PopupCheck = 11, | |
| PopupCheckBackground = 12, | |
| PopupGutter = 13, | |
| PopupItem = 14, | |
| PopupSeparator = 15, | |
| PopupSubmenu = 16, | |
| SystemClose = 17, | |
| SystemMaximize = 18, | |
| SystemMinimize = 19, | |
| SystemRestore = 20 | |
| } | |
| enum MenuBarStates : int { | |
| Active = 1, | |
| Inactive = 2 | |
| } | |
| enum MenuBarItemStates : int { | |
| Normal = 1, | |
| Hover = 2, | |
| Pushed = 3, | |
| Disabled = 4, | |
| DisabledHover = 5, | |
| DisabledPushed = 6 | |
| } | |
| enum MenuPopupItemStates : int { | |
| Normal = 1, | |
| Hover = 2, | |
| Disabled = 3, | |
| DisabledHover = 4 | |
| } | |
| enum MenuPopupCheckStates : int { | |
| CheckmarkNormal = 1, | |
| CheckmarkDisabled = 2, | |
| BulletNormal = 3, | |
| BulletDisabled = 4 | |
| } | |
| enum MenuPopupCheckBackgroundStates : int { | |
| Disabled = 1, | |
| Normal = 2, | |
| Bitmap = 3 | |
| } | |
| enum MenuPopupSubMenuStates : int { | |
| Normal = 1, | |
| Disabled = 2 | |
| } | |
| enum MarginTypes : int { | |
| Sizing = 3601, | |
| Content = 3602, | |
| Caption = 3603 | |
| } | |
| static readonly int RebarBackground = 6; | |
| 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(0); | |
| } finally { | |
| dc.ReleaseHdc(); | |
| } | |
| } | |
| private static int GetItemState(ToolStripItem item) { | |
| bool hot = item.Selected; | |
| if (item.IsOnDropDown) { | |
| if (item.Enabled) | |
| return hot ? (int)MenuPopupItemStates.Hover : (int)MenuPopupItemStates.Normal; | |
| return hot ? (int)MenuPopupItemStates.DisabledHover : (int)MenuPopupItemStates.Disabled; | |
| } else { | |
| if (item.Pressed) | |
| return item.Enabled ? (int)MenuBarItemStates.Pushed : (int)MenuBarItemStates.DisabledPushed; | |
| if (item.Enabled) | |
| return hot ? (int)MenuBarItemStates.Hover : (int)MenuBarItemStates.Normal; | |
| return hot ? (int)MenuBarItemStates.DisabledHover : (int)MenuBarItemStates.Disabled; | |
| } | |
| } | |
| 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::"; | |
| case ToolbarTheme.HelpBar: return "Help::"; | |
| default: return string.Empty; | |
| } | |
| } | |
| } | |
| private VisualStyleElement Subclass(VisualStyleElement element) { | |
| return VisualStyleElement.CreateElement(SubclassPrefix + element.ClassName, | |
| element.Part, element.State); | |
| } | |
| private bool EnsureRenderer() { | |
| if (!IsSupported) | |
| return false; | |
| if (renderer == null) | |
| renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal); | |
| return true; | |
| } | |
| // Gives parented ToolStrips a transparent background. | |
| protected override void Initialize(ToolStrip toolStrip) { | |
| if (toolStrip.Parent is ToolStripPanel) | |
| toolStrip.BackColor = Color.Transparent; | |
| base.Initialize(toolStrip); | |
| } | |
| // Using just ToolStripManager.Renderer without setting the Renderer individually per ToolStrip means | |
| // that the ToolStrip is not passed to the Initialize method. ToolStripPanels, however, are. So we can | |
| // simply initialize it here too, and this should guarantee that the ToolStrip is initialized at least | |
| // once. Hopefully it isn't any more complicated than this. | |
| protected override void InitializePanel(ToolStripPanel toolStripPanel) { | |
| foreach (Control control in toolStripPanel.Controls) | |
| if (control is ToolStrip) | |
| Initialize((ToolStrip)control); | |
| base.InitializePanel(toolStripPanel); | |
| } | |
| protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| renderer.SetParameters(MenuClass, (int)MenuParts.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; | |
| } | |
| } else { | |
| base.OnRenderToolStripBorder(e); | |
| } | |
| } | |
| Rectangle GetBackgroundRectangle(ToolStripItem item) { | |
| if (!item.IsOnDropDown) | |
| return new Rectangle(new Point(), item.Bounds.Size); | |
| // For a drop-down menu item, the background rectangles of the items should be touching vertically. | |
| // This ensures that's the case. | |
| Rectangle rect = item.Bounds; | |
| // The background rectangle should be inset two pixels horizontally (on both sides), but we have | |
| // to take into account the border. | |
| rect.X = item.ContentRectangle.X + 1; | |
| rect.Width = item.ContentRectangle.Width - 1; | |
| // Make sure we're using all of the vertical space, so that the edges touch. | |
| rect.Y = 0; | |
| return rect; | |
| } | |
| protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| int partID = e.Item.IsOnDropDown ? (int)MenuParts.PopupItem : (int)MenuParts.BarItem; | |
| renderer.SetParameters(MenuClass, partID, GetItemState(e.Item)); | |
| Rectangle bgRect = GetBackgroundRectangle(e.Item); | |
| renderer.DrawBackground(e.Graphics, bgRect, bgRect); | |
| } else { | |
| base.OnRenderMenuItemBackground(e); | |
| } | |
| } | |
| protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| // 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, RebarBackground, 0))) { | |
| renderer.SetParameters(RebarClass, RebarBackground, 0); | |
| } else { | |
| renderer.SetParameters(RebarClass, 0, 0); | |
| } | |
| if (renderer.IsBackgroundPartiallyTransparent()) | |
| renderer.DrawParentBackground(e.Graphics, e.ToolStripPanel.ClientRectangle, e.ToolStripPanel); | |
| renderer.DrawBackground(e.Graphics, e.ToolStripPanel.ClientRectangle); | |
| e.Handled = true; | |
| } else { | |
| base.OnRenderToolStripPanelBackground(e); | |
| } | |
| } | |
| // Render the background of an actual menu bar, dropdown menu or toolbar. | |
| protected override void OnRenderToolStripBackground(System.Windows.Forms.ToolStripRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| if (e.ToolStrip.IsDropDown) { | |
| renderer.SetParameters(MenuClass, (int)MenuParts.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, RebarBackground, 0))) | |
| renderer.SetParameters(RebarClass, RebarBackground, 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); | |
| } else { | |
| base.OnRenderToolStripBackground(e); | |
| } | |
| } | |
| // The only purpose of this override is to change the arrow colour. | |
| // It's OK to just draw over the default arrow since we also pass down arrow drawing to the system renderer. | |
| protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| ToolStripSplitButton sb = (ToolStripSplitButton)e.Item; | |
| base.OnRenderSplitButtonBackground(e); | |
| // It doesn't matter what colour of arrow we tell it to draw. OnRenderArrow will compute it from the item anyway. | |
| OnRenderArrow(new ToolStripArrowRenderEventArgs(e.Graphics, sb, sb.DropDownButtonBounds, Color.Red, ArrowDirection.Down)); | |
| } else { | |
| base.OnRenderSplitButtonBackground(e); | |
| } | |
| } | |
| Color GetItemTextColor(ToolStripItem item) { | |
| int partId = item.IsOnDropDown ? (int)MenuParts.PopupItem : (int)MenuParts.BarItem; | |
| renderer.SetParameters(MenuClass, partId, GetItemState(item)); | |
| return renderer.GetColor(ColorProperty.TextColor); | |
| } | |
| protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) { | |
| if (EnsureRenderer()) | |
| e.TextColor = GetItemTextColor(e.Item); | |
| base.OnRenderItemText(e); | |
| } | |
| protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| if (e.ToolStrip.IsDropDown) { | |
| renderer.SetParameters(MenuClass, (int)MenuParts.PopupGutter, 0); | |
| // The AffectedBounds is usually too small, way too small to look right. Instead of using that, | |
| // use the AffectedBounds but with the right width. Then narrow the rectangle to the correct edge | |
| // based on whether or not it's RTL. (It doesn't need to be narrowed to an edge in LTR mode, but let's | |
| // do that anyway.) | |
| // Using the DisplayRectangle gets roughly the right size so that the separator is closer to the text. | |
| Padding margins = GetThemeMargins(e.Graphics, MarginTypes.Sizing); | |
| int extraWidth = (e.ToolStrip.Width - e.ToolStrip.DisplayRectangle.Width - margins.Left - margins.Right - 1) - e.AffectedBounds.Width; | |
| Rectangle rect = e.AffectedBounds; | |
| rect.Y += 2; | |
| rect.Height -= 4; | |
| int sepWidth = renderer.GetPartSize(e.Graphics, ThemeSizeType.True).Width; | |
| if (e.ToolStrip.RightToLeft == RightToLeft.Yes) { | |
| rect = new Rectangle(rect.X - extraWidth, rect.Y, sepWidth, rect.Height); | |
| rect.X += sepWidth; | |
| } else { | |
| rect = new Rectangle(rect.Width + extraWidth - sepWidth, rect.Y, sepWidth, rect.Height); | |
| } | |
| renderer.DrawBackground(e.Graphics, rect); | |
| } | |
| } else { | |
| base.OnRenderImageMargin(e); | |
| } | |
| } | |
| protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) { | |
| if (e.ToolStrip.IsDropDown && EnsureRenderer()) { | |
| renderer.SetParameters(MenuClass, (int)MenuParts.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) { | |
| if (EnsureRenderer()) { | |
| Rectangle bgRect = GetBackgroundRectangle(e.Item); | |
| bgRect.Width = bgRect.Height; | |
| // Now, mirror its position if the menu item is RTL. | |
| if (e.Item.RightToLeft == RightToLeft.Yes) | |
| bgRect = new Rectangle(e.ToolStrip.ClientSize.Width - bgRect.X - bgRect.Width, bgRect.Y, bgRect.Width, bgRect.Height); | |
| renderer.SetParameters(MenuClass, (int)MenuParts.PopupCheckBackground, e.Item.Enabled ? (int)MenuPopupCheckBackgroundStates.Normal : (int)MenuPopupCheckBackgroundStates.Disabled); | |
| renderer.DrawBackground(e.Graphics, bgRect); | |
| Rectangle checkRect = e.ImageRectangle; | |
| checkRect.X = bgRect.X + bgRect.Width / 2 - checkRect.Width / 2; | |
| checkRect.Y = bgRect.Y + bgRect.Height / 2 - checkRect.Height / 2; | |
| // I don't think ToolStrip even supports radio box items, so no need to render them. | |
| renderer.SetParameters(MenuClass, (int)MenuParts.PopupCheck, e.Item.Enabled ? (int)MenuPopupCheckStates.CheckmarkNormal : (int)MenuPopupCheckStates.CheckmarkDisabled); | |
| renderer.DrawBackground(e.Graphics, checkRect); | |
| } else { | |
| base.OnRenderItemCheck(e); | |
| } | |
| } | |
| protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { | |
| // The default renderer will draw an arrow for us (the UXTheme API seems not to have one for all directions), | |
| // but it will get the colour wrong in many cases. The text colour is probably the best colour to use. | |
| if (EnsureRenderer()) | |
| e.ArrowColor = GetItemTextColor(e.Item); | |
| base.OnRenderArrow(e); | |
| } | |
| protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e) { | |
| if (EnsureRenderer()) { | |
| // BrowserTabBar::Rebar draws the chevron using the default background. Odd. | |
| string rebarClass = RebarClass; | |
| if (Theme == ToolbarTheme.BrowserTabBar) | |
| rebarClass = "Rebar"; | |
| int state = VisualStyleElement.Rebar.Chevron.Normal.State; | |
| if (e.Item.Pressed) | |
| state = VisualStyleElement.Rebar.Chevron.Pressed.State; | |
| else if (e.Item.Selected) | |
| state = VisualStyleElement.Rebar.Chevron.Hot.State; | |
| renderer.SetParameters(rebarClass, VisualStyleElement.Rebar.Chevron.Normal.Part, state); | |
| renderer.DrawBackground(e.Graphics, new Rectangle(Point.Empty, e.Item.Size)); | |
| } else { | |
| base.OnRenderOverflowButtonBackground(e); | |
| } | |
| } | |
| public bool IsSupported { | |
| get { | |
| if (!VisualStyleRenderer.IsSupported) | |
| return false; | |
| // Needs a more robust check. It seems mono supports very different style sets. | |
| return | |
| VisualStyleRenderer.IsElementDefined( | |
| VisualStyleElement.CreateElement("Menu", | |
| (int)MenuParts.BarBackground, | |
| (int)MenuBarStates.Active)); | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment