OnTopReplica/src/OnTopReplica/NativeToolStripRenderer.cs
Lorenz Cuno Klopfenstein a7774b6677 Code re-org
2018-04-10 18:52:40 +02:00

602 lines
20 KiB
C#

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
}
/// <summary>
/// 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
/// </summary>
class UXThemeToolStripRenderer : ToolStripSystemRenderer {
/// <summary>
/// It shouldn't be necessary to P/Invoke like this, however a bug in VisualStyleRenderer.GetMargins forces my hand.
/// </summary>
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
private static bool? _isSupportedCache = null;
public static bool IsSupported {
get {
if (_isSupportedCache.HasValue)
return _isSupportedCache.Value;
if (!VisualStyleRenderer.IsSupported) {
_isSupportedCache = false;
return false;
}
_isSupportedCache = VisualStyleRenderer.IsElementDefined(VisualStyleElement.CreateElement("MENU", (int)MenuParts.MENU_BARBACKGROUND, (int)MenuBarStates.MB_ACTIVE));
return _isSupportedCache.Value;
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <example>
/// NativeToolStripRenderer.SetToolStripRenderer(toolStrip1, toolStrip2, contextMenuStrip1);
/// </example>
/// <example>
/// toolStrip1.Renderer = new NativeToolStripRenderer();
/// </example>
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
/// <summary>
/// Creates a NativeToolStripRenderer for a particular ToolStrip. NativeToolStripRenderer will subscribe to some events
/// of this ToolStrip.
/// </summary>
/// <param name="toolStrip">The toolstrip for this NativeToolStripRenderer. NativeToolStripRenderer will subscribe to some events
/// of this ToolStrip.</param>
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
/// <summary>
/// Sets the renderer of each ToolStrip to a NativeToolStripRenderer. A convenience method.
/// </summary>
/// <param name="toolStrips">A parameter list of ToolStrips.</param>
[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
}
}