Fensterrahmen anpassen mit dem WindowChrome

    • WPF

      Fensterrahmen anpassen mit dem WindowChrome

      Dieses und weitere WPF-Tutorials auf SeriTools.de
      Beim Übertragen des Tutorials kann ich leider manche Stile, Markierungen nicht übernehmen, die "Vollversion" gibts nur auf meiner Seite. Inhaltlich sind diese aber gleich.


      Nach den in .NET 4.0 eingeführten taskbarbezogenen Klassen (#1; #2) im Namespace System.Windows.Shell wurde dort mit dem .NET-Framework 4.5 eine weitere Klasse eingeführt: Der WindowChrome.
      Der WindowChrome wird auch als “non-client area” bezeichnet, also der Bereich des Fensters, dessen Rendering vom Programm selbst nicht gesteuert werden kann, kurz gesagt: Der Fensterrahmen. Nun ja, er lässt sich doch steuern: Die WindowChrome-Klasse bietet allerlei Möglichkeiten zur Beeinflussung oder Neuerstellung des Fensterrahmens, und verhindert WinAPI-Frickelei.
      Übrigens: Der WindowChrome ist nur für die Aero/DWM-Oberfläche verfügbar, also ab Vista aufwärts. Bei Vista/7 muss Aero angeschaltet sein, bei Windows 8 ist das DWM-Desktop-Compositing immer eingeschaltet. Ist DWM/Aero ausgeschaltet, werden die betroffenen Stellen im Programm schwarz angezeigt.

      Aufbau des WindowChromes

      Das Hauptaugenmerk der WindowChrome-Klasse liegt auf der gleichnamigen Attached Property WindowChrome.WindowChrome. Diese fügt man – entweder per Style oder direkt – an ein Fenster an, erstellt in dieser ein <WindowChrome /> und kann dort die wichtigsten Einstellungen vornehmen.
      Zunächst ist allerdings zu beachten, dass durch das alleinige Hinzufügen von diesem Objekt die Renderfläche (die client-area) des Fensters auf die gesamte Breite und Höhe – inklusive Fensterrahmen – vergrößert wird. Deshalb sieht man bei folgendem Code nur ein weißes Fenster, denn die Standardhintergrundfarbe ist weiß. Darüber hinaus wird der Fenstertitel ausgeblendet.

      XML-Quellcode

      1. <Window [...]>
      2. <WindowChrome.WindowChrome>
      3. <WindowChrome />
      4. </WindowChrome.WindowChrome>
      5. <Grid />
      6. </Window>


      Um den eigentlichen Fensterrahmen nun wieder sichtbar zu machen, muss die Hintergrundfarbe des Fensters auf Transparent gesetzt werden. Nun ist der Fensterrahmen wieder zu sehen; die urspüngliche Client-Area ist nun schwarz.
      Eine andere, in den meisten Fällen flexiblere Lösung für den WindowChrome ist das Setzen eines Styles inklusive Template:

      XML-Quellcode

      1. <Window x:Class="WpfCustomWindowChrome.MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:this="clr-namespace:WpfCustomWindowChrome"
      5. Width="300" Height="150" Background="Transparent">
      6. <Window.Resources>
      7. <Style TargetType="this:MainWindow">
      8. <Setter Property="WindowChrome.WindowChrome">
      9. <Setter.Value>
      10. <WindowChrome GlassFrameThickness="-1" NonClientFrameEdges="Bottom" />
      11. </Setter.Value>
      12. </Setter>
      13. <Setter Property="Template">
      14. <Setter.Value>
      15. <ControlTemplate TargetType="this:MainWindow">
      16. <Grid>
      17. <RadioButton Margin="15,7">
      18. <TextBlock>RadioBox in der <Bold>Titelleiste?</Bold></TextBlock>
      19. </RadioButton>
      20. <ContentPresenter Margin="{x:Static SystemParameters.WindowNonClientFrameThickness}" />
      21. </Grid>
      22. </ControlTemplate>
      23. </Setter.Value>
      24. </Setter>
      25. </Style>
      26. </Window.Resources>
      27. <Grid>
      28. <Button />
      29. </Grid>
      30. </Window>

      (RAW-Code)

      Im Style wird der WindowChrome festgelegt. Ins ControlTemplate kommt alles, was “außerhalb” des normalen Fensterinhalts sein soll, der normale Inhalt gehört nach wie vor in Window.Content. Der Margin des ContentPresenters ist hier aufSystemParameters.WindowNonClientFrameThickness gesetzt, einer DependencyProperty, die die Dicke des Standard-DWM/Aero-Rahmens zurückgibt. So können Sie den Inhalt des Fensters auf elegante Weise in den Rahmen einpassen. Die weiteren benutzten Eigenschaften stelle ich weiter unten vor.
      Es gibt allerdings ein Problem: Wird das Fenster maximiert, verschieben sich die Inhalte einige Pixel zum Rand hin. Das liegt an der Methode, wie Windows Fenster maximiert: Schaut man sich die Fensterposition eines maximierten Fensters an, so liegt diese im negativen Bereich, also außerhalb der sichtbaren Arbeitsfläche. Diesem können Sie entgegengewirken, indem Sie einen Border um das Grid im ControlTemplate setzen, welcher durch einen Trigger seine Dicke von 0 auf die Dicke des Standard-ResizeBorders setzt.

      XML-Quellcode

      1. <ControlTemplate TargetType="this:MainWindow">
      2. <Border>
      3. <Border.Style>
      4. <Style TargetType="{x:Type Border}">
      5. <Setter Property="BorderThickness" Value="0" />
      6. <Style.Triggers>
      7. <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowState}"
      8. Value="Maximized">
      9. <Setter Value="{x:Static SystemParameters.WindowResizeBorderThickness}"
      10. Property="BorderThickness" />
      11. </DataTrigger>
      12. </Style.Triggers>
      13. </Style>
      14. </Border.Style>
      15. <Grid>
      16. <RadioButton Margin="15,7">
      17. <TextBlock>RadioBox in der <Bold>Titelleiste?</Bold></TextBlock>
      18. </RadioButton>
      19. <ContentPresenter Margin="{x:Static SystemParameters.WindowNonClientFrameThickness}" />
      20. </Grid>
      21. </Border>
      22. </ControlTemplate>

      (RAW-Code), Code basiert auf diesem Beitrag bei StackOverflow.com.

      Nun aber zu den Eigenschaften des WindowChromes:

      Thickness GlassFrameThickness

      GlassFrameThickness=”15,40″

      Die Eigenschaft GlassFrameThickness dient zur Einstellung der Dicke des Standard-DWM/Aero-Fensterrahmens. Um den DWM/Aero-Rahmen auf die gesamte Fläche auszuweiten, übergeben Sie den Wert -1. Im Codebehind kann man stattdessen auch WindowChrome.GlassFrameCompleteThicknessverwenden. Der Wert 0 deaktiviert den DWM/Aero-Rahmen.
      Achtung: Der GlassFrame besitzt ein merkwürdiges Verhalten im Zusammenhang mit maximierten Fenstern (DWM/Glass wird plötzlich schwarz, merkwürdige, transparente Ränder an den anderes Seiten). Die Lösung finden Sie weiter unten, bei der Eigenschaft NonClientFrameEdges.
      Falls die Eigenschaft nicht gesetzt wird, liest der WindowChrome den Standardwert aus Windows aus. Der Windows-Standardwert liegt bei "8,30,8,8".

      Thickness ResizeBorderThickness

      Ähnlich wie die DWM-Rahmendicke lässt sich auch der ResizeBorder einstellen. Die WindowChrome.ResizeBorderThickness gibt die Größe des Randes des Fensters an, an welchem das Fenster mit der Maus in der Größe verändert werden kann. Der Wert 0 deaktiviert diesen Rand, positive Werte setzen ihn neu.
      Falls die Eigenschaft nicht gesetzt wird, liest der WindowChrome den Standardwert aus Windows aus. Der Windows-Standardwert liegt bei "8,8,8,8".


      Double CaptionHeight

      Die CaptionHeight gibt die Höhe der Titelleiste eines Fensters an, das heißt, den oberen Bereich des Fensters, an dem das Fenster bewegen werden kann und in welchem das Systemmenü (Rechtsklickmenü zum Maximieren, Minimieren, Verschieben, Größe Verändern und Schließen) geöffnet werden kann.
      Achtung: Standardmäßig wird alles, was auf diesem oder dem Rand des ResizeBorders (siehe oben) liegt, aus dem Hit-Testing von WPF ausgeschlossen, das heißt, Controls erhalten dort keine Maus-Events. Es gibt eine Möglichkeit dieses Verhalten zu verändern, die Attached Property WindowChrome.IsHitTestVisibleInChrome. Ich werde diese an späterer Stelle weiter unten erklären.
      Wie auch bei den vorherigen Eigenschaften: Wird sie nicht gesetzt, liest der WindowChrome den Standardwert selbstständig aus. Dieser liegt im Normalfall bei 30.


      Boolean UseAeroCaptionButtons

      Um die Funktion der Standardknöpfe Schließen, Maximieren/Wiederherstellen und Minimieren zu deaktivieren, setzen Sie diese Eigenschaft auf False. Beachten Sie, dass diese Eigenschaft nur die Funktion der Buttons deaktiviert, nicht aber deren Anzeige. Möchten Sie ein Fenster ohne Knöpfe, setzen sie die Eigenschaft WindowStyle des Windows auf None und die GlassFrameThickness und die CaptionHeight auf Ihre gewünschten Werte.
      Der Standardwert dieser Eigenschaft ist True.


      CornerRadius CornerRadius


      CornerRadius=”50,15,50,15″

      Ist der DWM/Aero-Rahmen ausgeschaltet, ist also die GlassFrameThickness auf 0 gesetzt, können Sie mit dieser Eigenschaft den Radius der Fensterecken einstellen. Bereiche außerhalb dieser Radien werden vom Fenster zum darunterliegenden Fenster/Objekt durchgelassen. Der Rand besitzt keine Kantenglättung, wirkt also ziemlich pixelig.
      Auch diese Eigenschaft bezieht ihren Standardwert aus Windows, dieser liegt im Normalfall bei 8.


      [Flags] NonClientFrameEdges NonClientFrameEdges


      NonClientFrameEdges =”Left”
      NonClientFrameEdges =”None”


      Nun komme ich zu der Eigenschaft, die die Probleme mit dem GlassFrame und maximierten Fenstern behebt.
      Vorab: Die Lösung ist, diese Eigenschaft auf einen anderen Wert als den Standardwert None zu setzen.
      Jetzt aber zur eigentlichen Erklärung dieser Eigenschaft:NonClientFrameEdges beinhaltet die Seitenränder des Fensters, die noch – im Gegensatz zum restlichen Fenster – zum Non-Client-Bereich gehören sollen, was bedeutet, dass diese vom System gezeichnet/gerendert werden und somit nicht überschrieben werden können. Dies betrifft allerdings jeweils nur die äußersten Pixel, wie auf den Bildern zu erkennen ist. Auch eine Erhöhung der GlassFrameThickness ändert daran nichts.
      Anzumerken ist, dass die Werte None, Left, Bottom, Right und Top lauten, und dass sich diese aufgrund des Flags-Attributes miteinander kombinieren lassen. Die einzige nicht mögliche Kombination ist "Left,Right,Top,Bottom". None hingegen lässt sich mit den anderen kombinieren.
      Wird Top verwendet, deaktiviert dies die drei Titelleistenbuttons, genauso wie UseAeroCaptionButtons="True".
      Um das Problem mit den maximierten Fenstern bei gleichzeitig aktiviertem GlassFrame nun zu umgehen, muss mindestens einer der Werte außer None gesetzt werden. Welchen davon Sie setzen, oder wie viele Sie setzen, ist völlig egal, solange mindestens ein Wert davon ungleich None ist.
      Übrigens: Ausgewählte Werte aus Enumerationen mit Flags-Attribut werden in XAML durch Kommas getrennt.

      Attached Properties

      Boolean IsHitTestVisibleInChrome

      Wie weiter oben schon erwähnt, ist die Attached Property WindowChrome.IsHitTestVisibleInChrome dafür zuständig, ein Steuerelement auch auf dem ResizeBorder und in der Titelleiste für Mausinteraktion zugänglich zu machen. Logischerweise lassen sich an dieser Position weder das Fenster verschieben oder skalieren noch das Systemmenü öffnen.

      XML-Quellcode

      1. <Button Grid.Row="0" [...] WindowChrome.IsHitTestVisibleInChrome="True">Non-Client!</Button>


      ResizeGripDirection ResizeGripDirection

      Möchte man zusätzlich zum oder anstatt des ResizeBorders lieber einzelne ResizeGrips haben, also Ankerpunkte, an denen das Fenster skaliert werden kann. Man kann aus jedem Steuerelement ein solches Resizegrip machen. Die erlaubten Werte sind: None (schaltet die Funktion aus), TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft und Left. Die Werte sagen aus, von wo aus sich das Fenster vergrößert/verkleinert.

      XML-Quellcode

      1. <Button [...] WindowChrome.ResizeGripDirection="BottomRight">ResizeGrip</Button>
      | Keine Fragen per PN oder Skype.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „SeriTools“ ()