ChildWindow, DialogResult und MVVM

von Norbert Eder 31. August 2010 09:30

Die Steuerung eines ChildWindow-Objektes in Silverlight wirft sehr schnell Fragen auf, wenn MVVM sauber eingesetzt werden soll. Wann ist die Eigenschaft DialogResult zu setzen und wer ist dafür verantwortlich, sind die ersten Fragen, die sich stellen.

Ansatz 1: Binding auf Eigenschaft DialogResult

Der erste Versuch liegt vermutlich darin, die Eigenschaft DialogResult des ChildWindow auf eine Eigenschaft des dahinter liegenden ViewModels zu binden. Dies wird jedoch fehl schlagen, da es sich dabei nicht um eine Dependency Property handelt. Ein Binding ist also nicht möglich. Dieser Weg scheitert hier. Eine Beschreibung dieser Eigenschaft findet sich im MSDN.

Ansatz 2: Attached Behavior

Eine funktionierende Lösung besteht darin, ein Attached Behavior zu implementieren. Dieses stellt eine Dependency Property zur Verfügung, welche auf einem ChildWindow an das dahinter liegende ViewModel gebunden werden kann. Das ViewModel selbst muss dafür eine entsprechende Eigenschaft zur Verfügung stellen.

public class DialogResultBehavior
{
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
                "DialogResult", typeof(bool?), typeof(DialogResultBehavior), 
                new PropertyMetadata(DialogResultPropertyChanged)
        );
 
    private static void DialogResultPropertyChanged(
        DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs e)
    {
        var childWindow = dependencyObject as ChildWindow;
        if (childWindow != null)
            childWindow.DialogResult = e.NewValue as bool?;
    }
 
    public static bool? GetDialogResult(ChildWindow childWindow)
    {
        return childWindow.GetValue(DialogResultProperty) as bool?;
    }
 
    public static void SetDialogResult(ChildWindow childWindow, bool? value)
    {
        childWindow.SetValue(DialogResultProperty, value);
    }
}

Eingebunden wird dies im ChildWindow nun folgendermaßen:

<controls:ChildWindow x:Class="DevTyr.ChildWindow.MvvmDemo.Views.DateSelectionWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:beh="clr-namespace:DevTyr.ChildWindow.MvvmDemo.Behaviors"
           beh:DialogResultBehavior.DialogResult="{Binding DialogResult}">
           
           // Markup
           
</controls:ChildWindow>

In dieser Definition wird vorausgesetzt, dass das ViewModel eine Eigenschaft DialogResult bereit stellt. Diese wird durch das ViewModel mit dem korrekten Wert gesetzt. Im Normalfall geschieht dies durch an Schaltflächen gebundene Commands.

Das Ergebnis ist nun ein sauberer Weg, ein ChildWindow durch das im ViewModel implementierte Verhalten zu steuern. Wege á la Ereignishandler für das Click-Event, welche das MVVM-Pattern brechen, werden dadurch vermieden.

kick it on dotnet-kicks.de

Buchempfehlung: Ich bin dann mal offline

von Norbert Eder 22. August 2010 10:52

Ich bin dann mal offline: Ein Selbstversuch. Leben ohne Internet und Handy -Zufällig bin ich die Tage auf das Buch von Christoph Koch mit dem Titel Ich bin dann mal offline gestoßen. Auf fast 300 Seiten beschreibt er seinen Selbstversuch einige Wochen ohne Internet und Handy auszukommen. Wohl nicht ganz freiwillig, hatte er doch Probleme mit seinem Internetanschluss nach einem Umzug.

Vorweg möchte ich betonen, dass dies kein Buch für Internet- und Handy-Skeptiker ist, sondern vielmehr ein begeisterter Benutzer dieser Möglichkeiten vollkommen unvoreingenommen berichtet, wie er die Abstinenz überbrückt, mit welchen Schwierigkeiten er zu kämpfen hat und welche “neuen” Möglichkeiten, Gedankenwelten etc. sich ergeben.

Besonders positiv fällt sein Schreibstil auf, der das Buch wirklich flüssig gestaltet. Inhaltlich wird ebenfalls einiges geboten. So werden nicht nur eigene Erlebnisse und Gefühle beschrieben, sondern auch Studien und Interviews behandelt. Der ideale Nährboden für eigene Gedanken.

Ja, wie war es denn früher, als man noch zum Kollegen um die Ecke ging und einfach mal anläutete. Klingelt es heute an der Türe, dann ist es entweder der Paketdienst und liefert die lang ersehnte Amazon-Lieferung, oder es ist “ungebetener” Besuch, der sich eigentlich  anmelden und um einen Termin bitten sollte. 24 Stunden Erreichbarkeit, üble Nachrede, wenn nicht sofort am Telefon abgehoben wird. Terminabsprachen für private Treffen mit der Terminfixierung “Rufen wir uns dann kommenden Montag zusammen und dann machen wir uns Genaueres aus”. Lieber fünf kurze Telefonate um sich mal nicht frühzeitig zu fixieren. Lieber stundenlang auf Facebook surfen um sich über seine “Freunde” zu informieren, anstatt wahre Freundschaften persönlich zu pflegen …

Definitiv. Christoph Koch hat des Pudel’s Kern getroffen. Ich kann all denjenigen, die täglich Internet und Handy benutzen wirklich nur empfehlen, sich dieses Buch zu besorgen, es sich mit einem guten Schluck Wein auf der Couch gemütlich zu machen, zu lesen und den eigenen Gedanken freien Lauf zu lassen.

Javascript per Silverlight registrieren

von Norbert Eder 15. Juli 2010 15:12

Wenn es notwendig sein sollte, Javascript per Silverlight auf der HTML-Page zu registrieren, dann helfen die nachfolgenden Zeilen:

private void RegisterScript()
{
    var jsScript = HtmlPage.Document.CreateElement("script");
    jsScript.SetAttribute("type", "text/javascript");
    jsScript.SetAttribute("text", "function do(param) {  // code goes here; }");
 
    HtmlPage.Document.DocumentElement.AppendChild(jsScript);
}
Kategorien: Silverlight

Danke Microsoft – Client App Dev MVP 2010

von Norbert Eder 2. Juli 2010 08:29

Microsoft Most Valuable Professional - Client App DevGestern um Punkt 16:30 Uhr war es wieder so weit. Mail von Microsoft. Für ein weiteres Jahr darf ich mich Microsoft MVP für Client App Dev nennen. Das ehrt natürlich sehr und motiviert zusätzlich, auch weiterhin tatkräftig in der Microsoft-Community tätig zu sein.

Meinen herzlichen Glückwunsch auch allen anderen MVPs, die renominiert wurden bzw. neu hinzugekommen sind.

Zum MVP Profil.

Kategorien: Community

Drucken unter Silverlight

von Norbert Eder 15. Juni 2010 13:02

Drucken unter Silverlight war ja als Thema für mich – mangels einer Anforderung - nie wirklich interessant – zumal es hier auch Wege drumherum gibt. Nun gibt es dieses Feature aber doch und schlussendlich habe ich dafür auch eine Anforderung erhalten. Blieb also nichts anderes übrig, als sich das Feature einmal genauer anzusehen.

Am Anfang der Geschichte steht die Klasse PrintDocument. Sie bietet folgende Möglichkeiten:

Methode/Event Beschreibung
BeginPrint Wird nach dem Bestätigen des Druck-Dialogs aber vor dem eigentlichen Druckvorgang ausgelöst.
PrintPage Wird für jede zu druckende Seite ausgelöst und übernimmt die Aufgabe, das zu Druckende zu definieren.
EndPrint Wird ausgeführt, wenn der Druckvorgang fertiggestellt wurde (sowohl reguläres Ende des Druckens als auch bei einem Abbruch).
Print Öffnet den Druckdialog und startet bei dessen Bestätigung den Druckvorgang. Als Parameter ist der Name des zu druckenden Dokumentes zu übergeben.


Und so sieht es aus:

PrintDocument doc = new PrintDocument();
doc.BeginPrint += new EventHandler<BeginPrintEventArgs>(doc_BeginPrint);
doc.PrintPage += new EventHandler<PrintPageEventArgs>(doc_PrintPage);
doc.EndPrint += new EventHandler<EndPrintEventArgs>(doc_EndPrint);
doc.Print("MyDocument");

Interessant hierbei ist das Ereignis PrintPage. Der Handler dazu erhält ein Objekt vom Typ PrintPageEventArgs. Dieser stellt vier Eigenschaften zur Verfügung:

Eigenschaft Beschreibung
HasMorePages Darüber kann festgelegt werden, ob weitere Seiten zu drucken sind.
PageMargins Liefert die Seitenränder der Seite mit Hilfe eines Thickness-Objektes zurück.
PageVisual Setzt das Objekt vom Typ Visual fest, welches ausgedruckt werden soll.
PrintableArea Liefert die Größe der druckbaren Fläche zurück.

 

Wie in der Tabelle ersichtlich, kann per PageVisual festgelegt werden, welches Objekt vom Typ Visual ausgedruckt werden soll. Auf dieser Basis können nun zwei Anwendungsfälle abgedeckt werden:

  1. Es wird ein bestehendes Visual herangezogen und (quasi á la Screenshot) ausgedruckt.
  2. Programmatisch wird ein Visual mit den notwendigen Informationen und Elementen erzeugt, welches ausgedruckt wird.

Der erste Fall läßt sich mit herkömmlichen Boardmitteln sehr gut abdecken und muss nicht zwangsweise über Silverlight behandelt werden. Der zweite Fall ist hierbei schon durchaus interessanter, da eigene Drucksichten gebastelt werden kann. Dies ist zwar weniger interessant, wenn es sich um eine LOB-Silverlight-Anwendung handelt und im Kontext einer größeren Online-Anwendung eingebettet ist. Für andere Szenarien ist die Möglichkeit aber durchaus verwendbar.

kick it on dotnet-kicks.de

Validierung in Silverlight + DataAnnotations + Vererbung – Ein Drama

von Norbert Eder 11. Juni 2010 07:40

DataAnnotations sind eigentlich eine nette Sache. Implementiert wurden sie für die RIA Services, können jedoch auch ohne verwendet werden. Die Klassen befinden sich im Namespace System.ComponentModel.DataAnnotations. Bereit gestellt werden folgende Klassen:

Validator Kann über ValidateProperty, ValidateObject etc. Eigenschaften und Objektinstanzen validieren.
ValidatorContext Beschreibt den Kontext, der für die Validierung Gültigkeit hat. Beispielsweise kann hier der Name des Members mitgegeben werden, der zu validieren ist.
ValidatorAttribute Davon abgeleitet existieren zahlreiche Klassen, welche über eine Eigenschaft IsValid verfügen und den neuen Wert gegen bestehende Werte, welche über den ValidatorContext geliefert werden prüft.
ValidationResult Liefert das Ergebnis zurück. Entweder ein leeres ValidationResult-Objekt (ValidationResult.Success) oder gefüllt mit einer ErrorMessage.
ValidationException Diese wird ausgelöst, wenn die Validierung fehl schlägt und kann in den Bindings per ValidatesOnExceptions abgefragt werden.

Der Vorteil dieser Variante ist, dass sämtliche Validierungslogik in eigenen Klassen implementiert und per Attribute eingebunden wird. Einfache Pflege, keine ellenlangen Validierungslogiken in den Settern sind die Vorteile.

Ohne RIA-Services sieht das dann in etwa so aus:

[Required(ErrorMessage="Name is Required")]
[StringLength(20, MinimumLength=5, ErrorMessage="Name must have between 5 to 20 characters")]
public string Name
{
    get { return name; }
    set 
    {
        Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Name" });
        name = value; 
    }
}

Und hier das XAML dazu:

<TextBox Style="{StaticResource SimpleTextBoxStyle}" 
         Text="{Binding Path=StartDate, 
                        Mode=TwoWay, 
                        ValidatesOnExceptions=True, 
                        NotifyOnValidationError=True}"/>

Problemfall

Die Validierung selbst muss nun nicht zwangsweise am Model statt finden, sondern kann auch im ViewModel geschehen, vor allem dann, wenn die gebundenen Werte nicht sofort in das Model durchgeschrieben werden. Hier kommt es in der Praxis nun dazu, dass Basisklassen von mehreren Ableitungen benötigte Eigenschaften etc. zur Verfügung stellen und die entsprechenden Attribute für die Validierung darauf gesetzt werden. Beispiel:

public class BudgetViewModel
{
    private decimal budgetLimit;
 
    [Required]
    public decimal BudgetLimit
    {
        get { return budgetLimit; }
        set
        {
            Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "BudgetLimit" });
            budgetLimit = value;
        }
    }
}
 
public class SpecialBudgetViewModel : BudgetViewModel
{
    private string specialProperty;
 
    [Required]
    public string SpecialProperty
    {
        get { return specialProperty; }
        set
        {
            Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "BudgetLimit" });
            budgetLimit = value;
        }
    }
}

Bei der Ausführung und Test der Validierung schlagen aber sämtliche – in der Basis gesetzte Attribute - nicht an. D.h. die Validierung verläuft immer positiv.

Nach einer kurzen Recherche und zumeist nur sehr einfachen Beispielen blieb nur mehr der Weg, per .NET Reflector die in Frage kommenden Klassen zu betrachten.

Der implementierte Validator verwendet nun eine interne Klasse ValidationAttributeStore, welche die zu validierenden Typen und die zugehörigen Attribute verwaltet. Die Methode GetTypeStoreItem liefert nun im Falle einer Validierung die gewünschten Informationen zu einem bestimmten Typ zurück. Werden diese nicht gefunden, wird ein entsprechender Eintrag für diesen Store erstellt. Dabei werden die benutzerdefinierten Attribute per

Type.GetCustomAttributes(bool inherit)

ausgelesen. Und genau hierin liegt das Problem. Die hauseigene Dokumentation sagt dazu folgendes:

This method ignores the inherit parameter for properties and events. To search the inheritance chain for attributes on properties and events, use the appropriate overloads of the Attribute.GetCustomAttributes method.

Fazit

Der Ansatz ist nett, funktioniert aber nur, wenn keine Basisklassen/Vererbung im Spiel sind/ist. Andernfalls muss man sich nach einer anderen Lösung umsehen und gegebenenfalls eine eigene Implementierung starten.

kick it on dotnet-kicks.de

Datenbindung mit Priorität

von Norbert Eder 9. Juni 2010 19:26

In der Praxis kann es vorkommen, dass gebundene Eigenschaften mal nicht eben einen Wert zurück liefern, sondern durchaus länger benötigen. Dies tritt beispielsweise auf, wenn

  • Images (oder Listen davon) geladen werden
  • Daten von einem Service abgeholt werden
  • Längere Berechnungen durchzuführen sind

Sicherlich ließen sich weitere Punkte finden. Jetzt wäre es jedoch für den Benutzer unangenehm, die Oberfläche zu sperren und ihm keine Rückmeldung zu liefern, dass gerade seine Anforderung ausgeführt wird. Um dies zu bewerkstelligen, gibt es seit je her PriorityBinding. Darüber können mehrere Bindungen inklusive Priorität festgelegt werden. Die Priorität wird dabei in der Reihenfolge der Bindungs-Angaben bestimmt. Die erste Bindung hat die höchste Priorität.

Beispiele zum Thema PriorityBinding gibt es einige. Die meisten arbeiten jedoch mit einem Thread.Sleep und sind gerade für Einsteiger daher nicht so einfach auf die Realität um zu legen. Das nachfolgend gezeigte Beispiel folgt zwar ebenso einem akademischen Ansatz, kommt jedoch ohne Thread.Sleep aus und sollte die Funktionsweise daher – hoffentlich – besser beschreiben.

Beispiel

Die hier gezeigte Anwendung bietet die Möglichkeit, einen Wert einzugeben, auf dessen Basis der letzte Wert der Fibonacci-Reihe errechnet wird. Ausgeführt wird die Berechnung, wenn auf die angebotene Schaltfläche geklickt wird. Die nachfolgende Abbildung zeigt die sehr einfache Oberfläche. Während der Berechnung soll der Anwender als Resultat eine Meldung erhalten, dass die Berechnung in Gange ist bzw. schlussendlich das tatsächliche Ergebnis.

image

Implementierung

Zu Grunde liegt dieser Anwendung die von mir bereits in mehreren Artikeln angesprochene MVVM-Implementierung. D.h. es wird versucht, an dieser Stelle eine saubere Auftrennung der Zuständigkeiten zu erreichen.

Die Bereitstellung der notwendigen Eigenschaften, Berechnungen und Aktionen (siehe Command) übernimmt ein ViewModel:

[LocatorAttribute("MainViewModel")]
public class MainViewModel : ViewModelBase
{
    private string calculationMessage = "Calculating result ...";
    private int inputValue;
    private RelayCommand calculateCommand;
 
    public string CaluculationMessage
    {
        get { return calculationMessage; }
    }
 
    public int InputValue
    {
        get { return inputValue; }
        set
        {
            if (inputValue == value)
                return;
            inputValue = value;
            RaisePropertyChanged("InputValue");
        }
    }
 
    public int Result
    {
        get { return CalcFibonacci(InputValue); }
    }
 
    private int CalcFibonacci(int x)
    {
        if (x <= 1)
        {
            return 1;
        }
        return CalcFibonacci(x - 1) + CalcFibonacci(x - 2);
    }
 
    public ICommand CalculateCommand
    {
        get
        {
            if (calculateCommand == null)
                calculateCommand = new RelayCommand(Calculate);
            return calculateCommand;
        }
    }
 
    private void Calculate(object param)
    {
        RaisePropertyChanged("CaluculationMessage");
        RaisePropertyChanged("Result");
    }
}

Die Eigenschaft CalculationMessage liefert einen String zurück, der anzeigt, dass die Berechnung aktuell in Gange ist. Result selbst ruft eine Methode auf, welche den Fibonacci berechnet und das Ergebnis zurück liefert. Diese Berechnung kann nun, abhängig vom Eingangswert, recht lange dauern. Der implementierte und nach aussen gegebene Command löst lediglich ein PropertyChanged der beschriebenen Eigenschaften aus (dadurch wird beim Aktualisierung der Bindung auch die Berechnung durchgeführt).

Nun werfen wir einen Blick auf die View:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
 
    <TextBlock Text="PriorityBinding Sample" 
                Style="{StaticResource HeaderBlockStyle}" 
                Grid.ColumnSpan="2"/>
        
    <TextBlock Text="Fib of" Grid.Row="2"/>
    <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1">
        <TextBox Text="{Binding InputValue}"/>    
        <TextBlock Text="Enter at least a value > 30"/>
    </StackPanel>
        
    <TextBlock Text="Result" Grid.Row="3"/>
    <TextBlock Grid.Column="1" Grid.Row="3">
        <TextBlock.Text>
            <PriorityBinding>
                <Binding Path="Result" IsAsync="True" Mode="OneWay"/>
                <Binding Path="CaluculationMessage" Mode="OneWay"/>
            </PriorityBinding>
        </TextBlock.Text>
    </TextBlock>
        
    <Button Content="Calculate" 
            Command="{Binding CalculateCommand}" 
            Grid.Row="4" 
            Grid.ColumnSpan="2"/>
</Grid>

Von besonderer Bedeutung ist hier das TextBlock-Element, welches für die Darstellung des Ergebnisses zuständig ist:

<TextBlock Grid.Column="1" Grid.Row="3">
    <TextBlock.Text>
        <PriorityBinding>
            <Binding Path="Result" IsAsync="True" Mode="OneWay"/>
            <Binding Path="CaluculationMessage" Mode="OneWay"/>
        </PriorityBinding>
    </TextBlock.Text>
</TextBlock>

Hier wird für die Eigenschaft Text ein PriorityBinding angegeben. Die höchste Priorität besitzt die Bindung an die Eigenschaft Result des ViewModels. Diese Bindung erfolgt asynchron (da sie etwas länger dauern kann). Die zweite Bindung erfolgt auf unsere Eigenschaft, welche rückmeldet, dass der Berechnungsvorgang in Arbeit ist.

Geben wir in der ausgeführten Anwendung einen größeren Wert ein, liefert das erste Binding beim ersten Zugriff keinen Wert zurück. Daher wird das nächste Binding in der Liste herangezogen. Dieses liefert sofort einen Wert zurück (es muss nur ein String zurück gegeben werden). Steht das Ergebnis zur Verfügung, wird dieses nachgereicht und die Statusmeldung verschwindet.

Hinweis: DependencyProperty.UnsetValue gilt nicht als erfolgreicher Rückgabewert.

Es besteht auch noch die Möglichkeit die Eigenschaft FallbackValue der PriorityBinding zu setzen. Dies würde den Weg über die zusätzliche Eigenschaft im ViewModel sparen. Wer jedoch mehr Kontrolle über den angezeigten Wert benötigt, wird den gezeigten Weg wählen.

Download

Dieses Beispiel steht untenstehend als Download zur Verfügung.

kick it on dotnet-kicks.de

Benutzerdefinierte Spalten für das WPF-DataGrid erstellen

von Norbert Eder 22. Mai 2010 11:35

Das DataGrid-Element in WPF bietet bereits einige vordefinierte Spaltentypen an. Es finden sich die folgenden Typen:

  • DataGridCheckBoxColumn
  • DataGridComboBoxColumn
  • DataGridHyperlinkColumn
  • DataGridTextColumn

Wem diese Typen nicht reichen, der kann sich unter Verwendung des Typs DataGridTemplateColumn seine Spalte via Vorlagen seinen Wünschen entsprechend anpassen. Zum Anzeigen von Datumswerten in einem DataGrid könnte nun eine Vorlage so definiert werden:

<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Datum">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding MyDate}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Analog dazu könnte für den Bearbeitungsmodus die Eigenschaft CellEditingTemplate verwendet werden, um dafür einen DatePicker zur Verfügung zu stellen.

Sollen nun in einer (oder gar mehreren Tabellen) zahlreiche Spalten desselben Typs, allerdings mit unterschiedlichen Bindings dargestellt werden, dann würde dies bedeuten, dass für jeden einzelnen Fall eigene Vorlagen zu erstellen sind. Dies ist sowohl für die Erstellung aufwändig, als auch für die weitere Pflege. Daher bietet es sich an, einen eigenen Spaltentyp zu definieren.

Ausgangspunkt dabei ist die abstrakte Klasse DataGridBoundColumn. Diese bietet bereits die notwendige Funktionalität für das Data Binding an und muss nur mehr dafür sorgen, dass dieses auf eigens gesetzte Elemente angewandt wird. Nachfolgend ist die Implementierung einer Klasse DataGridDateColumn zu finden. Diese zeigt einen gebundenen Datumswert in einem TextBlock-Element an. Wird der Bearbeitungsmodus gestartet, wird ein DatePicker-Element verwendet.

public class DataGridDateColumn : DataGridBoundColumn
{
    private static Style _defaultEditingElementStyle;
    private static Style _defaultElementStyle;
 
    static DataGridDateColumn()
    {
        DataGridBoundColumn.ElementStyleProperty.OverrideMetadata(typeof(DataGridDateColumn), new FrameworkPropertyMetadata(DefaultElementStyle));
        DataGridBoundColumn.EditingElementStyleProperty.OverrideMetadata(typeof(DataGridDateColumn), new FrameworkPropertyMetadata(DefaultEditingElementStyle));
    }
 
    protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        DatePicker dp = new DatePicker();
        this.ApplyStyle(true, false, dp);
        this.ApplyBinding(dp, DatePicker.SelectedDateProperty);
        return dp;
    }
 
    private void ApplyStyle(bool isEditing, bool defaultToElementStyle, FrameworkElement element)
    {
        Style style = this.PickStyle(isEditing, defaultToElementStyle);
        if (style != null)
        {
            element.Style = style;
        }
    }
 
    private Style PickStyle(bool isEditing, bool defaultToElementStyle)
    {
        Style elementStyle = isEditing ? this.EditingElementStyle : this.ElementStyle;
        if ((isEditing && defaultToElementStyle) && (elementStyle == null))
        {
            elementStyle = this.ElementStyle;
        }
        return elementStyle;
    }
 
    private void ApplyBinding(DependencyObject target, DependencyProperty property)
    {
        BindingBase binding = this.Binding;
        if (binding != null)
        {
            BindingOperations.SetBinding(target, property, binding);
        }
        else
        {
            BindingOperations.ClearBinding(target, property);
        }
    }
 
    protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        TextBlock tb = new TextBlock();
        this.ApplyStyle(false, false, tb);
        this.ApplyBinding(tb, TextBlock.TextProperty);
        return tb;
    }
 
    public static Style DefaultEditingElementStyle
    {
        get
        {
            if (_defaultEditingElementStyle == null)
            {
                Style style = new Style(typeof(DatePicker));
                style.Setters.Add(new Setter(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center));
                style.Setters.Add(new Setter(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Top));
                style.Seal();
                _defaultEditingElementStyle = style;
            }
            return _defaultEditingElementStyle;
        }
    }
 
    public static Style DefaultElementStyle
    {
        get
        {
            if (_defaultElementStyle == null)
            {
                Style style = new Style(typeof(TextBlock));
                style.Setters.Add(new Setter(FrameworkElement.MarginProperty, new Thickness(2.0, 0.0, 2.0, 0.0)));
                style.Seal();
                _defaultElementStyle = style;
            }
            return _defaultElementStyle;
        }
    }
}

Die Einbindung geschieht in der gewohnten Form – es muss lediglich ein XML-Namespace für den Zugriff auf unseren CLR-Namespace erstellt werden:

<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
    <DataGrid.Columns>      
        <local:DataGridDateColumn Binding="{Binding StartDate}" 
                                    Header="Startdatum"/>
        <local:DataGridDateColumn Binding="{Binding EndDate}" 
                                    Header="Enddatum"/>
    </DataGrid.Columns>
</DataGrid>

Hier nun das Ergebnis:

image

Das Beispiel ist natürlich wieder wie gewohnt im Anschluss verfügbar.

kick it on dotnet-kicks.de

Styles und DataTemplates per Selector wählen

von Norbert Eder 19. Mai 2010 17:56

WPF setzt quasi die Verwendung von Styles und DataTemplates voraus. Durch das Aufkommen des MVVM-Patterns werden viele Problematiken gerade über DataTemplates gelöst. Dadurch entstehen Vorlagen für Datentypen, die je nach Anwendungsfall für eine unterschiedliche Darstellung sorgen sollen. Gleiches gilt auch für Styles. Dafür sieht WPF Selektoren vor, die implementiert werden können, um die Wahl der richtigen Ressource einfacher zu gestalten. Doch wie werden sie verwendet?

Von Wichtigkeit sind zwei Basisklassen:

  • StyleSelector: Dient der Auswahl von Styles und stellt die Methode SelectStyle zur Verfügung, die überschrieben und damit an die eigenen Bedürfnisse hin angepasst werden kann.
  • DataTemplateSelector: Dient der Auswahl von Datenvorlagen und stellt die Methode SelectTemplate zur Verfügung.

Implementierung

In einem kleinen Beispiel wird eine Anwendung zur Anzeige von Personen erstellt. Diese bietet die Auswahl von Personen per ComboBox-Element als auch per ListBox an. Je nach Geschlecht der Person soll die Darstellung unterschiedlich erfolgen.

Da dieses Verhalten sowohl für den Container von List-Elementen (hier wird ein Style benötigt) als auch für Typen vom Typ ContentControl (hier wird ein DataTemplate benötigt) notwendig ist, werden Selektoren für beide Fälle implementiert.

Nachfolgend findet sich die Implementierung für den Style-Selektor. Dieser prüft ob das übergebene Objekt vom Typ Person ist und liefert je nach Geschlecht den Namen einer anderen Ressource zurück. Auf Basis dieses Namens wird die Resource gesucht und ein Style-Objekt nach aussen gegeben.

public class SexStyleSelector : StyleSelector
{
    public override System.Windows.Style SelectStyle(
        object item, 
        System.Windows.DependencyObject container)
    {
        Person p = item as Person;
        if (p != null)
        {
            string styleName = String.Empty;
            switch (p.Sex)
            {
                case Sex.None:
                    styleName = "DefaultSexStyle";
                    break;
                case Sex.Male:
                    styleName = "MaleSexStyle";
                    break;
                case Sex.Female:
                    styleName = "FemaleSexStyle";
                    break;
            }
            return Application.Current.TryFindResource(styleName) as Style;
        }
        return base.SelectStyle(item, container);
    }
}

Analog dazu wird der Vorlagen-Selektor implementiert:

public class SexTemplateSelector : DataTemplateSelector
{
    public override System.Windows.DataTemplate SelectTemplate(
        object item, 
        System.Windows.DependencyObject container)
    {
        Person p = item as Person;
        if (p != null)
        {
            string styleName = String.Empty;
            switch (p.Sex)
            {
                case Sex.None:
                    styleName = "DefaultSexTemplate";
                    break;
                case Sex.Male:
                    styleName = "MaleSexTemplate";
                    break;
                case Sex.Female:
                    styleName = "FemaleSexTemplate";
                    break;
            }
            return Application.Current.TryFindResource(styleName) as DataTemplate;
        }
        return base.SelectTemplate(item, container);
    }
}

Darstellung

Im Anschluss müssen die entsprechenden Styles und Vorlagen deklariert werden. Diese können aus dem angehängten File entnommen werden. Schlussendlich muss noch eine korrekte Einbindung in die Darstellung vorgenommen werden:

<StackPanel>
        
    <ComboBox ItemsSource="{Binding Path=People,Source={StaticResource DataMock}}"
                ItemContainerStyleSelector="{StaticResource SexStyleSelector}"
                ItemTemplateSelector="{StaticResource SexTemplateSelector}"
                Margin="10"
                />
        
    <ListBox ItemsSource="{Binding Path=People,Source={StaticResource DataMock}}"
                ItemContainerStyleSelector="{StaticResource SexStyleSelector}"
                Margin="10"
                />
        
</StackPanel>

Besonderes Augenmerk sollte hierbei auf die ComboBox gelegt werden. Diese enthält eine Area, welche die einzelnen Objekte darstellt. Jedes der Objekte wird in einem ComboBoxItem-Container dargestellt, der einen Style für die Darstellung benötigt. Damit aber auch der ausgewählte Eintrag wie gewollt dargestellt werden kann, muss die Eigenschaft ItemTemplateSelector mit dem gewünschten Selektor versehen werden, der das korrekte DataTemplate auswählt.

Resultat

image

Es ist also nicht viel Code notwendig, um eine derartige Aufgabe zu lösen, zumal dies sehr oft ohne die Möglichkeit der Selektoren implementiert wird und dadurch natürlich sehr schnell das MVVM-Pattern verletzt werden kann, oder – bei Nichtverwendung – Logik an Stellen zu finden ist, die dafür nicht geeignet sind.

Download

kick it on dotnet-kicks.de

Die MSDN Blog-Parade geht in die zweite Runde! Mitmachen und gewinnen!

von Norbert Eder 19. Mai 2010 16:03

MSDN Deutschland veranstaltet wieder eine Blog-Parade! Und wie beim letzten Mal, können Sie mit etwas Glück wieder ein von drei Xbox 360 Elite-Paketen inkl. zwei Spielen gewinnen!

Thema der Blog-ParadeDieses Mal ist Ihre Kreativität gefragt! Wie Sie alle mitbekommen haben, ist das Thema Cloud Computing ein wesentlicher Bestandteil der zukünftigen Plattform von Microsoft. Aus diesem Grund wird das Thema dieser Blog-Parade sein, coole Ideen zu sammeln, was man alles mit Windows Azure machen kann. Ihrer Phantasie sind hier keine Grenzen gesetzt, ob es nun spezielle Web-Anwendungen sind oder Anwendungen, die sowohl lokal auf dem PC oder einem Windows Handy laufen, aber auch für innovative Funktionen, die die Cloud benötigen.

Die Blogparade läuft bis einschließlich 21.06.2010. Innerhalb der darauf folgenden zwei Wochen werden wir dann die glücklichen Gewinner anschreiben und bekannt geben.

Weitere Informationen finden sich hier.

Kategorien: Community