head.WriteLine()

Donnerstag, August 25, 2011

TT.DataForm: MVVM Support

Bei der Entwicklung von TT.DataForm habe ich besonderen Wert auf den MVVM-Support gelegt. Dies betrifft sowohl die Datenbindung, als auch die Operationen die der Benutzer über die Oberfläche auslösen kann. Hierfür stehen entsprechende Command-Eigenschaften bereit, mit denen einen ViewModel das Control steuern kann.
  • AddCommand: Legt einen Command für die Neuanlage eines Datensatzes fest.
  • RemoveCommand: Legt einen Command für das Entfernen von Datensätzen fest.
  • SelectionButtonCommand: Legt den Command für die Auswahlschaltfläche von Bildern fest.
Für die Navigation stehen keine separaten Commands zu Verfügung, da diese idealerweise über eine entsprechende die Datenquellen-View, wie ICollectionView oder IEditableCollectionView gesteuert werden können.

In manchen Fällen muss das ViewModel jedoch aktiv mit der DataForm kommunizieren. So sollte zum Beispiel vor dem Speichern der Änderungen die aktive Eingabe abgeschlossen werden, um den gerade eingegebenen Wert in die Datenquelle zu übertragen. Hierfür stellt DataForm die Methode AcceptChanges().

Da das ViewModel jedoch nicht direkt mit der Komponente kommunizieren kann, werden die wichtigsten Methoden in einem so genannten ActionCommand abstrahiert. Hierbei stellt das ViewModel eine Eigenschaft vom Typ ActionCommand zu Verfügung, die an die gleichnamige Eigenschaft der DataForm gebunden wird. Daraufhin kann das ViewModel über die ExecuteAction()-Methode die entsprechende Methode auf dem Command aufrufen, welche daraufhin an die DataForm weitergeleitet wird. ExcuteAction() nimmt den Typ der Methode in Form eines Wertes der DataFormAction-Enumeration entgegen. Die folgenden Werte stehen hierbei zu Verfügung:
  • AcceptChanges: Übernimmt die über die Oberfläche durchgeführten Änderungen.
  • DetachDataSource: Entfernt die Datenquelle und die zugehörigen UI-Elemente aus dem Control.
  • Reset: Erstellt die Oberfläche anhand Datenquelle neu.
  • SetBusy: Sperrt die Oberfläche und blendet den BusyIndicator ein.
  • SetAvailable: Blendet den BusyIndicator aus und entsperrt die Oberfläche.
Das folgende Beispiel demonstriert die Anwendung:

View:
<dataform:DataForm
  ActionCommand="{Binding ActionCommand}" />


ViewModel:

public ActionCommand ActionCommand { get; set; }

private void SaveChanges()
{
  this.ActionCommand.ExecuteAction(
    DataFormAction.AcceptChanges, null);
  this.ActionCommand.ExecuteAction(
    DataFormAction.SetBusy, null);
  …
}


Der Wert DataFormAction.SetBusy bewirkt hierbei, dass alle Eingabefelder deaktiviert werden und der eingebaute BusyIndicator eingeblendet wird.

BusyIndicator

Der Wert DataFormAction.SetAvailable beendet die Animation wieder.

Zusätzlich bietet TT.DataForm die folgenden Eigenschaften, mit denen das Verhalten der Oberfläche konfiguriert werden kann.

Eigenschaft Beschreibung
AllowAddNew Bestimmt, die Neuanlage eines Datensatzes über das Steuerelement möglich sein soll.
AllowRemove Bestimmt, das Entfernen eines Datensatzes über das Steuerelement möglich sein soll.
IsReadOnly Bestimmt, ob das Steuerelement schreibgeschützt dargestellt werden soll.
Error Liefert den aktuellen Eigabefehler oder einen Leerstring zurück.
HasError Ruft ab, ob der aktuelle Datensatz Eingabefehler enthält.
IsBusy Legt fest, ob der Busy Indicator angezeigt werden soll.
BusyIndicatorType Legt den Typ des Busy Indicators fest. Zur Auswahl stehen Wheel, Dots und Custom. Bei letzterem Wert muss über die Eigenschaft BusyIndicatorTemplate ein benutzerdefiniertes Template bereitgestellt werden.

Das Beispielprojekt, das in den Sourcen von TT.DataForm enthalten ist, zeigt die Verwendung der verschiedenen Mechanismen in einem MVVM-Szenario.

Labels:

Mittwoch, August 24, 2011

TT.DataForm: UI Customizing

In diesem Post soll es um die Anpassung der einzelnen UI-Bestandteile von TT.DataForm gehen.
TT.DataForm besteht aus einer Reihe von Unterkomponenten, die über Styles und Templates separat angepasst werden können. Hierzu zählen:
  • DataFormHeader: Der Kopfbereich des Controls (Titel, Navigation)
  • Marker: Markierungssymbole (aktives Feld, geändertes Feld, Pflichtfeld, Fehler)
  • DataFormItem: Ein Feldzeile mit Bezeichner, Eingabeelement, sowie den Markern.
  • DataFormFooter: Der Fußbereich des Controls (Beschreibungstext, Fehlertext).
  • BusyIndicator: Der eingebaute BusyIndicator der DataForm.
Die folgende Abbildung zeigt den internen Aufbau:TTDataFormCustomizing

Hierbei beschreiben die gelben Kästchen die jeweilige Komponente mit ihren Eigenschaften, während die grünen Kästchen die zugehörigen DataForm-Eigenschaften kennzeichnen, mit denen Anpassungen vorgenommen werden können.

Um beispielsweise die Symbole der Navigationsleiste zu ändern, können über HeaderStyle die Eigenschaften des DataFormHeader-Objekts verändert werden.

<dataform:DataForm x:Name="personDataForm">
  <dataform:DataForm.HeaderStyle>
    <Style TargetType="dataform:DataFormHeader">
      <Setter Property="Background"
        Value="LightYellow" />
      <Setter Property="MoveFirstImage"
        Value="/Images/Navigation_First.png" />
      <Setter Property="MovePreviousImage"
        Value="/Images/Navigation_Previous.png" />
      <Setter Property="MoveNextImage"
        Value="/Images/Navigation_Next.png" />
      <Setter Property="MoveLastImage"
        Value="/Images/Navigation_Last.png" />
      <Setter Property="AddImage"
        Value="/Images/New.png" />
      <Setter Property="RemoveImage"
        Value="/Images/Delete.png" />
    </Style>
  </dataform:DataForm.HeaderStyle>
</dataform>


Darüber hinaus stehen eine Reihe allgemeiner Eigenschaften bereit, mit denen grundsätzliche Entscheidungen des Erscheinungsbilds festgelegt werden können. Die folgende Tabelle zeigt die hierfür wichtigsten Einstellungen:

Eigenschaft Beschreibung
RowHeight Bestimmt die Höhe einer Feldzeile.
ColumnSpacing Bestimmt den Abstand zwischen Anzeigetext und Eingabesteuerelement.
ColumnCount Legt die Anzahl der anzuzeigenden Spalten fest.
ShowHeader Bestimmt, ob der Kopfbereich angezeigt werden soll.
ShowFooter Bestimmt, ob der Fußbereich angezeigt werden soll.
ShowEditTracker Bestimmt, ob das Symbol für geänderte Felder angezeigt werden soll.
ShowRowMarker Bestimmt, ob das Symbol für das aktuell fokussierte Felder angezeigt werden soll.
ShowRequiredFieldMarker Bestimmt, ob das Symbol für Pflichtfelder angezeigt werden soll.
ShowErrorMarker Bestimmt, ob das Symbol für Eingabefehler angezeigt werden soll.
CornerRadius Legt den Radius des äußeren Rahmens fest.
ShowDropShadow Zeichnet einen Schatteneffekt für das Steuerelement.

Noch mehr Beispiele für die Anpassungsmöglichkeiten finden Sie im Demoprojekt, das den Sourcen von TT.DataForm beiliegt.

Labels:

Dienstag, August 23, 2011

TT.DataForm: Mappings

Nachdem ich hier bereits die Data Binding-Mechanismen von TT.DataForm beschrieben habe, soll es in diesem Post um die Anpassung der UI-Generierung gehen. Hierfür stehen zwei Modelle zu Verfügung: Control Mappings und Item Definitions.

Control Mappings

Mit den Control Mappings kann konfiguriert werden, in welcher Form die zu Verfügung gestellten Daten in UI-Elemente umgesetzt werden sollen. Standardmäßig verfolgt TT.DataForm das folgende Mapping:

Datentyp Input Control
int, double, decimal NumericUpDown
string TextBox
DateTime DatePicker
bool CheckBox
ImageSource Image
Sonstige TextBlock

Um dieses Mapping anzupassen, können ControlMapping-Objekte definiert und über die ControlMapping-Eigenschaft zugewiesen werden. Hierbei stehen die folgenden Möglichkeiten zu Verfügung:
  • Mapping auf Feldebene
  • Mapping auf Datentypebene
  • Definition eines DataTemplate
  • Angabe von Metadaten
Hier ein paar Beispiele:

<dataform:DataForm.ControlMappings>
  <dataform:ControlMapping ControlType="{x:Type TextBox}"
                           BindingPropertyName="Text"
                           DataPropertyName="Id" />
  <dataform:ControlMapping DataType="{x:Type sys:String}"
                           ControlType="{x:Type TextBlock}"
                           BindingPropertyName="Text" />
  <dataform:ControlMapping DataType="{x:Type sys:DateTime}"
                           IsReadOnly="True"
                           Description="The birthday.">
    <dataform:ControlMapping.ColumnTemplate>
      <DataTemplate>
        <TextBox Text="{Binding Path=Birthday,
                        StringFormat={}{0:dd. MMMM yyyy}}"
                        IsReadOnly="False" />
      </DataTemplate>
    </dataform:ControlMapping.ColumnTemplate>
  </dataform:ControlMapping>
</dataform:DataForm.ControlMappings>


In diesem Beispiel wurde festgelegt, dass die Eigenschaft Id als TextBox (statt als NumericUpDown) gerendert und hierbei die Text-Eigenschaft gebunden werden soll. Darüber hinaus wurde festgelegt, dass alle Eigenschaften vom Typ string in Form von TextBlock (statt TextBox) dargestellt werden. Für den Datentyp DateTime wurde zusätzlich festgelegt, dass alle Felder des Typs schreibgeschützt dargestellt werden sollen. Diese Art der Metadatenangabe kann sinnvoll sein, wenn die Datenquelle nicht mit Data Annotations attributiert wurde, bzw. die Angaben überschrieben werden soll. Zuletzt wurde für das Feld Birthday ein eigenes DataTemplate definiert. Dies kann dann sinnvoll sein, wenn das zu erzeugende UI-Control speziell konfiguriert oder das Binding zum Beispiel mit einem ValueConverter versehen werden muss.

Item Definitions

Die Item Definitions erfüllen im Grunde den gleichen Zweck wie die Control Mappings, erlauben hierbei jedoch weitaus mehr Anpassungsmöglichkeiten. So kann zum Beispiel nicht nur das Eingabeelement, sondern auch das Anzeigeelement festgelegt werden. Darüber hinaus können Item Definitions sowohl für gebundene, als auch für ungebundene Felder definiert werden. Das bedeutet, dass die Oberfläche zum Beispiel ein Notizfeld enthalten kann, obwohl die Datenquelle keine entsprechende Eigenschaft bereitstellt.
Die Erstellung von Item Definitions erfolgt über die Eigenschaft DataFormItems. Hierüber können ein oder mehrere DataFormItem-Objekte zugewiesen werden. Diese bestimmen über die Eigenschaften Header und Content den Anzeigetext bzw. das zu verwendende Eingabeelement.
Hier ein Beispiel für die Definition von Item Definitions:

<dataform:DataForm.DataFormItems>
  <dataform:DataFormItem Header="ID:">
    <dataform:DataFormItem.Content>
      <controls:NumericUpDown Width="100"
       
HorizontalAlignment="Left"
        Value="{Binding Path=Id, ValidatesOnDataErrors=True}"/>
    </dataform:DataFormItem.Content>
  </dataform:DataFormItem>
  <dataform:DataFormItem>
    <dataform:DataFormItem.Header>
      <TextBlock Text="Name:" />
    </dataform:DataFormItem.Header>
    <dataform:DataFormItem.Content>
      <TextBox Text="{Binding Path=Name}" />
    </dataform:DataFormItem.Content>
  </dataform:DataFormItem>
</dataform:DataForm.DataFormItems>


Wie zu sehen ist, kann die Header-Eigenschaft entweder mit einem einfachen Text, oder mit einem beliebigem Control gefüllt werden.

Im nächsten Post geht es um die grafischen Anpassungsmöglichkeiten von TT.DataForm.

Labels:

Montag, August 22, 2011

TT.DataForm: Data Binding

Nach der kurzen Einführung hier, gehe ich in den folgenden Posts näher auf die Features von TT.DataForm ein. Den Anfang mache ich mit dem Thema Data Binding.

Datenquellen

Eine Datenquelle bindet man an TT.DataForm über die Eigenschaft DataSource. Hierbei kann eine beliebige Datenquelle angegeben werden – unabhängig davon ob es sich um eine einzelnes Objekt oder eine Liste handelt. Die folgenden Datentypen werden hierbei unterstützt:
  • System.Object
  • System.Type
  • IEnumerable
  • ICollectionView
  • CollectionViewSource
  • ListCollectionView
Darüber hinaus kann TT.DataForm auch mit dem Data Source Windows von Visual Studio verknüpft werden, dass ein direktes Einfügen aus der Toolbox möglich ist.
DataSourceWindow_thumb4

Metadaten

Die Generierung der Oberfläche erfolgt auf Grundlage von Metadaten, welche die Datenquelle in Form von Data Annotations bereit stellt. So werden zum Beispiel die Labels mit den Anzeigenamen belegt, die über das Display-Attribut angegeben wurden. Beispiel:
using System.ComponentModel.DataAnnotations;
public class Person
{
  [Required]
  [Editable(false)]
  [Display(Name = "ID", Description = "The id of the person.")]
 
[DisplayFormat(DataFormatString = "#{0}")]
  public int Id { get; set; }

  [Required]
  [StringLength(50)]
  [Display(Name = "Fullname",
    Description="The full name of the person.")]
  public string Name { get; set; } 
  …
}

Hierbei werden sowohl die Anzeigeattribute, als auch die Formatierungen und Validierungsregeln von TT.DataForm bei der Generierung berücksichtigt.

Lookup-Daten

Oft werden neben der primären Datenquelle zusätzliche Daten, zum Beispiel für ComboBoxen benötigt. Hierfür bietet TT.DataForm die Methode SetAssociationSource(), sowie die Eigenschaft SubDataSources. Mit Ihnen können mit der Hauptdatenquelle verknüpfte Datenquellen angegeben werden. Hierbei wird jeweils der Name der Eigenschaft in der Hauptdatenquelle, sowie die Liste der verknüpften Datenquelle angegeben.
Der folgende Code zeigt ein einfaches Beispiel:
var list = new ObservableCollection<Department>()
{
  new Department { Id = 1, Name = "Development" }
}
var persons = new ObservableCollection<Person>()
{
  new Person
  {
    Id = 1,
    Name = "Jörg Neumann",
    Department = departments[0]
  }
};
dataForm.DataSource = persons;
dataForm.SetAssociationSource("Department", departments);

Daraufhin erzeugt TT.DataForm für die Department-Eigenschaft eine Combobox und bindet diese an die angegebene Quelle.
SubDataSources_thumb2
Die Eigenschaft SubDataSources ist empfehlenswert, wenn die Quelle per Markup zugewiesen werden soll und die Anwendung dem MVVM-Pattern folgt. Hierbei muss als Quelle lediglich der Name der entsprechenden ViewModel-Eigenschaft angegeben werden, welche die verknüpfte Datenquelle zu Verfügung stellt.
ViewModel:
class PersonViewModel
{
public ObservableCollection<Person> Persons { get; }
public ObservableCollection<Departments> Departments { get; }
}

View:
<tt:DataForm DataSource = "{Binding Persons}">
  <tt:DataForm.SubDataSources>
  <tt:SubDataSource
    EntityPropertyName = "Department"
    DataSourcePropertyName = "Departments"/>
  <tt:DataForm.SubDataSources>
</tt:DataForm>

Im nächsten Post beschreibe ich, wie das Mapping zwischen Datenquelle und UI-Element beeinflusst werden kann.

Labels:

Freitag, August 19, 2011

Just Released: Thinktecture DataForm

Thinktecture DataForm ist ein Open Source Control für WPF (eine Silverlight-Version ist in Arbeit), das eine automatische Generierung von formularbasierten Oberflächen ermöglicht. Hierfür analysiert es die übergebene Datenquelle und die hiermit verknüpften Data Annotations. Darüber hinaus kann die Generierung durch ein umfangreiches Customizing-Modell gesteuert werden. Für eine bessere Usability werden kontextsensitive Symbole und Informationen zu den Daten angezeigt (Pflichtfeldmarkierungen, Beschreibungen, Eingabefehler, …). Darüber hinaus kann so gut wie jeder Aspekt der Oberfläche durch Styles und Templates angepasst bzw. ersetzt werden. Die Komponente unterstützt eine Vielzahl verschiedener Datenquellentypen, sowie programmatische und deklarative Validierung. Zudem kann die komplette Funktionalität über Commands gesteuert werden, was einen Einsatz in MVVM-Anwendungen erleichtert.
TTDataFormOverview
Die Einzelheiten der Komponente werde ich in nächster Zeit in separaten Posts beschreiben. Darüber hinaus gibt es auf der CodePlex-Site eine Dokumentation. Zudem ist ein Artikel in der dotnetpro in Planung.

Labels:

Montag, August 15, 2011

Artikel über TT.UIA in der dotnetpro

In der aktuellen Ausgabe der dotnetpro (9/2011) ist mein Artikel “Aspektorientierte UI-Entwicklung mit Adornern” erschienen. Dies ist der erste Teil einer dreiteiligen Artikelserie in denen ich die technischen Aspekte von Thinktecture UIAnnotations erkläre.
Aus dem Abstract:
Benutzeroberflächen sind mehr als nur einfache Eingabemasken. Das drückt sich unter anderem darin aus, dass Nutzerfreundlichkeit und Interaktivität immer wichtiger werden. Bei der Implementierung stehen hingegen Kapselung und Wiederverwendbarkeit im Vordergrund. Adorner werden beiden Anforderungen gerecht.
dnp_092011

Labels: