Freitag, 31. Mai 2013

WPF RibbonWindow

Das Ribbon hat sich mittlerweile bei vielen Anwendungen durchgesetzt und wird nun nicht mehr nur von Microsoft selber genutzt. In WPF ist es denkbar einfach das Grundgerüst eines Ribbons zu erstellen.

Bevor es wirdlich los geht, muss erstmal die benötigte Reference System.Windows.Controls.Ribbon hinzugefügt werden:

Und nun auch schon der Sourcecode, in dem zwei RibbonButtons angezeigt werden:

 <RibbonWindow x:Class="WpfSpike.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     Title="Ribbon" Height="300" Width="500">  
   <Grid>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="Auto" />  
       <RowDefinition Height="*" />  
     </Grid.RowDefinitions>  
     <Ribbon>  
       <RibbonTab Header="Home">  
         <RibbonGroup Header="Group 1">  
           <RibbonButton Label="Button 1" />  
           <RibbonButton Label="Button 2" />  
         </RibbonGroup>  
       </RibbonTab>  
     </Ribbon>  
     <Grid Grid.Row="1">  
       <TextBlock>main content</TextBlock>  
     </Grid>  
   </Grid>  
 </RibbonWindow>  

Wie man sieht, kann dieses Beipiel nun auch ganz einfach um mehrere RibbonTabs, RibbonGroups oder andere Elemente, wie zum Beispiel RibbonRadioButtons oder RibbonTextBox erweitern.

Das Ergebnis sieht dann wie folgt aus:








WPF DataTrigger

Der DataTrigger ist ein guter und einfacher Weg, WPF Controls im Aussehen zu ändern oder zum Beispiel das IsEnabled Flag anzupassen. Im Folgenden werde ich den Content eines Buttons anpassen, je nachdem, ob die Checkbox selektiert ist oder nicht. Es ist natürlich auch möglich an eine Property vom DataContext zu binden.

 <CheckBox x:Name="IsEnabled" IsChecked="True" HorizontalAlignment="Center" />  
 <Button>  
      <Button.Style>  
           <Style>  
                <Style.Triggers>  
                     <DataTrigger Binding="{Binding ElementName=IsEnabled, Path=IsChecked}" Value="True">  
                          <Setter Property="Button.Content" Value="one" />  
                     </DataTrigger>  
                     <DataTrigger Binding="{Binding ElementName=IsEnabled, Path=IsChecked}" Value="False">  
                          <Setter Property="Button.Content" Value="two" />  
                     </DataTrigger>  
                </Style.Triggers>  
           </Style>  
      </Button.Style>  
 </Button>  

Das Ergebnis ist dann wie erwartet:





Samstag, 25. Mai 2013

Die kleinen und großen Helferlein

Es gibt zig unterschiedliche Helfer bei der Entwicklung mit .NET. Hier mal ein Auszug von den Tools, die ich teilweise schon seit vielen Jahren einsetze:

Freitag, 24. Mai 2013

GroupBox Header wird nicht disabled angezeigt

Die WPF GroupBox kann wie jedes Control, das von UIElement ableitet, über die Property IsEnabled enabled bzw. disabled werden.
Leider wird der Header Text dabei nicht wie erwartet ausgegraut. Wenn man aber das HeaderTemplate überschreibt, kann man auf einfache Weise dieses Verhalten implementieren.

 <StackPanel>  
      <CheckBox Content="CheckBox" x:Name="Checkbox" />  
      <GroupBox Header="Text 1" IsEnabled="{Binding ElementName=Checkbox, Path=IsChecked}" />  
      <GroupBox Header="Text 2" IsEnabled="{Binding ElementName=Checkbox, Path=IsChecked}">  
           <GroupBox.HeaderTemplate>  
                <DataTemplate>  
                     <Label Content="{Binding}" />  
                </DataTemplate>  
           </GroupBox.HeaderTemplate>  
      </GroupBox>  
 </StackPanel>  

Hier der Unterschied zwischen den beiden GroupBox Controls:

Donnerstag, 23. Mai 2013

InverseBooleanConverter mit bool? Unterstützung

An manchen Stellen ist es notwendig einen boolischen Wert in WPF zu invertieren. Dafür bieten sich Converter an. Man erstellt einfach eine Klasse, die von IValueConverter ableitet und implementert die benötigten Methoden Convert und ConvertBack.
Mein InverseBooleanConverter berücksichtigt auch den Typ Nullable<bool>. Dies kann zum Beispiel für Checkboxen wichtig sein kein, wenn man die Property IsThreeState auf true gesetzt hat, um 3 States (true, false und null) abzubilden.

Folgend der Converter:
 public class InverseBooleanConverter : IValueConverter  
 {  
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
      {  
           return InverseValue(value, targetType);  
      }  
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
      {  
           return InverseValue(value, targetType);  
      }  
      private static object InverseValue(object value, Type targetType)  
      {  
           if (targetType != typeof(bool) && targetType != typeof(bool?))  
                throw new InvalidOperationException("The targetType must be a bool!");  
           if (value == null)  
                return null;  
           return !(bool)value;  
      }  
 }  

Und hier die Beispielview mit dem XAML Code.
 <StackPanel>  
      <StackPanel.Resources>  
           <wpf:InverseBooleanConverter x:Key="InverseBooleanConverter" />  
      </StackPanel.Resources>  
      <CheckBox x:Name="IsEnabled" IsChecked="True" HorizontalAlignment="Center" />  
      <TextBox Width="80" Height="20" IsEnabled="{Binding Path=IsChecked, ElementName=IsEnabled}">text 1</TextBox>  
      <TextBox Width="80" Height="20" IsEnabled="{Binding Path=IsChecked, ElementName=IsEnabled, Converter={StaticResource InverseBooleanConverter}}">text 2</TextBox>  
 </StackPanel>  



Mittwoch, 22. Mai 2013

Tracen bei WPF Bindings

Seit dem .NET Framework 3.5 stellt Microsoft für Debugging Zwecke ein Tracing für WPF zur Verfügung.

 <Button Command="{Binding ReleaseCommand, PresentationTraceSources.TraceLevel=High}">Release</Button>  

Damit hat man die Möglichkeit ein bisschen nach zu vollziehen, was bei den WPF Controls unter der Haube so passiert. Zum Beispiel, wie ein gebundener Wert vor und nach einem Converter ist. Der Trace Level kann  None, Low, Medium oder High sein.
Diese angefügte Eigenschaft kann auf alle Klassen angewendet werden, die von BindingBase oder BindingExpressionBase ableiten. Also für folgende Klassen:
  • Binding
  • MultiBinding
  • PriorityBinding
  • BindingExpression
  • MultiBindingExpression
  • PriorityBindingExpression.
Außerdem ist die auch bei ObjectDataProvider oder XmlDataProvider anwendbar.

Hier nochmal ein Auszug in dem ich ein Command am Button gebunden habe:

 System.Windows.Data Warning: 55 : Created BindingExpression (hash=41717140) for Binding (hash=2418355)  
 System.Windows.Data Warning: 57 :  Path: 'ReleaseCommand'  
 System.Windows.Data Warning: 59 : BindingExpression (hash=41717140): Default mode resolved to OneWay  
 System.Windows.Data Warning: 60 : BindingExpression (hash=41717140): Default update trigger resolved to PropertyChanged  
 System.Windows.Data Warning: 61 : BindingExpression (hash=41717140): Attach to System.Windows.Controls.Button.Command (hash=34101444)  
 System.Windows.Data Warning: 66 : BindingExpression (hash=41717140): Resolving source   
 System.Windows.Data Warning: 69 : BindingExpression (hash=41717140): Found data context element: Button (hash=34101444) (OK)  
 System.Windows.Data Warning: 77 : BindingExpression (hash=41717140): Activate with root item MainViewModel (hash=58169988)  
 System.Windows.Data Warning: 107 : BindingExpression (hash=41717140):  At level 0 - for MainViewModel.ReleaseCommand found accessor RuntimePropertyInfo(ReleaseCommand)  
 System.Windows.Data Warning: 103 : BindingExpression (hash=41717140): Replace item at level 0 with MainViewModel (hash=58169988), using accessor RuntimePropertyInfo(ReleaseCommand)  
 System.Windows.Data Warning: 100 : BindingExpression (hash=41717140): GetValue at level 0 from MainViewModel (hash=58169988) using RuntimePropertyInfo(ReleaseCommand): SimpleCommand`2 (hash=26754911)  
 System.Windows.Data Warning: 79 : BindingExpression (hash=41717140): TransferValue - got raw value SimpleCommand`2 (hash=26754911)  
 System.Windows.Data Warning: 88 : BindingExpression (hash=41717140): TransferValue - using final value SimpleCommand`2 (hash=26754911)  
 System.Windows.Data Warning: 55 : Created BindingExpression (hash=31382336) for Binding (hash=5171309)  
 System.Windows.Data Warning: 57 :  Path: 'ReleaseCommand'  
 System.Windows.Data Warning: 59 : BindingExpression (hash=31382336): Default mode resolved to OneWay  
 System.Windows.Data Warning: 60 : BindingExpression (hash=31382336): Default update trigger resolved to PropertyChanged  
 System.Windows.Data Warning: 61 : BindingExpression (hash=31382336): Attach to WpfControls.HoldButton.Command (hash=2919017)  
 System.Windows.Data Warning: 66 : BindingExpression (hash=31382336): Resolving source   
 System.Windows.Data Warning: 69 : BindingExpression (hash=31382336): Found data context element: HoldButton (hash=2919017) (OK)  
 System.Windows.Data Warning: 77 : BindingExpression (hash=31382336): Activate with root item MainViewModel (hash=58169988)  
 System.Windows.Data Warning: 106 : BindingExpression (hash=31382336):  At level 0 using cached accessor for MainViewModel.ReleaseCommand: RuntimePropertyInfo(ReleaseCommand)  
 System.Windows.Data Warning: 103 : BindingExpression (hash=31382336): Replace item at level 0 with MainViewModel (hash=58169988), using accessor RuntimePropertyInfo(ReleaseCommand)  
 System.Windows.Data Warning: 100 : BindingExpression (hash=31382336): GetValue at level 0 from MainViewModel (hash=58169988) using RuntimePropertyInfo(ReleaseCommand): SimpleCommand`2 (hash=26754911)  
 System.Windows.Data Warning: 79 : BindingExpression (hash=31382336): TransferValue - got raw value SimpleCommand`2 (hash=26754911)  
 System.Windows.Data Warning: 88 : BindingExpression (hash=31382336): TransferValue - using final value SimpleCommand`2 (hash=26754911)  

Dienstag, 21. Mai 2013

Nachtrag zu TextFormatting

Wie ich ja schon berichtete, macht es gegebenfalls Sinn, die TextOptions.TextFormattingMode Property auf Display zu setzen. Ich habe auch geschrieben, das es möglich ist diese Einstellung direkt beim Window zu setzen und somit alle darin liegenden Elemente direkt mitzusetzen.

Diese Einstellung sollte aber nur gesetzt werden, wenn es auch wirklich notwendig ist, sprich bei Texten mit einer Schriftgröße kleiner 14.

Montag, 20. Mai 2013

Wie funktioniert eigentlich ein RadioButton?

Wie schon in einem vorherigen Post beschrieben, möchte ich den WPF Expander um die GroupName Eigenschaft erweitern. Um dies zu tun, sollte man sich vorher einfach mal den RadioButton anschauen. Schließlich arbeitet dieses Control nach dem gewünschten Verhalten.

Der RadioButton ist generell eine spezielle Ausprägung des ToggleButtons. Der RadioButton bzw. ToggleButton ist eines der wenigen Controls, die über einen Namen Bezug zu anderen Controls haben. Besitzen zum Beispiel zwei RadioButtons auf einer View den gleich GroupName, kann immer nur ein Control angewählt werden.

Aber wie funktioniert das eigentlich? Was passiert hinter den Kulissen?
An sich ist die Umsetzung gar nicht so kompliziert. Jedes Control überwacht die IsChecked Property. Wird das Control ausgewählt (IsChecked wird auf true gesetzt), wird zwischen zwei Situationen unterschieden und es passiert folgendes:

  1. Es wurde beim aktuellen Control kein GroupName angegeben, so werden alle RadioButtons des selben Parents, die ebenfalls keinen GroupName besitzen und deselektiert (IsChecked wird auf false gesetzt).
    Im folgenden Beispiel kann immer nur einer der Buttons 1, 2 oder 3 gesetzt werden. Button 4 spielt hierbei keine Rolle!
     <StackPanel>  
         <RadioButton x:Name="button1">Button 1</RadioButton>  
         <RadioButton x:Name="button2">Button 2</RadioButton>  
         <RadioButton x:Name="button3">Button 3</RadioButton>  
     </StackPanel>  
     <RadioButton x:Name="button4">Button 4</RadioButton>
    

  2. Wird ein GroupName angegeben, werden alle RadioButtons geprüft, ob sie den gleichen Namen und die gleiche VisualRoot besitzen. Bei all diesen, werden die Elemente, die nicht angeklickt worden sind deselektiert.
    Im folgenden Beispiel bilden die RadioButtons 1, 2 und 4 eine Gruppe. Das StackPanel spielt dabei keine Rolle, ebenso wie der RadioButton 2.
  3.  <StackPanel>  
       <RadioButton x:Name="button1" GroupName="Group1">Button 1</RadioButton>  
       <RadioButton x:Name="button2">Button 2</RadioButton>  
       <RadioButton x:Name="button3" GroupName="Group1">Button 3</RadioButton>  
     </StackPanel>  
     <RadioButton x:Name="button4" GroupName="Group1">Button 4</RadioButton>  
    

Für die Umsetzung des erweiterten ExpanderControls heißt dies nun, dass das Control jede Änderung der IsExpanded, anstatt der IsChecked Property überwachen müsste und sich genauso wie der RadioButton verhalten sollte.

Samstag, 18. Mai 2013

Controls selber bauen

Ein eigenes WPF Control zu schreiben ist im Gegensatz zu anderen Technologien wie z.B. WinForms schnell und einfach gemacht.
Es muss lediglich eine Klasse erstellt werden, die von Control ableitet und es muss ein ControlTemplate dazu angelegt werden.
Fügt man im Visual Studio 2013 ein neues CustomControl hinzu, werden diese beiden Klassen automatisch erstellt. Außerdem wird, wenn noch nicht vorhanden, das Assembly Attribut ThemeInfoAttribute der AssemblyInfo.cs hinzugefügt. Das ControlTemplate wird vom Studio direkt in der /Themes/Generic.xaml Datei angelegt und kann dort beliebig gestaltet werden. Fügt man seinem Control eine DependencyProperty hinzu, kann diese direkt über TemplateBinding an das ControlTemplate gebunden werden.

Freitag, 17. Mai 2013

Text wird unscharf dargestellt

Texte werden in WPF standardmäßig leicht unscharf angezeigt. Dieses Problem hat Microsoft erkannt und mit dem .NET Framework 4.0 eine Lösung mitgebracht.

 TextOptions.TextFormattingMode="Display"  

Nutzt man diese Einstellung an einem Control, so wird der Text wieder klar und deutlich angezeigt. Setzt man diese Eigenschaft direkt am Window Objekt, so wirkt es sich auf alle Child Elemente aus.

 <Window TextOptions.TextFormattingMode="Display" />  

Expander mit GroupName

Mal wieder habe ich die Anforderung ein Outlook ähnliches Menü zu erstellen. Versucht man selber dieses Problem mit Standard WPF Controls zu lösen, würde man wahrscheinlich eine Liste von Expandern erstellen und übers ViewModel ausschließen, dass zwei Expander gleichzeitig geöffnet sein können.
Sucht man im Internet nach Lösungen, so kann man verschiede Accordion Controls herunterladen. So ein Accordion bekommt dann meist direkt beliebig viele Expander als Liste übergeben.
Aber nun mal eine andere Idee: Wie wäre es, das Expander Control um eine GroupName Property zu erweitern? Das Verhalten wäre dann genauso wie bei RadioButtons. Außerdem hätte dies den großen Vorteil, dass die Expander nicht nur untereinander bzw. nebeneinander angezeigt werden könnten, sondern irgendwo auf der View verteilt sein könnten. Ich werde dies demnächst mal umsetzen und davon berichten!

Donnerstag, 16. Mai 2013

TemplateBinding oder TemplatedParent

Nicht immer funktioniert die Markup-Extension TemplateBinding. Dafür gibt es verschiedene Gründe. Zum Beispiel beim Verwenden von Triggern kann es zu Problemen beim Binding kommen.

TemplateBinding ist eine vereinfache Form des Bindings und für ControlTemplates benutzt. Sollte TemplateBinding mal nicht funktionieren, so kann man auch einfach auf die RelativeSource TemplatedParent zugreifen. Dies sollte aber wirklich nur gemacht werden, wenn das TemplateBinding nicht funktioniert, weil dieses speziell zum Binden bei ControlTemplates optimiert wurde.

  Text="{TemplateBinding Title}"  
  Text="{Binding Title, RelativeSource={RelativeSource TemplatedParent}}"  

Mittwoch, 15. Mai 2013

WPF Controls mit falscher Culture

Generell werden alle WPF Controls mit der en-US Culture erstellt. Dadurch werden unter Umständen z.B. Zahlen oder Daten im falschen Format angezeigt. Dieses Problem ist bei Microsoft schon lange bekannt und kann mit einem Workaround umgangen werden.

Wird folgende Zeile Code ausgeführt, wird von allen FrameworkElement's die Sprache auf die aktuelle Culture gesetzt.


 FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));  

Damit hätte man schon mal alle FrameworkElement's abgedeckt.
Nutzt man nun vielleicht auch FrameworkContentElement's wie z.B. das Run Control so kann man auch explizit für diese die Sprache festlegen:

 FrameworkContentElement.LanguageProperty.OverrideMetadata(typeof(Run), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));  
           
Das Überschreiben dieser Metadaten darf allerdings nur einmal aufgerufen werden, ansonsten wird eine Exception ausgelöst. Am Besten ruft man diese Funktionalitäten in der App.xaml.cs in der OnStartup Methode auf.