WPF: Style für alle Controls eines Typs

5. Juli 2011

Styles sind in WPF eine Möglichkeit, visuelle Eigenschaften, die für mehrere Controls gelten sollen, an zentraler Stelle zu definieren und den Controls zuzuweisen. Dies erspart zum Einen Schreibaufwand und zum Anderen Zeit, wenn eine dieser Einstellungen geändert werden soll. In diesem Post soll es aber nicht um die Basics von Styles gehen, sondern um den Anwendungsfall, dass man einen Style für alle Controls eines bestimmten Typs definiert.

Nehmen wir an, wir haben ein Window mit zwei GroupBoxes, die jeweils zwei TextBlocks enthalten:

<Window x:Class="StylesExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Styles" Height="200" Width="200">
    <StackPanel>
        <GroupBox Header="Vornamen">
            <StackPanel>
                <TextBlock Text="Petra" />
                <TextBlock Text="Michael" />
            </StackPanel>
        </GroupBox>
        <GroupBox Header="Nachnamen">
            <StackPanel>
                <TextBlock Text="Müller" />
                <TextBlock Text="Meier" />
            </StackPanel>
        </GroupBox>
    </StackPanel>
</Window>

Sieht als Ergebnis so aus:

Nun wollen wir allen TextBlocks einen gelben Hintergrund und einen Margin von 3 verpassen. Ein Weg wäre, den Style in den Window-Ressourcen zu definieren und bei jedem TextBlock das Style-Property anzugeben. Dies bedeutet aber wieder, dass wir bei jedem zusätzlichen TextBlock mehr schreiben müssen. Schöner wäre es doch, wenn wir direkt angeben könnten, dass der Style für alle TextBlock-Controls gelten soll. Dies geschieht, indem man einem Style nur den TargetType mitgibt, aber keinen Key:

<Window.Resources>
    <Style TargetType="TextBlock" >
        <Setter Property="Background" Value="Yellow" />
        <Setter Property="Margin" Value="3" />
    </Style>
</Window.Resources>

Starten wir die Anwendung jetzt, sieht das Ergebnis folgendermaßen aus:

Da wir den Style in den Ressourcen des Windows definiert haben, wird er auf alle TextBlock-Controls innerhalb des Windows angewendet. Würden wir ihn in den Ressourcen einer der GroupBoxes definieren, würde er nur für die TextBlock-Controls dieser GroupBox gelten. Möchten wir also, dass die Vornamen einen hellgrünen Hintergrund bekommen, definieren wir einen neuen Style innerhalb der ersten GroupBox:

<GroupBox.Resources>
    <Style TargetType="TextBlock" >
        <Setter Property="Background" Value="LightGreen" />
        <Setter Property="Margin" Value="3" />
    </Style>
</GroupBox.Resources>

Für alle TextBlock-Controls der Vornamen-GroupBox gilt nun der Style aus den GroupBox-Ressourcen, während für alle anderen TextBlock-Controls weiterhin der Style aus den Window-Ressourcen gilt.

Jetzt kann es aber natürlich vorkommen, dass eines der TextBlock-Controls einen abweichenden Style bekommen soll, zum Beispiel wenn der TextBlock “Meier” fett erscheinen soll. Kein Problem, setzen wir einfach einen Style für das Control:

<TextBlock Text="Meier">
    <TextBlock.Style>
        <Style TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
    </TextBlock.Style>
</TextBlock>

Wie wir sehen, führt uns das aber nicht wirklich zum Ziel. Die Schrift ist jetzt zwar fett, aber der Standard-Style ist verloren gegangen. Wir müssen also einen Weg finden, um den neuen Style auf dem Standard-Style basieren zu lassen. Dazu bietet jeder Style ein BasedOn-Property, über das der Basis-Style für den Style angegeben werden kann. Nun haben die Styles, die für alle Controls in ihrem Kontext gelten, aber keinen Key, über den wir sie ansprechen können. Zumindest keinen, den wir angegeben haben. Intern vergibt WPF aber dennoch einen Key, nämlich den Type des Target-Controls. Und über diesen können wir den Standard-Style ansprechen:

<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
    <Setter Property="FontWeight" Value="Bold" />
</Style>

So kommen wir zu unserem gewünschen Ergebnis:


WPF: Controls abhängig von CheckBox disablen

2. Juli 2011

Ein nicht seltener Usecase in einer Anwendung ist eine Checkbox, die zusätzliche Felder zum Editieren freigibt, zum Beispiel für eine abweichende Lieferadresse. In diesem kurzen Post will ich zeigen, wie sich das in WPF ganz leicht direkt im XAML eines Windows oder UserControls umsetzen lässt. Als erstes basteln wir uns die View, welche innerhalb des Root-Layout-Grids eine GroupBox enthält:

        <GroupBox Margin="5">
            <GroupBox.Header>
                <CheckBox x:Name="deliveryAddressCheckBox" 
                          Content="Abweichende Lieferadresse" />
            </GroupBox.Header>
            <Grid Margin="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Label Content="Vorname" Grid.Column="0" Grid.Row="0" />
                <TextBox Text="Peter" Margin="2" 
                         Grid.Column="1" Grid.Row="0" />
                <Label Content="Nachname" Grid.Column="0" Grid.Row="1" />
                <TextBox Text="Müller" Margin="2" 
                         Grid.Column="1" Grid.Row="1" />
            </Grid>
        </GroupBox>

Ausgeführt sieht das Ganze so aus:

 

Nun wollen wir, dass die TextBoxes für Vor- und Nachname nur editierbar sind, wenn die Checkbox gechecked ist. Unter WinForms müssten wir dazu einen Handler an das Click-Event der Checkbox hängen und dort im Code-Behind das Enabled-Property für die beiden TextBoxes setzen. WPF macht uns die Sache da bedeutend leichter und bietet dank Binding die Möglichkeit, diesen Usecase direkt im XAML abzubilden. Dafür binden wir einfach die IsEnabled-Properties an das IsChecked-Property der CheckBox:

<TextBox Text="Peter" Margin="2" 
  Grid.Column="1" Grid.Row="0"
  IsEnabled="{Binding ElementName=deliveryAddressCheckBox, Path=IsChecked}"
/>

Wenn wir die Anwendung nun starten, sieht das Ergebnis so aus:

Und wenn wir auf die Checkbox klicken, können die Felder editiert werden:

 

Ein, wie ich finde, kleines, aber feines Beispiel dafür, wie WPF einem das Leben einfacher macht. Das komplette XAML-File gibt es hier: Klick!


Spaß bei der Arbeit

20. Februar 2011

Wer mir dafür einen logischen Grund nennen kann, bekommt ein Eis!


.Net-Klassen für XML-Schema generieren

12. Dezember 2010

Zur Abwechslung gibt es heute mal wieder einen technischen Artikel. Ich stand in meinem aktuellen Projekt vor der Aufgabenstellung, dass ich Informationen aus einer XML-Datei auslesen und verarbeiten muss. An sich nichts weltbewegendes, spätestens seit LINQ to XML kann das sogar Spaß machen. Für meinen aktuellen Fall wollte ich aber einen anderen Weg gehen und zwar mit Klassen, die das XML-Schema repräsentieren, um dann die XML-Datei direkt in Instanzen von diesen Klassen zu parsen. Also so ähnlich, wie beim Lesen von eigenen Config-Sections der app.config. Mit Hilfe der Jungs von Google habe ich herausgefunden, dass es ein Tool von Microsoft gibt, um genau diese Klassen automatisch zu erzeugen. Dieses will ich euch heute an einem Beispiel zeigen.

Nehmen wir an, wir bekommen Daten zu Wettermessungen im XML-Format geliefert und müssen diese verarbeiten. Eine Datei kann Infos zu mehreren Stationen enthalten, von denen zu jeder 1 bis n Messungen gemeldet werden. Eine Messung kann dann wiederum mehrere Einzelmessungen enthalten. Daraus entsteht folgendes Schema:

Die Darstellung stammt übrigens aus dem XML-Schema-Designer vom Visual Studio 2010. Den finde ich ganz nützlich, um ein Schema zu visualisieren, nur trifft es Designer nicht wirklich, denn bearbeiten kann man ein Schema damit nicht, bzw. nur sehr eingeschränkt.

Nachdem wir nun das Schema haben, müssen wir die Klassen generieren. Dazu gibt es das Tool xsd.exe, welches sich im bin-Verzeichnis des Microsoft SDKs (z. B. C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin) versteckt. Um für unser Beispiel C#-Klassen zu generieren, müssen wir es mit folgenden Parametern aufrufen:

xsd WetterMessungen.xsd /classes /namespace:SampleApplication.BusinessObjects

Nun haben wir eine Datei namens WetterMessungen.cs im aktuellen Verzeichnis. Mit dem Tool lassen sich noch ein paar andere Sachen machen, mehr dazu verrät die MSDN.

Die generierte Datei jetzt noch im Visual Studio in das Projekt einbinden und schon haben wir generierte Klassen für unser Schema. Die sind natürlich alle partial und können demnach noch individuell erweitert werden. Jetzt brauchen wir nur noch die Möglichkeit, eine XML-Datei in Instanzen dieser Klassen zu parsen. Dazu benutzen wir ganz einfach Deserialization:

(Gibt auf WordPress.com scheinbar keine Möglichkeit, Code formatiert darzustellen, daher als Bild. Ihr könnt den Code aber downloaden: Klick! Das war auch schon das ganze Geheimnis, nun können wir anhand eines XML-Schemas automatisch entsprechende Klassen generieren und XML-Dateien in Instanzen von diesen parsen.

Und im nächsten Teil zeige ich euch, wie man das ganze als Custom Tool für’s Visual Studio automatisieren kann, so dass die Klassen automatisch bei jeder Schema-Änderung generiert werden.


“WHERE IN” in LINQ-Abfragen

25. August 2010

Ich stand gerade vor dem Rätsel, wie ich WHERE IN-Einschränkungen in einer LINQ-Abfrage abbilden kann. Die Lösung ist schlicht wie einfach:

Man legt also einfach eine Liste für die zu prüfenden Werte an und arbeitet dann in der Where-Abfrage mit Contains.


Lines of Code

7. August 2010

Die Lines of Code (LoC) eines Software-Projekts werden von Controllern und anderen Management-Und-Möchtegern-Management-Mitgliedern gerne herangezogen, um irgendwelche völlig abwegigen Vergleiche oder Bewertungen aufzustellen. Mein persönliches Highlight in der Hinsicht war die Idee eines Projektleiters, die Qualität einzelner Module in gemeldeten Kunden-Fehlern pro 1.000 LoC zu bestimmen. Wäre diese Idee in die Tat umgesetzt worden, hätte das die Entwickler dazu verleitet, den Code kosmetisch unschön zu strecken, um möglichst viele LoC zu generieren und so in der Qualitäts-Statistik gut abzuschneiden, was wiederum die Wartbarkeit des Codes verschlechtert hätte.

Ich selber halte die Lines of Code für eine interessante Statistik, um eine ungefähre Hausnummer für die Projekt-Größe zu ermitteln. Mehr aber auch nicht. Alle weiterführenden Statistiken sind für die Tonne.

Um für ein .Net-Projekt die Lines of Code zu ermitteln, gibt es mehrere Wege. Der einfachste Weg geht über die “Code Metrics”, die ab der Team System-Version in Visual Studio integriert sind. Nun wird die aber privat niemand wirklich besitzen, so dass ein anderer Weg her muss. Es gibt einige externe Add-Ins, welche aber Nutzern der Express-Editionen nicht viel nützen, da diese keine Add-Ins unterstützen. Aus diesem Grund habe ich für mich einen eigenen LoC-Counter entwickelt.

Das Programm kann für eine .Net-Solution die LoC für alle zugehörigen Dateien (inkl. XML und XSD) ermitteln. Im oberen Grid werden die Ergebnisse der einzelnen Dateien angezeigt, im unteren die Gruppierung nach Projekten. Es werden die Gesamtanzahl der Zeilen, die echten Code-Zeilen, sowie Kommentar- und Leer-Zeilen ermittelt. Wie im Screenshot zu sehen ist, werden auch Designer-Dateien mitgezählt.

Falls Interesse besteht, würde ich das Programm veröffentlichen.


Follow

Bekomme jeden neuen Artikel in deinen Posteingang.

Join 809 other followers