Blue Orange Green Pink Purple

Posts Tagged ‘WPF’

You can use the search form below to go through the content and find a specific post or page:

Mar 09

MVVM: Behandeln von Events über Commands, Teil 2

In meinem letzten Eintrag gings darum, wie man Events eines WPF Controls auf Commands eines ViewModels umleiten kann. Verwendet habe ich dort die Attached Properties von WPF. Das Problem dabei war aber, dass man für diese Attached Properties für ein Control nur einmal verwenden kann. Das heißt, dass man zB für ein Border-Control nicht zugleich den MouseEnter- und MouseLeave-Event auf ein Command umleiten kann. Um das mit der Lösung der Attached Properties zu erreichen, wäre es notwendig, einen zusätzlichen Wrapper rund um das Border-Control einzuführen:


<Border Margin="50"
        commanding:AttachedCommand.Command="{Binding MouseEnterCommand}"
        commanding:AttachedCommand.EventName="MouseEnter">
   <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
           Background="YellowGreen" CornerRadius="20"
           commanding:AttachedCommand.Command="{Binding MouseLeaveCommand}"
           commanding:AttachedCommand.CommandParameter="Border #1"
           commanding:AttachedCommand.EventName="MouseLeave">
   </Border>
</Border>

Um diesem Umweg zu vermeiden, möchte ich heute einen neuen Ansatz präsentieren, der die seit Expression Blend 3 zur Verfügung stehenden Behaviors und Triggers verwendet.

TargetedTriggerAction aus System.Windows.Interactivity verwenden
Die Idee dahinter ist, einen Trigger zu erstellen, der diverse Dependency Properties implementiert. Diese Properties müssen deshalb Dependency Properties sein, weil wir sonst kein Databinding darauf anwenden können. Eine dieser DP ist vom Typ ICommand — also das Command, das wir ausführen wollen, wenn der Trigger gestartet wird:


public ICommand Command
{
   get { return (ICommand)GetValue(CommandProperty); }
   set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
 ´  DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTrigger),
                                new PropertyMetadata(null, OnCommandChanged));

Wichtig bei der Dependency Property ist der Callback OnCommandChanged, der dann aufgerufen wird, wenn der DP ein neuer Wert zugewiesen wird.
Warum ist das wichtig? Bei einer Command-Property, die out-of-the-box bei bereits vorhandenen Controls wie Button vom Framework mitgeliefert wird, wird der Status des Controls abhängig von der CanExecute-Methode des Commands verändert. Also liefert CanExecute false zurück, dann wird der Button, der das Command verwendt, deaktiviert. Genau diese Funktionalität müssen/sollen/wollen wir mit unserem Trigger auch abbilden.

Deshalb registrieren wir uns  in der OnCommandChanged Methode auf den CanExecuteChanged Event, den jede Klasse, die ICommand implementiert, selbstverständlich auch implementieren muss. Sobald dieser Event geworden wurde, wollen wir prüfen, ob unser Control, das ein Binding auf das Command besitzt, noch einen gültigen Status besitzt:


private static void OnCommandChanged(DependencyObject action, DependencyPropertyChangedEventArgs e)
{
   ((CommandTrigger)action)._RefreshCommandHandling((ICommand)e.OldValue, (ICommand)e.NewValue);
}

private void _RefreshCommandHandling(ICommand oldCommand, ICommand newCommand)
{
   if (oldCommand != null) oldCommand.CanExecuteChanged -= new EventHandler((sender, args) => _UpdateCanExecute());
   if (newCommand != null) newCommand.CanExecuteChanged += new EventHandler((sender, args) => _UpdateCanExecute());

   _UpdateCanExecute();
}

Wir bekommen im Eventhandler über die EventArgs den alten und den neuen Wert, den die Dependency Property angenommen hat. Das wir haben die Möglichkeit, uns vom Event des alten Commands (also des alten Wertes der DP) zu entfernen und uns auf den Event des neuen Commands (also der neue Wert der DP) zu registrieren. In der _UpdateCanExecute() Methode prüfen wir schlussendlich, ob das Control, auf dem der Trigger angewendet wird, noch den gültigen Status aufgrund von CanExecute besitzt:


private void _UpdateCanExecute()
{
   if (this.Command == null) return;

   RoutedCommand command = this.Command as RoutedCommand;

   if (command != null) this.IsEnabled = command.CanExecute(this.CommandParameter, this.CommandTarget);
   else this.IsEnabled = this.Command.CanExecute(this.CommandParameter);

   if (this.Target != null && this.SyncOwnerIsEnabled) this.Target.IsEnabled = this.IsEnabled;
}

Im XAML Code wird dieser Trigger jetzt ganz einfach verwendet, indem wir zuerst den richtigen Namespace einbinden und anschließend den Trigger auf ein beliebiges Control anwenden:


<Border>
   <interactivity:Interaction.Triggers>
      <interactivity:EventTrigger EventName="MouseEnter">
         <commanding:CommandTrigger Command="{Binding MouseEnterCommand}" />
      </interactivity:EventTrigger>

      <interactivity:EventTrigger EventName="MouseLeave">
         <commanding:CommandTrigger Command="{Binding MouseLeaveCommand}"  />
      </interactivity:EventTrigger>
   </interactivity:Interaction.Triggers>
</Border>

Das tolle an diesem Trigger ist jetzt, dass er mehrmals auf ein und dem selben Control angewendet werden kann. Wir können jetzt also mehrere Events über den selber Trigger behandeln und auf Commands weiterleiten.

Das Demoprojekt gibts wie immer unter http://downloads.juergenoberngruber.at/blog/CommandAction_Trigger.zip.

In diesem Sinne.

Mar 07

MVVM: Behandeln von Events über Commands, Teil 1

Durch WPF ist eine neue Softwarearchitektur sehr in Mode gekommen: MVVM (Model – View – ViewModel; http://en.wikipedia.org/wiki/Model_View_ViewModel). Dank Databinding und Commanding ist diese Softwarearchitektur in WPF relativ einfach umsetzbar. Ein Problem, dass es jedoch nach wie vor gibt, ist die oft fehlende Command-Property bei diversen WPF Controls bzw. die fehlende Möglichkeit, Commands aufgrund von Events aufzurufen.

Ein Beispiel?
Angenommen wir haben eine View, die eine ListView verwendet. Diese ListView stellt einen Event namens “SelectionChanged” zur Verfügung. Wir haben über WPF nun keine Standard-Lösung, wie wir diesen Event auf ein Command einer ViewModel Klasse leiten. Also, wie wir ein Command aufrufen, sobald der Event der ListView gefeuert wird/wurde.

Abhilfe über Attached Properties
Die Lösung liegt in den von WPF eingeführten Attached Properties (http://msdn.microsoft.com/en-us/library/ms749011.aspx).

Wir wollen also erreichen, das ein Command ausgefürt wird, sobald eine Event gefeuert wird. Wir erstellen uns zuerst eine Klasse, die zwei unterschiedliche Attached Properties beinhaltet: Command und EventName. Command ist das Command, das ausgeführt werden soll, sobald der Event gefeuert wird. EventName ist der Name des Events, auf den wir horchen wollen — also der Event, der das Command triggern soll. Wichtig dabei ist, dass wir Callback-Methoden registrieren, die aufgerufen werden, sobald die Attached Property gesetzt wird. In diesen Eventhandler registrieren wir über .NET Reflection eine anonyme Methode als Eventhandler für den Event, der über die Attached Property angegeben wurde:


public class AttachedCommand : DependencyObject
{
   private static IDictionary<DependencyObject, CommandParameter> Parameter { get; set; }

   public static ICommand GetCommand(DependencyObject obj)
   {
      return (ICommand)obj.GetValue(CommandProperty);
   }
   public static void SetCommand(DependencyObject obj, ICommand value)
   {
      obj.SetValue(CommandProperty, value);
   }
   public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(AttachedCommand), new UIPropertyMetadata(CommandChanged));

   public static string GetEventName(DependencyObject obj)
   {
      return (string)obj.GetValue(EventNameProperty);
   }
   public static void SetEventName(DependencyObject obj, string value)
   {
      obj.SetValue(EventNameProperty, value);
   }
   public static readonly DependencyProperty EventNameProperty =
       DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(AttachedCommand), new UIPropertyMetadata(EventNameChanged));

   public static void CommandChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
   {
      if (!Parameter.ContainsKey(dependencyObject)) Parameter.Add(dependencyObject, CommandParameter.Default);

      Parameter[dependencyObject].Command = args.NewValue as ICommand;

      _AttachEventHandler(dependencyObject);
   }

   public static void EventNameChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
   {
      if (!Parameter.ContainsKey(dependencyObject)) Parameter.Add(dependencyObject, CommandParameter.Default);

      Parameter[dependencyObject].EventName = args.NewValue as string;

      _AttachEventHandler(dependencyObject);
   }

   private static void _AttachEventHandler(DependencyObject dependencyObject)
   {
      if (dependencyObject == null) throw new ArgumentNullException("DependencyObject is null");
      if (!Parameter.ContainsKey(dependencyObject)) return;

      CommandParameter parameter = Parameter[dependencyObject];
      if (parameter.Command == null || string.IsNullOrEmpty(parameter.EventName)) return;

      EventInfo eventInfo = dependencyObject.GetType().GetEvent(parameter.EventName);
      if (eventInfo == null)
          throw new InvalidProgramException(string.Format("Cannot find am event with the name <{0}>.", parameter.EventName));

      parameter.Callback = _CreateHandler(eventInfo, () => { parameter.Command.Execute(null); });

      eventInfo.AddEventHandler(dependencyObject, parameter.Callback);
   }
}

Parameter speichert eine Liste von CommandParameter, die notwendige Metadaten für das Commanding speichert. Wir brauchen diese Liste deshalb, weil wir die Attached Properties ja auf unterschiedlichen WPF Controls verwenden können. In der _AttachEventHandler Methode holen wir uns den Event über Reflection und speichern einen neuen Eventhandler für den Event, der schlussendlich das Command ausführt.

In der View können wir diese Attached Properties jetzt ganz einfach verwenden, in dem wir zuerst den Namespace einbinden und anschließend auf einem beliebigen Controls die Properties anwenden:

<Window x:Class="CommandAction.Views.Master"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:commanding="clr-namespace:CommandAction.Commanding"
             xmlns:viewmodels="clr-namespace:CommandAction.ViewModels"
             Title="Master" Height="306" Width="722"
             WindowStyle="ToolWindow" WindowStartupLocation="CenterScreen">

    <Window.DataContext>
        <viewmodels:MasterViewModel />
    </Window.DataContext>

    <Border Margin="50" CornerRadius="20" Background="YellowGreen"
               commanding:AttachedCommand.Command="{Binding MouseLeaveCommand}" commanding:AttachedCommand.EventName="MouseLeave">
    </Border>

</Window>

Problem?
Soweit so gut. Das einzige Problem das wir über diese Methode bekommen ist, dass wir für ein WPF Control nur einen Event auf ein Command umleiten können, weil für die Attached Properties nur einmal auf ein WPF Control anwenden können. Wollen wir mehrere Events auf Commands umleiten, müssen wir uns einen anderen Weg einfallen lassen. Angenommen wir wollen für ein Image Control den MouseEnter und MouseLeave Event über zwei unterschiedliche Commands behandeln, können wir das mit der Attached Property Variante nicht umsetzen (außer wir wrappen rund um das Image Control ein anderes Control).
Lösen können wir das mit den seit Expression Blend 3 zur Verfügung stehenden Behaviors/Triggers. Wie das ganze funktioniert, erklär ich in meinem nächsten Post, der in den nächsten Tagen kommen wird.

Das Demoprojekt gibts wie immer unter http://downloads.juergenoberngruber.at/blog/CommandAction.zip zu finden.

In diesem Sinne.

Feb 27

INotifyPropertyChanged mit Hilfe von AOP lösen

Heute möchte ich einmal eine Technik vorstellen, die ich schon öfters bei meinen WPF Projekten eingesetzt habte. Verwendet man in WPF die MVVM-Architektur (ModelView – View – Model), so kommt man um das Konzept des mächtigen WPF Databindings nicht herum. Wer mit Databinding noch nicht so vertraut ist, soll sich zuerst mal hier http://msdn.microsoft.com/en-us/library/ms750612.aspx etwas schlau machen.

Das interessante am WPF Databinding ist die Möglichkeit, Werte bidirektional zu binden: Das heißt, dass man einerseits die Daten von der Datenquelle zur Datensenke binden kann und andererseits auch Änderungen der Datensenke automatisch in die Datenquelle übernehmen kann. In MVVM würde das bedeuten, dass man auf der View (XAML) UI-Controls aus Daten des ViewModels befüllen kann und Änderungen, die auf der View auftreten automatisch zurück in das ViewModel speichern kann, ohne dabei eine Benutzerinteraktion zu benötigen. Wichtig ist auch, dass bereits gebundene Werte auf der View automatisch aktualisiert werden sollen, sobald im ViewModel – also bei der Datenquelle – Änderungen auftreten.

INotifyPropertyChanged oder DependencyProperties
Um ein automatisches Binding-Update zu erreichen, gibt es zwei unterschiedliche Wege, das zu erreichen:

  1. Man leitet eine Klasse von DependencyObject ab und implementiert DependencyProperties. Da ein ViewModel normalerweise nicht von DependencyObject ableitet, ist diese Möglichkeit im Kontext von MVVM nicht wirklich brauchbar. Mehr über DependencyObject und DependencyProperty gibts unter http://msdn.microsoft.com/en-us/library/system.windows.dependencyobject.aspx
  2. Man implementiert das Interface INotifyPropertyChanged (http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx). Dieses Interface implementiert einen Event names PropertyChanged, der gefeuert wird, sobald sich eine Property bzw. der Wert einer Property verändert hat. Bindet man nun in der View (XAML) auf eine Property eines ViewModels, muss diese Property jedesmal, wenn sich der Wert der Property verändert, diesen Event feuern, um automatisch das Databinding zu aktualisieren. Wirft es den Event nicht, gibts auch keine automatische Aktualisierung der View.

Das war mal das Grundgerüst, dass wir für unsere weiteres Thema heute benötigen.

Wo ist das Problem?
Sehen wir uns einmal folgende Klasse an, die INotifyPropertyChanged implementiert und ein paar Properties für das Databinding zur Verfügung stellt:


public class ApplicationViewModel : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

    private string _ApplicationTitleField = string.Empty;
    public string ApplicationTitle
    {
       get { return _ApplicationTitleField; }
       set
       {
          _ApplicationTitleField = value;
          if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ApplicationTitle"));
       }
    }

    private string _ApplicationVersionField = string.Empty;
    public string ApplicationVersion
    {
        get { return _ApplicationVersionField; }
        set
        {
           _ApplicationVersionField = value;
           if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ApplicationVersion"));
        }
     }
}

So, wo ist hier jetzt das Problem? Genau, die Properties selbst. Im Grunde genommen ist es kein Problem bzw. ist syntaktisch alles in Ordnung. Ich persönlich habe aber ein Problem damit, aus den Automatic Properties “alte .NET 2.0″ Properties zu erzeugen, nur um im Setter der Property den PropertyChanged Event feuern zu können. Wie lösen wir das Ganze jetzt: Wir verwenden ein tolle Bibliothek names PostSharp, die die Prinzipen von AOP (Aspect Oriented Programming) implementiert.

Lösung: AOP mittel PostSharp
Mit AOP können wir Funktionalität in sogenannte Aspekte auslagern und diese nach Bedarf wiederverwenden. Genau so einen Aspekt können wir jetzt für unser Problem mit dem PropertyChanged Event einsetzen. Wir können hergehen und uns selbst ein die notwendige Funktionalität programmieren oder wir verwenden bereits vorhandene Bibliotheken, die uns bei unserer Problemlösung unterstützen.

In der Community gibt es mittlerweile eine handvoll verfügbaren Bibliotheken, die AOP Funktionalität für .NET zur Verfügung stellen. Mein persönlicher Favorit ist auf alle Fälle PostSharp entwickelt von Gael Fraiteur. Einer der großen Vorteile von PostSharp ist , dass sich die Bibliothek in den MSBuild Process hängt, direkt den CIL Code manipuliert und somit keine Perfomanceeinbussen entstehen. Wie gehen wir jetzt also vor, um unser Problem der alten .NET Properties mit Hilfe eines Aspekts zu lösen?

  1. Zuerst laden wir uns mal PostSharp herunten und installieren es: http://www.sharpcrafters.com/postsharp/download
  2. Um PostHsarp verwenden zu können, müssen wir 2 Referenzen zu unserem Projekt hinzufügen: PostSharp.Public und PostSharp.Laos
  3. Dann erstellen wir uns den neuen Aspekt. Im Grunde ist das eine einfach Klasse, die von einer bestimmten Basisklasse ableitet und als Serializeable gekenntzeichnet werden muss:
    [Serializable]
    public class NotifyAspect : OnMethodBoundaryAspect
    {
    }
    
  4. Die Basisklasse OnMethodBoundaryAspect stellt PostSharp zur Verfügung. Diese Klasse bietet eine Methode names OnExit an, die wir in unserem Apsekt einfach überschreiben und implementieren. Diese Methode wird aufgerufen, wenn eine Methode verlassen wird — also wenn das Ende einer Methode erreicht wurde. Da wir unseren Aspekt für Properties verwenden, ist unsere Methode eigentlich der Getter bzw. Setter – Zugriff der Property:
    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
       if (!(eventArgs.Instance is ViewModelBase))
          throw new InvalidCastException("Cannot raise PropertyChanged on viewmodel that doesn't derived from ViewModelBase");
    
       if (!eventArgs.Method.IsSetter()) return;
    
       ViewModelBase viewModel = eventArgs.Instance as ViewModelBase;
    
       viewModel.OnPropertyChanged(eventArgs.Method.SetterName());
    }
    

    Wir prüfen,ob die Instanz, zu der die Methode gehört von einer bestimmten Basisklasse ist. Ist das nicht so, dann werfen wir einen Fehler, da der Aspekt nur mit bestimmen Typen umgehen kann. Warum? Weil wir später eine Methode der Instanz aufrufen müssen. Jetzt überprüfen wir, ob die Methode, für die der Aspekt aufgerufen wird eigentlich eine Setter-Methode einer Property ist. Das haben wir in eine Extension-Methode ausgelagert (der detaillierte Code ist im Beispielprojekte nachzulesen). Jetzt können wir casten und eine bestimmte Methode der Basisklasse aufrufen: OnPropertyChanged wirft im Endeffekt den PropertyChanged Event des Interfaces INotifyPropertyChanged. Diesen Event können wir nur in der Klasse werfen, in der der Event auch definiert wurde. Daher müssen wir hier in unserem Aspekt diesen Umweg gehen.

    public void OnPropertyChanged(string propertyName)
    {
       if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Propertyname is null or too short.");
    
       PropertyInfo info = this.GetType().GetProperty(propertyName);
       if (info == null) throw new InvalidProgramException("The requested property doesn't exist.");
    
       if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    
  5. Jetzt fehlt uns nur noch, dass wir den Aspekt in unserem ViewModel verwenden:
    public class ApplicationViewModel : ViewModelBase
    {
       [Notify]
       public string ApplicationTitle { get; set; }
    
       [Notify]
       public string ApplicationVersion { get; set; }
    }
    

Wir können uns jetzt eine View bzw. XAML Seite bauen, die dieses ViewModel als DataContext gesetzt hat und auf die beiden Properties databinden. Das Demoprojekt ist wie immer unter http://downloads.juergenoberngruber.at/blog/INotifyPropertyChangedWithAOP.zip zu finden.

In diesem Sinne.

Nov 22

HowTo: Behaviors/Triggers richtig einsetzen

Mit Expression Blend 3 ist ein neues Konzept gekommen, das definitiv eine großartige Erweiterung des Funktionsumfangs von WPF und Silverlight darstellt. Hat man bisher fehlende Funktionalität eines UI Controls über Umwege erstellt (zb das Ausfrühen von Commands, sobald ein RoutedEvent eines Controls auftritt), kann man das realtiv einfach und zentral verwalten: die Rede ist von den Behaviors und Triggers!

Was sind Behaviors und Triggers?
Es handelt sich um ein Konzept, gewisse Funktionalität zentral auszulagen und später auf jedes UI Control anzuhängen. Wann, was und wie das Behavior bzw. der Trigger die Dinge erledigt spielt keine Rolle bzw. entscheidet das Behavior bzw. der Trigger von selbst. Unterschieden wird zwischen drei unterschiedlichen Typen:

  • Behavior: Diese Klasse ist die am meisten verwendete. Dabei implementiert das Behavior selbst vollkommen autonom was wann und wie passieren soll. Das UI Control, dass das Behavior verwendet hat keinerlei Kontrolle darüber, was im Behavior selbst passiert.
  • TriggerAction: Diese Klasse wird direkt an einen RoutedEvent gehängt und aufgerufen, sobald dieser Event auftritt.
  • TargetedTriggerAction: Diese Klasse ist der TriggerAction sehr ähnlich — jedoch kann dabei ein anderes UI Control beim RoutedEvent verändert werden

Alle drei Klassen haben generische Gegenstücke, bei denen noch expilizit das UI Control angegeben werden kann, für das das Behavior verwendet werden kann.

Custom Behavior erstellen!
Wir wollen uns nun ein eigenes Behavior erstellen, das relativ einfach ist, aber ein, meiner Meinung nach, gängies Problem lösen soll. Es soll die Maus verändern, sobald der Benutzer über einen Control bewegt, das angeklickt werden kann. Funktionaliätit, die es im Web schon lange gibt, auf Rich Client Anwendungen aber fehlt.

Als erstes erstellen wir uns eine Klasse, die von Behavior ableitet. Behavior liegt im Assembly System.Windows.Interactivity, dass standarmäßig nicht inkludiert ist. Zu finden sollte es unter

%Progam Files%\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\WPF (oder Silverlight)

sein. Haben wir die Klasse, können wir unterschiedliche Methoder überschreiben. Die zwei wichtigsten für unser Demo sind OnAttached() und OnDetaching(). In OnAttached registrieren wir uns Eventhandler für die Events MouseEnter und MouseLeave. Im OnDetaching entfernen wir diese Eventhandler wieder.

Betritt nun die Maus das Control, wird der Eventhandler für MouseEnter aufgerufen, in dem wir den Mauszeiger verändern. In MouseLeave setzen wir ihn wieder auf den Standard zurückgesetzt. Ganz einfach eigentlich.


public class ClickableBehavior : Behavior<FrameworkElement>
{
     protected override void OnAttached()
     {
          this.AssociatedObject.MouseEnter += new MouseEventHandler(_MouseEnter);
          this.AssociatedObject.MouseLeave += new MouseEventHandler(_MouseLeave);

          base.OnAttached();
     }

     protected override void OnDetaching()
     {
          this.AssociatedObject.MouseEnter -= new MouseEventHandler(_MouseEnter);
          this.AssociatedObject.MouseLeave -= new MouseEventHandler(_MouseLeave);

          base.OnDetaching();
     }

     /// <summary>
     /// Der Typ des Cursos, auf den das Behavior wechsel soll.
     /// </summary>
     public Cursor EnterCursor
     {
          get { return (Cursor)GetValue(EnterCursorProperty); }
          set { SetValue(EnterCursorProperty, value); }
     }
     public static readonly DependencyProperty EnterCursorProperty =
           DependencyProperty.Register("EnterCursor", typeof(Cursor),
           typeof(ClickableBehavior), new UIPropertyMetadata(Cursors.Hand));

     #region Eventhandler

     /// <summary>
     /// Wird aufgerufen, sobald die Maus das FrameworkElement betritt.
     /// </summary>
     private void _MouseEnter(object sender, MouseEventArgs e)
     {
          this.AssociatedObject.Cursor = EnterCursor;
     }

     /// <summary>
     /// Wird aufgerufen, sobald die Maus das FrameworkElement verlässt.
     /// </summary>
     private void _MouseLeave(object sender, MouseEventArgs e)
     {
          this.AssociatedObject.Cursor = null;
     }

     #endregion Eventhandler
}

Nachdem wir die Logik nun haben, können wir das Behavior jetzt verwenden. Das tun wir, indem wir im XAML Code zuerst den richtigen Namespace einbinden (System.Windows.Interactivity und unseren lokalen Namespace) und dann auf ein beliebiges Control das Behavior anfügen:


<Grid Margin="50" Background="Gold">
    <interactivity:Interaction.Behaviors>
         <local:ClickableBehavior />
    </interactivity:Interaction.Behaviors>
</Grid>

Das Demoprojekt kann wie immer hier  heruntergeladen werden: Download Demoprojekt (VS 2010 Solution).

In diesem Sinne.

Nov 21

HowTo: WPF Fenster mit eigener Titelleiste

Letztens stand ich vor der Herausforderung, bei einem Projekt ein nettes, anspruchvolles UI zu gestalten. Ein gutes UI sollte individuell sein und nicht zu sehr am Look & Feel des Betriebssystem hängen. Dank WPF ist sowas ja kein Problem mehr (zumindest mit nicht allzu großem Aufwand).

Ein WPF Fenster mit eigenen Titelleiste?
Ein Fenster einer Anwendung individuell zu gestalten, bedeutet für mich auch zugleich, eine eigene Titelleiste zu rendern und nicht die des OS zu verwenden. Mit WPF geht das relativ einfach.

1. Rahmenloses Fenster erstellen
Als erstes erstellen wir uns mal ein ein WPF Projekt in Visual Studio. Um nun aus dem “normalen” Fenster ein Fenster zu erzeugen, dass eine eigene Titelleiste render kann, müssen wir zunächst die Standardleiste von Windows entfernen. In WPF geht das ganz einfach, in dem wir die Properties

  • “WindowStyle” auf “None” und
  • “AllowsTransparency” auf “True“

setzen. Somit haben wir mal unser ersten Ziel erreicht und haben ein rahmenloses Fenster.

2. Titelleiste aus einem Usercontrol bauen
Damit wir nun eine eigene Titelleiste rendern können, ist es am sinnvollsten, das gesamte UI inklusive Logik in ein Usercontrol auszulagen.
Also in Visual Studio ein Usercontrol zum Projekt hinzufügen und darin dann das gewünschte Aussehen in XAML erzeugen. Das Ganze sieht bei mir ca. so aus (ich habe mittlerweile das Fenster wieder auf weiß umgestellt):

Nachdem wir nun das Aussehen definiert haben, müssen wir noch die Logik für die einzelnen Events hinzufügen. Ein “normales” Fenster hat folgendene Eigenschaften:

  • Minimieren
  • Maximieren
  • Schließen
  • Fenster verschieben

Um diese Funktionalität zu implementieren, müssen wir auf den einzelnen Controls Eventhandler registrieren, die wiederrum Hilfsfunktionen verwenden, die die genaue Funktionsweise implementieren.

2.1 Schließen
Sobald die Grafik zum “Beenden der Anwendung” geklickt wurde, führen wir folgenden Code aus:

private void _Exit()
{
   System.Windows.Application.Current.Shutdown();
}

2.2 Minimieren
Für das Minimieren des Fensters verwenden wir einfach die Eigenschaft “WindowState” des übergeordneten Fensters und setzen diese auf “Minimize”:

private void _Minimize()
{
   _Parent.WindowState = WindowState.Minimized;
}

2.3 Maximieren
Für das Maximieren ist es wichtig, das wir nicht, wie beim Minimieren, den WindowState des übergeordneten Fensters auf “Maximized” setzen, da wir sonst die Anwendung auf Fullscreen schalten –> und das wollen wir nicht. Stattdessen wollen wir nur den verfügbaren Platz des Screens verwenden (ohne Taskleiste, etc.). Das bekommen wir von SystemParameters.WorkArea. Abhängig von einem Flag, das wir uns intern mitspeichern, setzen wir die Abmessungen und die Position des übergeordneten Fensters. Wichtig dabei ist auch, das wir beim Maximieren die aktuelle Position des Fensterns speichern, damit wir beim zurückschalten von Maximized auf Normal die richtigen (alten, letzten) Abmessungen und die Position setzen können:

private void _Resize()
{
   if (_CurrentState == WindowState.Normal)
   {
      _PreviousPosition = new Dimensions
      {
          Left = _Parent.Left,
          Top = _Parent.Top,
          Width = _Parent.Width,
          Height = _Parent.Height
      };

      _Parent.Width = SystemParameters.WorkArea.Width;
      _Parent.Height = SystemParameters.WorkArea.Height;
      _Parent.Left = SystemParameters.WorkArea.Left;
      _Parent.Top = SystemParameters.WorkArea.Top;
      _CurrentState = WindowState.Maximized;
   }
   else
   {
      _Parent.Width = _PreviousPosition.Width;
      _Parent.Height = _PreviousPosition.Height;
      _Parent.Left = _PreviousPosition.Left;
      _Parent.Top = _PreviousPosition.Top;
      _CurrentState = WindowState.Normal;
   }
}

2.4 Fenster verschieben
Um das Fenster zu verschieben greifen wir auf Win32 zurück. Dort gibts zwei Methoden, mit denen man ein paar Win32 Messages versendet, um das Hauptfenster zu verschieben:

private void _Move()
{
   ReleaseCapture();
   SendMessage(new WindowInteropHelper(_Parent).Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}

In diesem Sinne.

  • Recent Posts
    • HowTo: Eigene Regeln für Microsoft StyleCop erstellen
    • HowTo: Microsoft StyleCop Integration mit Visual Studio und MSBuild
    • HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 2
    • HowTo: Eigene Templates für Visual Studio 2008 erstellen, Teil 1
    • Performance des Cassini Webservers in Kombination mit Firefox
  • Archives
    • June 2010 (2)
    • May 2010 (3)
    • March 2010 (3)
    • February 2010 (4)
    • January 2010 (3)
    • December 2009 (1)
    • November 2009 (9)
  • Tags
    .net AOP ASP.NET ASP.NET MVC blend Bootmanager C# ci Codequality Configuration Continuous Integration css Cursor DateTime DDD Deployment dynamisch emit Exrpession Extensibility Fluent HowTo Microsoft MVVM Pattern PostSharp Reflection Repository ruby silverlight Software Design StyleCop System teamcity Templates VHD Virtual Images Visual Studio Vorlagen web Windows 7 Windows Mobile WinForms WPF XAML
  • About

    Jürgen Oberngruber is a project manager and software architect living in Wels, Austria and currently working at ecomplexx Austria, Wels. During his study at the University of Applied Sciences in Hagenberg, Austria he gained a deep knowledge in the field of software engineering using a lot of different programming languages. Since a few years he's focusing on the Microsofts .NET platform including all relevant technologies. One of his passion is to explore, to test and to evaluate new technologies and programming languages (mostly in the field of Microsofts .NET platform). Checkout more information on www.juergenoberngruber.at

  • Meta
    • Log in
    • Entries RSS
    • Comments RSS
    • WordPress.org
  • Archives
    • June 2010
    • May 2010
    • March 2010
    • February 2010
    • January 2010
    • December 2009
    • November 2009
  • Search






  • Home

© Copyright Jürgen Oberngruber's Blog. All rights reserved.
Designed by FTL WordPress Themes brought to you by DT Web Template

Back to Top