Biggle's Blog

Web- und Software Development

by Mario Binder

Toggle – WindowState Maximize / Normal

Wenn man in seiner WPF Applikation eigene Application-Icons zum minimieren, maximieren, schließen, usw. einsetzt, muss man erstens das Verhalten und zweitens das Aussehen selbst implementieren.

In diesem Snippet möchte ich zeigen, wie man zwischen den angezeigten Grafiken wechselt, wenn der entsprechende Zustand eintrifft.

Ist die Applikation im maximierten Zustand soll das Icon für “minimieren” angezeigt werden und wenn der Zustand der Applikation im normalen Zustand ist, anders herum.

Dies geht recht einfach mit einem Style und einem DataTrigger:

    <Style TargetType="Image" x:Key="ToggleIcon">
        <Setter Property="Source" Value="{DynamicResource MaximizeIcon}" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding WindowState, ElementName=TheNameOfMyWindow}" Value="Maximized">
                <Setter Property="Source" Value="{DynamicResource NormalIcon}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

Die DynamicResources in dem Beispiel sind gebunden an eine ImageSource:

    <ImageSource x:Key="MaximizeIcon">/MyApp.UI.Resources;component/Images/icon_maximize.png</ImageSource>
    <ImageSource x:Key="NormalIcon">/MyApp.UI.Resources;component/Images/icon_normal.png</ImageSource>

Die Verwendung ist dann nur noch Formsache…

<Image Stretch="None" Style="{DynamicResource ToggleIcon}" HorizontalAlignment="Right" VerticalAlignment="Top" Cursor="Hand" 
ToolTip="{x:Static Resources:Resources.maximize}" MouseLeftButtonDown="ToggleMaxNormal" />
private void ToggleMaxNormal(object sender, MouseButtonEventArgs e)
{
    switch (WindowState)
    {
        case WindowState.Normal:
            WindowState = WindowState.Maximized;
            break;
        case WindowState.Maximized:
            WindowState = WindowState.Normal;
            break;
    }
}

Viel Spaß beim entwickeln : )

by Mario Binder

Interaction Event-Trigger als Style auslagern

In einem Caliburn.Micro Projekt setze ich den EventTrigger ein, um meine Commands an einem ViewModel zu binden.

Nun möchte ich gerne immer den selben EventTrigger an mehreren Stellen einsetzen und verhindern, dass der Code an “tausend” Stellen redundant eingesetzt wird. Aus diesem Grund, soll diese Interaktion als Style zu Verfügung gestellt werden.

Der folgende Code zeigt, wie der redundante Code aussehen würde:

<r:RibbonButton Label="{x:Static Helper:ViewModels.AlleKontaktGruppen}" LargeImageSource="/OCC.Resources;component/Images/32/kdm/Groups-Meeting-Dark-icon.png"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <cal:ActionMessage MethodName="Navigate"> <cal:Parameter Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type r:RibbonButton}, AncestorLevel=1},Path=Label}" /> </cal:ActionMessage> </i:EventTrigger> </i:Interaction.Triggers> </r:RibbonButton>

Wie gehen wir also vor? Wir leiten uns dazu eine Klasse namens “Triggers” von einer typisierten Liste des Typen System.Windows.Interactivity.TriggerBase ab und registrieren diese als DepedencyProperty:

using System.Collections.Generic; using System.Windows; using System.Windows.Interactivity; namespace OCC.WPF.Helper { public class Triggers : List<System.Windows.Interactivity.TriggerBase> { } public static class OCCInteraction { public static Triggers GetTriggers(DependencyObject obj) { return (Triggers)obj.GetValue(TriggersProperty); } public static void SetTriggers(DependencyObject obj, Triggers value) { obj.SetValue(TriggersProperty, value); } public static readonly DependencyProperty TriggersProperty = DependencyProperty.RegisterAttached("Triggers", typeof(Triggers), typeof(OCCInteraction), new UIPropertyMetadata(null, OnPropertyTriggersChanged)); private static void OnPropertyTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var triggers = Interaction.GetTriggers(d); foreach (var trigger in e.NewValue as Triggers) triggers.Add(trigger); } } }

Und in einem Ressourcenwörterbuch “Style.xaml” bauen wir uns den entsprechenden Trigger dann in zusammen:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Helper="clr-namespace:OCC.WPF.Helper" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:cal="http://www.caliburnproject.org" xmlns:Ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"> <Helper:Triggers x:Key="ribbonBtnClickTrigger" x:Shared="False"> <i:EventTrigger EventName="Click"> <cal:ActionMessage MethodName="Navigate"> <cal:Parameter Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Ribbon:RibbonButton}, AncestorLevel=1},Path=Label}" /> </cal:ActionMessage> </i:EventTrigger> </Helper:Triggers> <Style x:Key="ribbonBtnClickBehavior" TargetType="FrameworkElement"> <Setter Property="Helper:OCCInteraction.Triggers" Value="{StaticResource ribbonBtnClickTrigger}"/> </Style> </ResourceDictionary>

Anschließend können wir unser Element an das Style-Attribute binden:

<r:RibbonButton Label="{x:Static Helper:ViewModelNames.AlleKontaktGruppen}" LargeImageSource="/OCC.Resources;component/Images/32/kdm/Groups-Meeting-Dark-icon.png" Style="{StaticResource ribbonBtnClickBehavior}"/>

Sieht doch gleich viel besser aus.

Viel Spaß beim entwickeln : )

by Mario Binder

Asynchrones Befüllen einer ObservableCollection<T> in einem ViewModel

Die Aufgabenstellung beschreibt, das innerhalb eine ViewModels Daten aus einer Datenbank ausgelesen und über der Netzwerkverbindung noch weiter verarbeitet werden müssen. Hier liegt es auf der Hand, dass eine asynchrone Lösung her muss, sprich es muss unter allen Umständen verhindert werden, dass die UI blockiert.

Wie man das machen kann, zeige ich wie folgt:

Ich erstelle mir vorerst einen einfachen Delegate.

1 private delegate void AsyncAwsDelegate();

Im Konstruktor der ViewModels, bzw in einem Command wird dann an diesem Delegate ein BeginInvoke aufgerufen, der die Methode zur asynchronen Verarbeitung zugewiesen bekommt.

1 public MainViewModel() 2 { 3 AsyncAwsDelegate del = (FetchItemsAsync); 4 del.BeginInvoke(null, null); 5 }

In dieser Methode findet dann der Prozess statt, wo die Items verarbeitet und einer ObservableCollection<T> hinzugefügt werden.

1 private void FetchItemsAsync() 2 { 3 var items = _dataLayer.Items.ToList(); 4 items.ForEach(i => Items.Add(_aws.ItemLookup(i.ItemId))); 5 }

Das geht leider nicht ohne Dispatcher, sprich wenn ich die “normale” ObservableCollection verwenden würde, müsste ich zuerst in den UI Thread gelangen, ansonsten knallt es.

Dazu konnte ich eine nette spezialisierte Klasse finden, die mir diese Arbeit abnimmt.

1 public class DispatchingObservableCollection<T> : ObservableCollection<T> 2 { 3 private readonly Dispatcher _currentDispatcher; 4 5 /// <summary> 6 /// The default constructor of the ObservableCollection 7 /// </summary> 8 public DispatchingObservableCollection() 9 { 10 //Assign the current Dispatcher (owner of the collection) 11 _currentDispatcher = Dispatcher.CurrentDispatcher; 12 } 13 14 /// <summary> 15 /// Executes this action in the right thread 16 /// </summary> 17 ///<param name="action">The action which should be executed</param> 18 private void DoDispatchedAction(Action action) 19 { 20 if (_currentDispatcher.CheckAccess()) 21 action(); 22 else 23 _currentDispatcher.Invoke(DispatcherPriority.DataBind, action); 24 } 25 26 /// <summary> 27 /// Clears all items 28 /// </summary> 29 protected override void ClearItems() 30 { 31 DoDispatchedAction(() => base.ClearItems()); 32 } 33 34 /// <summary> 35 /// Inserts a item at the specified index 36 /// </summary> 37 ///<param name="index">The index where the item should be inserted</param> 38 ///<param name="item">The item which should be inserted</param> 39 protected override void InsertItem(int index, T item) 40 { 41 DoDispatchedAction(() => base.InsertItem(index, item)); 42 } 43 }

Da die ObservableCollection das INotifyPropertyChanged Interface implementiert, brauch ich auch nicht mehr dafür Sorge tragen, dass sich bei jedem Zufügen eines Items die UI aktualisieren muss.

Viel Spaß beim entwickeln : )

by Mario Binder

DataBindings debuggen – WPF

Beim DataBinding ist es nicht immer einfach festzustellen, warum denn nun ein Wert nicht angezeigt wird. Die Fehlersuche zeigt sich mitunter, gerade auch in immer größer werdenden Projekten, als äußerst schwierig.

Das liegt daran, das im DataBinding keine Exception geworfen werden, wenn hier ein Element, eine Property, oder ein Path nicht gefunden wird.

Ein paar kleine Tricks können Abhilfe und für ein stressfreies Wochenende sorgen.

Tipp 1: Ausgabefenster

Lässt man sich beim Debuggen das Ausgabefenster von Visual Studio anzeigen, kann man diesem schon wertvolle Informationen entlocken.

Eine Fehlermeldung könnte z.B. wie folgt aussehen:

System.Windows.Data Error: 40 : BindingExpression path error: 'Mesage' property not found on 'object' ''MainWindow' (Name='')'. BindingExpression:Path=Mesage; DataItem='MainWindow' (Name=''); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')

Hier finde ich dann, das ich mich bei der Property “Message” verschrieben habe (“Mesage”).

Um das Ausgabefenster anzuzeigen, geht man unter Ansicht > Ausgabe oder zeigt dieses über die Tastenkombination CTRL+W+O an. Man muss hier aber darauf achten, dass man sich bereits im Debugging befindet.

Vorher sollte man überprüfen ob die Einstellungen in Visual Studio richtig sind.

Dazu geht man nach Extras > Optionen > Debugging > Ausgabefenster und wählt hier unter WPF-Ablaufverfolgung im DropDown “DataBinding” die Option “Alle

image

 

Tipp 2: PresentationTraceSources

Wem die Informationen in der Ausgabe nicht ausreichen, kann sein Control um weitere, ausführlichere Informationen erweitern.

Hierzu referenziert man auf den Namespace System.Diagnostics aus der Assembly WindowsBase

image

 

Anschließend erweitert man sein Binding um das AttachedProperty TraceLevel aus der Klasse PresentationTraceSources und dieses um den Wert High:

<TextBox Text="{Binding Mesage, debugging:PresentationTraceSources.TraceLevel=High}"  />

Hier bekommt man dann richtig viele Informationen zu einem Binding, die ich hier auszugsweise nur als Grafik zeigen möchte:

image

Mögliche Werte in TraceLevel können sein: High, Low, Medium und None.

Tipp 3: IValueConverter

Nun, es kommt vor, dass obwohl die Properties richtig definiert sind, immer noch nicht das angezeigt wird, was man eigentlich erwartet. Hier kommt die dritte Möglichkeit ins Spiel.

Es wäre doch toll, wenn man irgendwie einen BreakPoint irgendwo setzen könnte um nach gewohnter Manier zu debuggen. Ja, auch das ist möglich.

Hierzu definiert man sich einen Converter, der lediglich den Value durchreicht:

public class DebuggingConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }
}

Nun referenziert man den Namespace zum Converter und in den Resources des Elements wird der Converter bekannt gemacht:

xmlns:converter="clr-namespace:Debugging"

<Grid.Resources>
    <converter:DebuggingConverter x:Key="debugger" />
</Grid.Resources>

Im Binding wird der Converter dann noch definiert

<TextBox Text="{Binding Message, Converter={StaticResource debugger}}"  />

Jetzt setze ich meine BreakPoints im Convert oder beim TwoWay-Binding auch im ConvertBack und kann mir den Wert genauer anschauen.

 

Viel Spaß beim entwickeln : )

by Mario Binder

Gemeinsamen Validation Style für Controls – WPF

Um einen gemeinsamen Style für Validierungsfehler in der WPF zu verwenden, definiert man einen Style für den TargetType Control und vergibt diesen einen Key.

<Style x:Key="validationTriggerBase" TargetType="Control">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent   }" />
        </Trigger>
    </Style.Triggers>
</Style>

Im Anschluss, kann man einzelne Controls diesen Style zuweisen:

<Style TargetType="{x:Type DatePicker}" BasedOn="{StaticResource validationTriggerBase}" />

image

<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource validationTriggerBase}" />

image

Nicht vergessen, das bindende Control muss ValidatesOnDataErrors=True aufrufen.

<StackPanel Width="250" HorizontalAlignment="Center" DataContext="{StaticResource Person}">
    <TextBox Text="{Binding Name, ValidatesOnDataErrors=True, 
        UpdateSourceTrigger=PropertyChanged}" />

    <DatePicker SelectedDate="{Binding Birthday, StringFormat=dd.mm.yyyy, 
        ValidatesOnDataErrors=True,
        UpdateSourceTrigger=PropertyChanged, Converter={StaticResource conv}}" />
</StackPanel>

Viel Spaß beim entwickeln : )

by Mario Binder

Enumeration in einer ComboBox darstellen – WPF Quicky

Um eine Aufzählung unter WPF in einer ComboBox darzustellen, brauch man nicht wirklich viel.

Mit der Methode GetValues() aus der Klasse Enum, bekommt man einen Array mit den Konstanten aus der Aufzählung zurück.

<ComboBox ItemsSource="{Binding AutoCompleteFilters}" SelectedItem="{Binding SelectedFilterMode}" />

public List<AutoCompleteFilterMode> AutoCompleteFilters { get; set; }

private void SetAutoCompleteFilters()
{
    Array values = Enum.GetValues((typeof(AutoCompleteFilterMode)));

    AutoCompleteFilters = new List<AutoCompleteFilterMode>();
    foreach (AutoCompleteFilterMode value in values)
    {
        AutoCompleteFilters.Add(value);
    }
}

image

Viel Spaß beim entwickeln : )