Visual Styles / Das Betriebssystem Controls zeichnen lassen

    • Allgemein
    • .NET (FX) 4.0

    Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von nafets.

      Visual Styles / Das Betriebssystem Controls zeichnen lassen

      Moin,
      da sich hier nur wenige Leute Visual Styles kennen, möchte ich sie hier mal vorstellen und Verwendungsmöglichkeiten vorstellen.

      Vorweg
      Dieses Thema ist für Leute gedacht, die sich schon mit GDI+ und im Idealfall auch mit der Erstellung von eigenen Controls auskennen. Die Codes sind alle in C# geschrieben, lassen sich aber ohne weiteres über einen Konverter übersetzen.

      Nun zum Thema
      Bei Visual Styles lässt man Windows die Sachen zeichnen, anstatt dass man es selbst macht. Das hat den Vorteil, dass sich erstellte Controls immer dem aktuellen Theme von Windows anpassen und dass sich der benötigte Code stark reduziert.
      Als Beispiel möchte ich euch zeigen, wie man einen NavigationButton zeichnen kann - das gleiche Control wie Artentus in seinem Projekt [OpenSource] ExplorerNavigationButton von Hand erstellt hat.
      Aber erstmal zu den Basics:
      Will man Windows Controls zeichnen lassen, macht man das mit Hilfe des System.Windows.Forms.VisualStyles-Namespace. Um ein Control zu zeichnen, braucht man nun einen VisualStyleRenderer, wofür man wiederum ein VisualStyleElement braucht. Dieses Element repräsentiert einen Teil eines bestimmten Controls. Man kriegt dieses Element entweder über die Unterklassen (z.B. VisualStyleElement.Button.PushButton.Default) oder über die CreateElement-Methode. Da die Unterklassen nicht komplett sind, holen wir uns mal ein Element über seine ID:

      C#-Quellcode

      1. VisualStyleElement NavigationBackButton = VisualStyleElement.CreateElement("Navigation", 1, 2);

      Hier ist nun einiges an Erklärung fällig:
      "Navigation" ist der Name der Klasse, in der sich das gewünschte Element befindet. Die 1 ist die PartId - hier die Id für einen nach links zeigenden Navigation-Button. Die 2 ist die StateId, sie bestimmt das genauere Aussehen, beim NavigationButton stellt sie den Hover-Status dar.
      Jetzt haben wir dieses Element aber noch nicht gezeichnet - dafür brauchen wir einen VisualStyleRenderer:

      C#-Quellcode

      1. VisualStyleRenderer NavigationBackButtonRenderer = new VisualStyleRenderer(NavigationBackButton);

      Nun müssen wir diesen Button nur noch zeichnen - ich mach das mal im Paint-Event:

      C#-Quellcode

      1. protected override void OnPaint(PaintEventArgs e)
      2. {
      3. VisualStyleElement NavigationBackButton = VisualStyleElement.CreateElement("Navigation", 1, 2);
      4. VisualStyleRenderer NavigationBackButtonRenderer = new VisualStyleRenderer(NavigationBackButton);
      5. NavigationBackButtonRenderer.DrawBackground(e.Graphics, new Rectangle(0, 0, 30, 30));
      6. base.OnPaint(e);
      7. }

      Und schon haben wir unseren NavigationButton gerendert:
      Hier noch eine mögliche Umsetzung des kompletten NavigationButton (Auszug aus der Library, an welcher ich gerade arbeite):
      NavigationButton Umsetzung
      Control-Klasse

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.ComponentModel;
      4. using System.Diagnostics;
      5. using System.Drawing;
      6. using System.Linq;
      7. using System.Text;
      8. using System.Threading.Tasks;
      9. using System.Windows.Forms;
      10. using VisualStyleControls.Rendering;
      11. namespace VisualStyleControls.Controls
      12. {
      13. /// <summary>
      14. /// A simple Back/Forward Button drawn by Windows via Visual Styles.
      15. /// </summary>
      16. [ToolboxBitmap(typeof(Button))]
      17. [Designer(typeof(NavigationButtonDesigner))]
      18. [DefaultEvent("Click")]
      19. [Description("A simple Back/Forward Button drawn by Windows via Visual Styles")]
      20. public class NavigationButton
      21. : Control
      22. {
      23. /// <summary>
      24. /// Initializes a new instance of the <see cref="T:VisualStyleControls.Controls.NavigationButton"/> class.
      25. /// </summary>
      26. public NavigationButton()
      27. : base()
      28. {
      29. this.SuspendLayout();
      30. this.Size = new Size(30, 30);
      31. this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
      32. this.UpdateStyles();
      33. this.ResumeLayout(false);
      34. }
      35. /// <summary>
      36. /// Gets raised if the arrow direction has changed.
      37. /// </summary>
      38. [Category("Appearance")]
      39. [Description("Gets raised when the Button Type got changed.")]
      40. public event EventHandler ButtonTypeChanged;
      41. /// <summary>
      42. /// Raises the <see cref="E:VisualStyleControls.Controls.NavigationButton.ButtonTypeChanged"/> event.
      43. /// </summary>
      44. protected virtual void OnButtonTypeChanged()
      45. {
      46. if (this.ButtonTypeChanged != null)
      47. {
      48. this.ButtonTypeChanged(this, EventArgs.Empty);
      49. }
      50. }
      51. private NavigationButtonType type = NavigationButtonType.Back;
      52. private VisualStyleControls.Rendering.ButtonState state = VisualStyleControls.Rendering.ButtonState.Normal;
      53. /// <summary>
      54. /// Indicates the Type of this Button.
      55. /// </summary>
      56. /// <value>
      57. /// The current Type.
      58. /// </value>
      59. [Category("Appearance")]
      60. [Description("Indicates the Type of this Button.")]
      61. public NavigationButtonType ButtonType
      62. {
      63. get
      64. {
      65. return this.type;
      66. }
      67. set
      68. {
      69. if (value != this.type)
      70. {
      71. this.type = value;
      72. this.Invalidate();
      73. this.OnButtonTypeChanged();
      74. }
      75. }
      76. }
      77. protected override void OnPaint(PaintEventArgs e)
      78. {
      79. switch (this.ButtonType)
      80. {
      81. case NavigationButtonType.Back:
      82. NavigationButtonRenderer.RenderBackButton(e.Graphics, new Rectangle(0, 0, this.Width, this.Height), this.Enabled ? this.state : VisualStyleControls.Rendering.ButtonState.Disabled);
      83. break;
      84. case NavigationButtonType.Forward:
      85. NavigationButtonRenderer.RenderForwardButton(e.Graphics, new Rectangle(0, 0, this.Width, this.Height), this.Enabled ? this.state : VisualStyleControls.Rendering.ButtonState.Disabled);
      86. break;
      87. default:
      88. Debug.WriteLine("Really don't know what happened here but the ButtonType property has a value nonexistent in NavigationButtonType");
      89. break;
      90. }
      91. base.OnPaint(e);
      92. }
      93. protected override void OnMouseEnter(EventArgs e)
      94. {
      95. this.state = VisualStyleControls.Rendering.ButtonState.Hot;
      96. this.Invalidate();
      97. base.OnMouseEnter(e);
      98. }
      99. protected override void OnMouseLeave(EventArgs e)
      100. {
      101. this.state = VisualStyleControls.Rendering.ButtonState.Normal;
      102. this.Invalidate();
      103. base.OnMouseLeave(e);
      104. }
      105. protected override void OnMouseDown(MouseEventArgs e)
      106. {
      107. this.state = VisualStyleControls.Rendering.ButtonState.Pressed;
      108. this.Invalidate();
      109. base.OnMouseDown(e);
      110. }
      111. protected override void OnMouseUp(MouseEventArgs e)
      112. {
      113. this.state = (e.X >= 0 && e.X < this.Width && e.Y >= 0 && e.Y < this.Height) ? VisualStyleControls.Rendering.ButtonState.Hot : VisualStyleControls.Rendering.ButtonState.Normal;
      114. this.Invalidate();
      115. base.OnMouseUp(e);
      116. }
      117. protected override void OnEnabledChanged(EventArgs e)
      118. {
      119. this.Invalidate();
      120. base.OnEnabledChanged(e);
      121. }
      122. }
      123. }

      Designer-Klasse

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.ComponentModel.Design;
      4. using System.Linq;
      5. using System.Text;
      6. using System.Threading.Tasks;
      7. using System.Windows.Forms.Design;
      8. namespace VisualStyleControls.Controls
      9. {
      10. public class NavigationButtonDesigner
      11. : ControlDesigner
      12. {
      13. private DesignerActionListCollection actionLists;
      14. public override DesignerActionListCollection ActionLists
      15. {
      16. get
      17. {
      18. return this.actionLists != null ?
      19. this.actionLists :
      20. this.actionLists = new DesignerActionListCollection(new DesignerActionList[] { new NavigationButtonDesignerActionList(this.Component) });
      21. }
      22. }
      23. public override SelectionRules SelectionRules
      24. {
      25. get
      26. {
      27. return SelectionRules.Moveable;
      28. }
      29. }
      30. }
      31. }
      32. //-----------------------
      33. using System;
      34. using System.Collections.Generic;
      35. using System.ComponentModel;
      36. using System.ComponentModel.Design;
      37. using System.Linq;
      38. using System.Text;
      39. using System.Threading.Tasks;
      40. namespace VisualStyleControls.Controls
      41. {
      42. /// <summary>
      43. /// Provides a DesignerActionList for the <see cref="T:VisualStyleControls.Controls.NavigationButton"/> Control.
      44. /// </summary>
      45. public class NavigationButtonDesignerActionList
      46. : DesignerActionListBase<NavigationButton>
      47. {
      48. /// <summary>
      49. /// Initializes a new instance of the <see cref="NavigationButtonDesignerActionList"/> class.
      50. /// </summary>
      51. /// <param name="component">The component.</param>
      52. public NavigationButtonDesignerActionList(IComponent component)
      53. : base(component)
      54. { }
      55. /// <summary>
      56. /// Returns a list of <see cref="T:System.ComponentModel.Design.DesignerActionItem"/> representing the DesignerActionList items.
      57. /// </summary>
      58. /// <returns>
      59. /// A <see cref="T:System.ComponentModel.Design.DesignerActionItem"/>-Array.
      60. /// </returns>
      61. public override DesignerActionItemCollection GetSortedActionItems()
      62. {
      63. DesignerActionItemCollection aItems = new DesignerActionItemCollection();
      64. aItems.Add(new DesignerActionPropertyItem("ButtonType", "Type", "Appearance", "Indicates the Type of this Button."));
      65. return aItems;
      66. }
      67. /// <summary>
      68. /// Indicates the Type of the targeted <see cref="T:VisualStyleControls.Controls.NavigationButton"/>.
      69. /// </summary>
      70. /// <value>
      71. /// The current Type.
      72. /// </value>
      73. public NavigationButtonType ButtonType
      74. {
      75. get
      76. {
      77. return this.TargetComponent.ButtonType;
      78. }
      79. set
      80. {
      81. this.TargetComponent.ButtonType = value;
      82. this.DesignerActionUIService.Refresh(this.Component);
      83. }
      84. }
      85. }
      86. }
      87. //----------------
      88. using System;
      89. using System.Collections.Generic;
      90. using System.ComponentModel;
      91. using System.ComponentModel.Design;
      92. using System.Linq;
      93. using System.Text;
      94. using System.Threading.Tasks;
      95. namespace VisualStyleControls.Controls.Helpers
      96. {
      97. /// <summary>
      98. /// A generic DesignerActionList.
      99. /// </summary>
      100. public abstract class DesignerActionListBase<T>
      101. : DesignerActionList
      102. where T : class, IComponent
      103. {
      104. /// <summary>
      105. /// Initializes a new instance of the <see cref="T:VisualStyleControls.Controls.Helpers.DesignerActionListBase"/> class.
      106. /// </summary>
      107. /// <param name="component">A component related to this <see cref="T:VisualStyleControls.Controls.DesignerActionListBase"/>.</param>
      108. public DesignerActionListBase(IComponent component)
      109. : base(component)
      110. {
      111. this.TargetComponent = component as T;
      112. this.DesignerActionUIService = this.GetService(typeof(DesignerActionUIService)) as DesignerActionUIService;
      113. }
      114. /// <summary>
      115. /// Returns the <see cref="T:System.ComponentModel.IComponent"/> this <see cref="T:VisualStyleControls.Controls.Helpers.DesignerActionListBase"/> is targeted at.
      116. /// </summary>
      117. /// <value>
      118. /// The target component.
      119. /// </value>
      120. protected T TargetComponent { get; private set; }
      121. /// <summary>
      122. /// Returns the <see cref="T:System.ComponentModel.Design.DesignerActionUIService"/>.
      123. /// </summary>
      124. /// <value>
      125. /// The designer action UI service.
      126. /// </value>
      127. protected DesignerActionUIService DesignerActionUIService { get; private set; }
      128. /// <summary>
      129. /// Returns a list of <see cref="T:System.ComponentModel.Design.DesignerActionItem"/> representing the DesignerActionList items.
      130. /// </summary>
      131. /// <returns>
      132. /// A <see cref="T:System.ComponentModel.Design.DesignerActionItem"/>-Array.
      133. /// </returns>
      134. public override DesignerActionItemCollection GetSortedActionItems()
      135. {
      136. throw new NotImplementedException();
      137. }
      138. }
      139. }

      Renderer-Klasse

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Drawing;
      4. using System.Linq;
      5. using System.Text;
      6. using System.Threading.Tasks;
      7. using System.Windows.Forms.VisualStyles;
      8. namespace VisualStyleControls.Rendering
      9. {
      10. /// <summary>
      11. /// Renders the Windows Navigation Button
      12. /// </summary>
      13. public static class NavigationButtonRenderer
      14. {
      15. /// <summary>
      16. /// Renders a Windows Navigation Button BackButton
      17. /// </summary>
      18. /// <param name="dc">The dc to draw to (you can also pass a <see cref="System.Drawing.Graphics"/> object).</param>
      19. /// <param name="bounds">The bounds.</param>
      20. /// <param name="state">The state.</param>
      21. public static void RenderBackButton(IDeviceContext dc, Rectangle bounds, ButtonState state)
      22. {
      23. new VisualStyleRenderer(NavigationButtonRenderer.CreateBackButtonElement(state)).DrawBackground(dc, bounds);
      24. }
      25. /// <summary>
      26. /// Creates a Windows Navigation Button BackButton Visual Style Element
      27. /// </summary>
      28. /// <param name="state">The state.</param>
      29. public static VisualStyleElement CreateBackButtonElement(ButtonState state)
      30. {
      31. return VisualStyleElement.CreateElement("Navigation", 1, (int)state);
      32. }
      33. /// <summary>
      34. /// Renders a Windows Navigation Button ForwardButton
      35. /// </summary>
      36. /// <param name="dc">The dc to draw to (you can also pass a <see cref="System.Drawing.Graphics"/> object).</param>
      37. /// <param name="bounds">The bounds.</param>
      38. /// <param name="state">The state.</param>
      39. public static void RenderForwardButton(IDeviceContext dc, Rectangle bounds, ButtonState state)
      40. {
      41. new VisualStyleRenderer(NavigationButtonRenderer.CreateForwardButtonElement(state)).DrawBackground(dc, bounds);
      42. }
      43. /// <summary>
      44. /// Creates a Windows Navigation Button ForwardButton Visual Style Element
      45. /// </summary>
      46. /// <param name="state">The state.</param>
      47. public static VisualStyleElement CreateForwardButtonElement(ButtonState state)
      48. {
      49. return VisualStyleElement.CreateElement("Navigation", 2, (int)state);
      50. }
      51. /// <summary>
      52. /// Renders a Windows Navigation Button MenuButton
      53. /// </summary>
      54. /// <param name="dc">The dc to draw to (you can also pass a <see cref="System.Drawing.Graphics"/> object).</param>
      55. /// <param name="bounds">The bounds.</param>
      56. /// <param name="state">The state.</param>
      57. public static void RenderMenuButton(IDeviceContext dc, Rectangle bounds, ButtonState state)
      58. {
      59. new VisualStyleRenderer(NavigationButtonRenderer.CreateMenuButtonElement(state)).DrawBackground(dc, bounds);
      60. }
      61. /// <summary>
      62. /// Creates a Windows Navigation Button MenuButton Visual Style Element
      63. /// </summary>
      64. /// <param name="state">The state.</param>
      65. public static VisualStyleElement CreateMenuButtonElement(ButtonState state)
      66. {
      67. return VisualStyleElement.CreateElement("Navigation", 3, (int)state);
      68. }
      69. }
      70. }
      71. //-------------------------
      72. using System;
      73. using System.Collections.Generic;
      74. using System.Linq;
      75. using System.Text;
      76. using System.Threading.Tasks;
      77. namespace VisualStyleControls.Rendering
      78. {
      79. /// <summary>
      80. /// Represents a Button state
      81. /// </summary>
      82. public enum ButtonState
      83. : int
      84. {
      85. /// <summary>
      86. /// The normal state
      87. /// </summary>
      88. Normal = 1,
      89. /// <summary>
      90. /// The hot/hover state
      91. /// </summary>
      92. Hot = 2,
      93. /// <summary>
      94. /// The pressed state
      95. /// </summary>
      96. Pressed = 3,
      97. /// <summary>
      98. /// The disabled state
      99. /// </summary>
      100. Disabled = 4
      101. }
      102. }

      Dieser Code ließe sich aber natürlich bei Bedarf um ein Vielfaches verkürzen - dies ist ja wie gesagt ein Auszug aus einer Library.
      [line]​[/line]
      Hier möchte ich auch noch meine VisualStyle-Library vorstellen. Sie ist noch in Arbeit, aber schon komplett Nutzbar. Ich stelle hier einfach mal ein Klassendiagramm (nur die Renderer-Klassen, die Enums hab ich weg gelassen - das Bild sollte man direkt anzeigen lassen, um auch nur irgendwas lesen zu können) und die Projektmappe zur Verfügung:

      Die Projektmappe befindet sich im Anhang (@Mods: Es sind keine ausführbaren Dateien vorhanden ;))

      Hoffentlich konnte ich mit diesem kleinen Artikel etwas auf die VisualStyles aufmerksam machen und vielleicht wird sich ja auch der ein oder andere damit beschäftigen.

      Grüße,
      Stefan
      Dateien
      Nice 8-)

      aber zwei Sachen:

      1. Es gibt eine Beschränkung der Größe (also selbst wenn man für die parameter eine größere größe wählt, bleibt das bild gleichgroß)
      und 2. (ist jetzt ne kleinigkeit):

      C#-Quellcode

      1. VisualStyleElement NavigationBackButton = VisualStyleElement.CreateElement("Navigation", 1, 2);
      2. VisualStyleRenderer NavigationBackButtonRenderer = new VisualStyleRenderer(NavigationBackButton);


      kannst du auch ausserhalb der paint prozedur verlagern weil sonst wird immer ne neue instanz erstellt obwohl man die nur einmal braucht.

      Was ich noch fragen wollte,

      gibt es eine Auflistung von allen Klassen (also wie Navigation) die man sich iwo ansehen kann?
      Ich hab mir die einfach ausm Internet zusammengesucht. Du kannst auch einfach den Wrapper von mir verwenden und bei Bedarf natürlich auch modifizieren, da sind alle wichtigen Elemente drin (außer TreeView, Menu & ListView, weil die bei mir nicht funktioniert haben).
      Hmm - guck - ich fand im ObjectBrowser ganz viele vorgefertigte VisualStyles

      Ich hätte jetzt gedacht, wenn du einen ComboboxRenderer geschrieben hast, dass man den irgendwie über diese vorgefertigten Styles mit einem Standard-Projekt verlinken kann, sodass alle Combos des Projektes mit deinem Renderer gezeichnet werden, wenn man die Lib einbindet, und im Startup EnableVisualStyles() aufruft.
      Aber passier nix.
      Also konzeptionell würde ich von einem Style-Konzept erwarten, dass man keine Controls selber coden oder beerben muss, sondern dass man den bestehenden Controls irgendwie einen Style zuordnet, und dann erscheinen sie im neuen Gewand.
      Und meine OB-Liste sieht auch danach aus, dass das so vorgesehen ist, aber ich komm nicht drauf, wie.
      Oder hast du eine Ahnung, warum im Framework mehr als 200 VisualStyles vorgefertigt sind, und was man mit denen anfängt?
      Die Liste ist noch nicht mal in Ansätzen das, was mein Wrapper kann. Ich schätze, ich habe ca. 400 Parts (vs. ~200 im Framework) implementiert und viele von denen habe deutlich mehr States als die Framework-Version. Gesamt könnte es sein, dass ich zwischen 750-1000 States verfügbar gemacht habe, was viel mehr ist als das im Framework verfügbare. Außerdem hast du VisualStyles falsch verstanden - die sind nur eine Möglichkeit, Windows Elemente zeichnen zu lassen. Es würde nix bringen, die Formcontrols damit zeichnen zu lassen, weil man keinen Unterschied sehen würde.

      nafets schrieb:

      Außerdem hast du VisualStyles falsch verstanden - die sind nur eine Möglichkeit, Windows Elemente zeichnen zu lassen. Es würde nix bringen, die Formcontrols damit zeichnen zu lassen, weil man keinen Unterschied sehen würde.
      Das ist ja meine Frage: Welchen Sinn haben die eingebauten VisualStyles?
      Wieso gibts davon so viele im Framework, und was kann man damit anfangen?
      Ich habe keine Ahnung, wieso es im Framework diese paar Methoden gibt, ich denke aber, dass sie zur Renderung der Controls im Framework verwendet werden. Bei meiner Library gehts einfach darum, einen so gut wie kompletten Wrapper zu erstellen - im Gegensatz zu dem im Framework. Mein Ziel ist es, komplettere Versionen der Framework-Controls zu erstellen. Zum Beispiel hat eine CheckBox 4 States: Unchecked, Checked, Mixed und Excluded. Zumindest bei dem letzten habe ich keine Ahnung, wie man ihn im Framework benutzen könnte. Ähnliches bei der ProgressBar: Es ist möglich, sie 1:1 mit VisualStyles nachzuzeichnen, sodass es auch möglich wird, auf die normal animierte ProgressBar zu zeichnen. Oder man könnte auch ein verbessertes NumericUpDown mit dem Spin-VisualStyle zeichnen. Oder ein TabControl, bei welchem die Reiter an allen Seiten gut aussehen mit dem Tab-VisualStyle. Oder ein alternatives Startmenü mit dem StartPanel-VisualStyle. Oder die richtigen Aero-Labels mit dem TaskDialog-VisualStyle. Oder oder oder...