publish and discover academic work ...

WPF: Fenster mit Windows 8 Style

Hallo Kollegen!

Neulich musste ich mich wieder mit WPF beschäftigen. Diesmal wollte ich unbedingt etwas Interessantes basteln, deswegen habe ich mich entschieden ein bisschen mit dem Style zu experimentieren. Windows 8 ist im Moment aktuell und sein Style in aller Munde. Die Wahl eines geeigneten Style fiel damit auf auf Metro (wie heißt das Style jetzt übrigens?). Das Resultat des Experimentes sieht wie folgt aus:



Sie möchten wissen, wie Sie ein tolles Fenster basteln können? Ich lade Sie herzlich ein, den Artikel weiterzulesen.


Das Hauptproblem

WPF arbeitet nicht mit NC-area. NC steht für «Non-client area», sie wird auf einem tieferen Niveau bearbeitet. Falls Sie ein Element des Fensters wie z.B. Border oder Button ändern möchten, wird Ihnen empfohlen das Style des Windows vollständig neu zu machen, etwa so:

<Window
    AllowsTransparency="true"
    WindowStyle="None"> ...


Ich werde diesem Rat folgen und ein eigenes Style erstellen. Mein Ziel war es auch, ohne fremden Bibliotheken auszukommen. Der Code für das Style befindet sich im Verzeichnis CustomizedWindow, das Fenster selbst im Verzeichnis des Projekts. Die Struktur des Projektes sieht wie folgt aus:



Style

Der Style für das Fenster wird mit einem ControlTemplate festgelegt. Der Inhalt liegt im ContentPresenter und die Funktionalität werden wir in C# umsetzen. Dafür wird das Attribut x:Class im ResourceDictionary benutzt. Für XAML ist das ganze, sehr üblich.

<ResourceDictionary
    x:Class="Whush.Demo.Styles.CustomizedWindow.VS2012WindowStyle">
    <Style x:Key="VS2012WindowStyle" TargetType="{x:Type Window}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <!-- XAML mit Border,Icon,Button usw. -->
                    <ContentPresenter />
                    <!—noch Fenster- XAML -->
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


Für die Verwaltung der Fenster benötigen wir Buttons, die ihre ursprüngliche Funktionalität behalten:



Style für Buttons
<Style x:Key="VS2012WindowStyleTitleBarButton" TargetType="{x:Type Button}">
    <Setter Property="Focusable" Value="false" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Grid>
                    <Border x:Name="border" Background="Transparent" />
                    <ContentPresenter />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="#FFF" />
                        <Setter TargetName="border" Property="Opacity" Value="0.7" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="border" Property="Background"
                            Value="{StaticResource VS2012WindowBorderBrush}"/>
                        <Setter TargetName="border" Property="Opacity" Value="1" />
                        <Setter Property="Foreground" Value="#FFF"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


Die Buttons bestehen aus Bildern oder Icons in Format einer Vektorgrafik. Der Code für den Maximize-Button sieht ungefähr so aus:

<Path StrokeThickness="1"
    RenderOptions.EdgeMode="Aliased"
    Data="M0,0 H8 V8 H0 V0 M0,1 H8 M0,2 H8" />


Für die Überschrift benutze ich Segoe UI. Hier müssen Sie jedoch aufpassen, dass Motion Blur nicht eingeschaltet ist, sonst wird der Text nicht klar genug dargestellt, etwa wie die Zweite Zeile auf dem Bild:



Genau für diese Zwecke gibt es die Eigenschaft EdgeMode=«Aliased» für Buttons und in Textblöcken gebe ich die Eigenschaft TextFormattingMode=«Display» an:

<TextBlock
    TextOptions.TextRenderingMode="ClearType"
    TextOptions.TextFormattingMode="Display" > ...


Interaktion mit Fenstern

Das Fenster darf nicht «einfrieren». Wir wollen es per Darg-and-Drop verschieben und seine Große ändern können. Um diese Funktionalität zu ermöglichen benutze ich transparente Kontroller (Controls). Ein Beispiel für die Ecke oben links:

<Rectangle
    x:Name="rectSizeNorthWest"
    MouseDown="OnSizeNorthWest"
    Cursor="SizeNWSE" Fill="Transparent"
    VerticalAlignment="Top" HorizontalAlignment="Left"
    Width="5" Height="5" />


Sie werden bemerken, dass das Element auf das Event „MouseDown“ reagiert und die Methode OnSizeNorthWest aufruft:

void OnSizeNorthWest(object sender) {
    if (Mouse.LeftButton == MouseButtonState.Pressed) {
        Window window = ((FrameworkElement)sender).TemplatedParent as Window;
        if (window != null && window.WindowState == WindowState.Normal) {
            DragSize(w.GetWindowHandle(), SizingAction.NorthWest);
        }
    }
}


Wir sind nun fast fertig. Es fehlt nur noch eine Reaktion auf das Event „isActiv“. Dafür zwingen wir die Statusleiste (StatusBar) die eigene Farbe zu ändern, wenn das Fenster den Fokus verliert.

XAML für StatusBar
<Style.Resources>
    <Style TargetType="{x:Type StatusBar}">
        <Style.Triggers>
            <DataTrigger Value="True"
                Binding="{Binding IsActive, RelativeSource={RelativeSource AncestorType=Window}}">
                <Setter Property="Foreground"
                     Value="{StaticResource VS2012WindowStatusForeground}" />
                <Setter Property="Background"
                     Value="{StaticResource VS2012WindowBorderBrush}" />
            </DataTrigger>
            <DataTrigger Value="False"
                Binding="{Binding IsActive, RelativeSource={RelativeSource AncestorType=Window}}" >
                <Setter Property="Foreground"
                    Value="{StaticResource VS2012WindowStatusForegroundInactive}" />
                <Setter Property="Background"
                    Value="{StaticResource VS2012WindowBorderBrushInactive}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Style.Resources>
 Das Ergebnis
Wir müssen nur den Style mit  dem Projekt mit Hilfe von  den Applikations-Ressourcen verbinden:
<Application ... StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles/CustomizedWindow/VS2012WindowStyle.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>


Jetzt sehen alle Fenster im Projekt ein bisschen interessanter aus:



Schließlich möchte ich Ihnen auch den Quelltext des Projekts nicht vorenthalten.
Viel Spaß mit «Windows 7.5»!
  • DrWeb DrWeb,
  • 19 November 2012, 21:18
  • 0

Kommentare (12)

RSS zusammenklappen / ausklappen
Kannst du den Quelltext bitte Zippen und wo anders hochladen da Github sehr sehr unstabil ist und seit Tagen nur fehler schmeisst wenn ich den Code anschauen oder Runterladen will.
Danke.
0
Hm… Ich habe gerade noch mal geprüft, der Link funktioniert
0
Wunderbar! Vielen Dank!
0
Vielen Dank! Das gestylte Fenster funktioniert auch mit .NET 4.0 (habe die Solution entsprechend angepasst). Wenn ich allerdings die Darstellung des Fensters nach Vollbild (WindowStyle.Maximized) wechsle, dann überlappt das Fenster den gesamten Desktop inkl. der Task-Bar. Ist dies so gewünscht? Standardmässig bzw. in meinem Fall zwingend erforderlich, muss die Task-Bar weiterhin sichtbar bleiben (sofern sie nicht als ausgeblendet konfiguriert wird).

Gibt es dazu eine Lösung?
0
Hallo Björn,

Danke für Deine Danke!

ich habe leider auf dieses Problem uberhaupt nicht gedacht und habe leider keine lösung dazu. Als eine Alternative kann ich Dich das Projekt vorschlagen, ich habe es später gefunden. Ich habe das Projekt selbst nicht ausprobiert, aber sieht richtig gut aus. Probier mal einfach.

P.S. Falls Du die Lösung findest, poste sie bitte hier.
0
Hallo,

wenn man folgende Eigenschaft des Fensters setzt, funktioniert das mit der Taskleiste:

this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;

Grüße
Matthias
0
Ich habe Elysium mal kurz angeschaut, da ich jedoch nur das Window verwenden möchte ist dies keine Alternative.

Ich habe nun die Umschaltung zwischen Normal/Maximized manuell implementiert, ohne Zuweisung von WindowState oder tiefgründige API Zugriffe. Ich speichere dazu die aktuelle Fenstergrösse und -position bevor auf Maximized umgeschaltet wird. Die Fenstergrösse und -position ermittle ich mit 'System.Windows.SystemParameters.WorkArea' und weise sie dem fenster zu.

Zugegeben, die Lösung ist nicht sehr sexy, aber Hauptsache man kann das Fenster (fast) wie gewohnt maximieren.
0
Noch eine kleine Korrektur für alle die das Fenster nutzen:
Zum Verwenden eines GridSplitter innerhalb des Fensters fehlt ein AdornerDecorator im ControlTemplate. Beim Bewegen des GridSplitter wird eine Exception ausgelöst, wenn der AdornerDecorator fehlt (siehe auch http://support.microsoft.com/kb/955940/de).

Dazu habe ich das ControlTemplate wie folgt ergänzt:

0
Ups, der Source Code wurde vorher nicht eingefügt (Gastzugang). In Textform: Der AdornerDecorator muss um das äussere Grid im Control Template eingefügt werden.
0
Hey kannst du das Projekt vielleicht wo anders hochladen? :)

Fehlermeldung wenn ich es als Zip downloaden möchte: 503 Service Unavailable

:/

wäre echt nett :D
0
Hallo lieber Acadopus Leser,

es ist immer etwas schwierig urheberrechtlich geschütze Inhalte woanders hochzuladen und der breiten Masse zugänglich zu machen.
Ich habe es aber soeben nocheinmal auf der offiziellen Projektseite: github.com/D-Key/whosh ausprobiert — mit Erfolg.
Vielleicht kannst du es nocheinmal ausprobieren.
0
Hallo,

das gefällt mir sehr gut, toll gemacht!
Darf deine Vorlage in etwas abgeänderter Form in kommerziellen Projekten verwendet werden?

Grüße
Matthias
0
  • avatar
  • Matthias H.   Gast
  • 20 Februar 2014, 08:34
  • Antworten

Kommentar schreiben

Ihr Name
Sie sind ein Gast, Sie dürfen keine HTML-Tags verwenden
Bitte geben Sie die Zeichen in das folgende Feld ein