Tutorialreihe <WPF lernen/>

    • WPF

    Es gibt 33 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

      2.1.4.8

      Auf die aktuelle Culture eingehen



      Die WPF biete diverse sinnvolle Mechanismen um z.b. den Text in einer TextBox so darzustellen wie dies der aktuelle Anwendungsfall erfordert.
      Das StringFormat beim Binding ist hier ein sehr gutes Beispiel welches wir bereits unter anderem in Kapitel 2.1.4.7 verwendet hatten.

      Aber wie sieht es eigentlich aus mit der Kultur? Kann ich das selbst steuern? Was ist der Default wert bei einer WPF Anwendung? Wie kann ich steuern ob wie ein Datum angezeigt wird oder welches Währungssymbol hinter einem Betrag steht? Das sind Fragen auf welche wir heute eingehen werden.
      Erstmal sein gesagt das die WPF per Default von en-US als Kultureinstellung ausgeht. Das bedeutet dass in allen Controls (DataGrid, TextBox, Label usw.) das amerikanische Format verwendet wird.
      Um dies zu demonstrieren habe ich mal ein Window erstellt welches dies demonstriert.

      XML-Quellcode

      1. <Window x:Class="MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      6. mc:Ignorable="d"
      7. Title="MainWindow" Height="450" Width="800">
      8. <DockPanel LastChildFill="True">
      9. <Border BorderThickness="2" BorderBrush="Black" DockPanel.Dock="Bottom">
      10. <StackPanel DataContext="{Binding SelectedPerson}">
      11. <TextBox Text="{Binding Firstname}"/>
      12. <TextBox Text="{Binding Lastname}"/>
      13. <TextBox Text="{Binding Birthday}"/>
      14. <TextBox Text="{Binding Credit, StringFormat=\{0:C\}}"/>
      15. </StackPanel>
      16. </Border>
      17. <DataGrid ItemsSource="{Binding Persons}" SelectedItem="{Binding SelectedPerson}"/>
      18. </DockPanel>
      19. </Window>




      Wie man gut erkennen kann wird sowohl für den Geburtstag als auch für das Guthaben der Personen die amerikanische Kultur verwendet. Bis hin zum Dollar-Zeichen.
      Das kann auch zu Problemen führen. Wenn wir und die TextBox für das Guthaben unten genauer ansehen sehen wir das hier das „Tausender-Trennzeichen“ ein Komma ist und das Komma ein Punkt.

      Viele gehen nun her und verwenden schlicht statt dem StringFormat \{0:C\ ein eigenes StringFormat und denken dass damit die Sache erledigt ist. Weit gefehlt.
      Gibt der User nun einen Betrag wie 1493,67 ein wird die WPF in diesem Fall nämlich 149367 als Wert zurückspeichern. Fatal! Man müsste nämlich 1494.67 eingeben.
      Also ist es wichtig dass wir die richtige Kultur (Culture) verwenden. Ob man nun die Culture des Windows Benutzers mittels CultureInfo.CurrentCulture verwendet oder ob man es den Benutzer aussuchen lässt (Beispielsweise über Einstellungen) bleibt einem hier selbst überlassen.

      Am besten setzt man die Kultur in der Application_Startup Methode, kann allerdings natürlich auch sonst wo gesetzt werden wie Beispielsweise im Window_Loaded des Startfensters.

      VB.NET-Quellcode

      1. Dim currentCulture As CultureInfo = CultureInfo.CurrentCulture Thread.CurrentThread.CurrentCulture = currentCulture Thread.CurrentThread.CurrentUICulture = currentCulture LanguageProperty.OverrideMetadata(GetType(FrameworkElement), New FrameworkPropertyMetadata(
      2. XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)))


      Möchte man nicht die Systemkultur verwenden dann kann die erste Zeile wie folgt ersetzt werden:

      VB.NET-Quellcode

      1. Dim currentCulture As CultureInfo = New CultureInfo("de-DE")


      So könnte in den Programmeinstellungen der String für die Kultur hinterlegt werden.
      Hier ein kleiner Einblick was in der Culture so hinterlegt ist:



      Wie wir sehen geht es bei der Kultur nicht einfach nur um die Sprache, sondern auch um Tastaturlayouts, Kalender, Nummernformate usw.!
      Es gibt auch Sprachen welche man von rechts nach links liest wie z.b. Arabisch.
      OK, sehen wir uns mal an wie dies aussieht wenn wir die aktuelle Systemsprache auf das oben bereits gezeigt Fenster anwenden indem wir im Window_Loaded Event die Culture setzen.



      Das sieht schon besser aus. Wir haben sowohl das Deutsche Datumsformat als auch das Euro-Symbol und das korrekte Trennzeichen.
      Geben wird nun einen Betrag mit einem Komma wie 1657,47 ein wird der Wert nun auch korrekt gespeichert.

      Converter
      Als nächstes sehen wir uns an wie dies mit Convertern aussieht. Converter haben wir ja bereits in Kapitel 2.1.4.4 kennengelernt. Nun sehen wir uns an wie wir innerhalb eines Converters auf die aktuelle Kultur eingehen.
      Hierfür erstellen wir einen Konverter mit dem Namen „TestConverter“. Dieser konvertiert im Moment nichts. Er soll uns nur dazu dienen zu sehen welche Culture wir in einem Converter hineingereicht bekommen.

      VB.NET-Quellcode

      1. Public Class TestConverter
      2. Implements IValueConverter
      3. Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
      4. Debug.WriteLine("Aktuelles Währungssymbol: " & culture.NumberFormat.CurrencySymbol)
      5. Return value
      6. End Function
      7. Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      8. Throw New NotImplementedException()
      9. End Function
      10. End Class


      Wir lassen uns hier schlicht das aktuelle Währungssymbol in die Konsole ausgeben um anschließend einfach den value so wie dieser ist wieder zurückzugeben.
      Der Konverter gibt folgenden Text in die Konsole aus:

      Quellcode

      1. Aktuelles Währungssymbol: €


      Wir sehen also dass hier auch die Systemkultur mit übergeben wird und könnten nun darauf reagieren um Beispielsweise Umwandlungen zu vollziehen.

      Fazit: Die WPF nimmt per Default immer en-US als Culture heran.
      Dies muss vom Entwickler überschrieben werden da sonst immer und überall das amerikanische Format verwendet wird.
      Dies ist wie wir gesehen haben auch sehr wichtig da es ansonsten zu sehr unangenehmen Nebeneffekten kommen kann welche oft gerne mal länger unentdeckt bleiben.


      Natürlich erkläre ich dies alles auch wieder interaktiv in einem Video wo ihr besser sehen könnt wie jeder Schritt funktioniert.
      Ich freue mich auch über jedes Like :thumbup: oder ein Kommentar :whistling: im Video oder dem Kanal.




      Hier die Solution: 2_1_4_8_Culture.zip
      Und wie immer auch das PDF für euer E-Book:
      Tutorialreihe WPF lernen 2_1_4_8.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.5.(1) Dependency Properties

      2.1.5

      Dependency Properties



      Jeder der mit der WPF Arbeitet hört früher oder später von Dependency Properties. Sie sind Voraussetzung für Styles, Trigger oder Animationen. Jeder verwendet Dependency Properties da jedes Control diverse Eigenschaften besitzt welche als Dependency Properties implementiert sind. Bei den BuildIn Controls sind dies fast alle Properties.

      2.1.5.1

      Was sind Dependency Properties und wie unterscheiden sie sich von normalen Properties



      Generell wird bei Dependency Properties zwischen zwei Arten unterschieden:
      • Dependency Properties, die durch klassische .Net Properties gekapselt werden. Diese werden auf einfach Dependency Properties genannt.
      • Dependency Properties, welche durch zwei statische Methoden gekapselt werden. Dabei wird die Dependency Property auf Objekte anderer Klassen gesetzt. Diese werden auch als Attached Properties genannt.

      Wir werden klären wann und warum eine Dependency Property implementiert werden sollte bevor wir dann eigene Dependency Properties erstellen und Anwenden. Ich werde auch auf die Metadaten, die Validierung und Logik dahinter eingehen.

      DependencyObject und DependencyProperty

      Die Klasse DependencyProperty definiert im Grunde nur den Schlüssel zu einem Wert einer Dependency Property, später dazu mehr.
      DependencyObject definiert zum Setzen oder Abfragen einer Dependency Property die Methoden GetValue und SetValue. Eine DependencyProperty-Instanz stellt im Grunde lediglich den Schlüssel zum eigentlichen Wert dar.
      Vereinfacht gesehen speichert eine DependencyObject-Instanz die Werte von DependencyProperties intern in einer Art IDictionary-Instanz unter dem entsprechenden Schlüssel ab. Da die Klasse DependencyObject für das Speichern und Verwalten von DependencyProperties zuständig ist können auch nur Objekte welche von DependencyObject ableiten Werte einer Dependency Property speichern.

      Der Wert einer DependencyProperty ist von vielen Faktoren abhängig, daher der Name Dependency Property (Dependency = Abhängigkeit). Der Wert kann durch ein DataBinding oder eine Animation geändert werden. Die Dependency Property kann einen Default-Wert haben und sogar durch ein im Logical Tree (der Elementebaum in der WPF) höher liegendem Element oder weiteren Metadaten beeinflusst werden welche Beispielsweise auch einen Layoutprozess auslösen können. All diese Dinge müssen bedacht und auch Priorisiert werden. Z.b. muss der Wert einer Animation immer Vorrang vor einem lokal gesetzten Wert haben. Diese Logik übernimmt uns die Property Engine. Die Engine ist im Grunde etwas Code innerhalb von DependencyObject welcher uns diese Arbeit abnimmt wodurch einiges in der WPF gerade in Verbindung mit Binding „wie von Geisterhand“ wirkt.
      Wie bereits angesprochen sind fast alle Properties von Elementen der WPF als Dependency Properties implementiert. Auch in eigenen Klassen können wir Dependency Properties implementieren, der größte Vorteil ist das Properties welche als Dependency Properties implementiert sind, bereits über einen internen Benachrichtigungsmechanismus verfügen und somit kein INotifyPropertieChanged implementiert werden und das Event OnPropertieChanged geworfen werden muss. Dadurch ist eine Dependency Property ideal für DataBinding geeignet. Neben diesem Vorteil gibt es noch andere Vorteile wie Default-Werte, oder Metadaten welche eben nur mit Dependency Properties zur Verfügung stehen.

      Nachstehend eine Tabelle in welche ich diese einfach mal als Services bezeichne:

      Service
      Beschreibung
      Animationen
      Die WPF besitzt eine sehr gute Unterstützung von Animationen welche nur von Dependency Properties verwendet werden können.
      Metadaten
      Eine Dependency Property besitzt MetaDaten über welche sehr viel gesteuert werden kann wie Beispielsweise der Default-Wert.
      Expressions
      Expressions ermöglichen es das der Wert eines Dependency Property zur Laufzeit dynamisch ermittelt werden kann. Z.b. sind DataBinding und DynamicResources über Expressions implementiert.
      Data Binding
      DataBinding ist über Expressions implementiert, dabei muss die Ziel-Property ein DependencyProperty sein.
      Styles
      Styles haben wir bereits kennengelernt. In einem Style lassen sich allerdings nur Properties setzen welche als Dependency Property implementiert sind.
      Vererbung
      Der Wert einer Dependency Property kann über den Logical Tree vererbt werden was sich über die Metadaten definieren lässt.


      Im nächten Kaptiel erstellen und Rendern wir eine Ellipse welche einen Text enthält unter Verwendung von Dependency Properties und machen diese sogar auch abhängig zueinander um zu sehen wie sich dies auf deren Werte auswirkt.

      Und wie immer auch das PDF für euer E-Book: 2.1.5.1.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.5.2 Wir erstellen und Rendern eine „TextEllipse“ mittels Dependency Properties

      2.1.5.2

      Wir erstellen und Rendern eine "TextEllipse" mittels DependencyProperties



      Um die Funktionsweise und das Zusammenspiel von Dependency Properties in der Praxis kennenzulernen möchte ich ein Simples Beispiel erstellen welches eine Ellipse rendern soll in welcher sich in der Mitte ein Text befindet.
      Dies könnte z.b. so aussehen:


      Um auch Binding und die Manipulation von Dependency Properties kennenzulernen soll man im XAML für diese Ellipse die Eigenschaft Diameter angeben können, welche den Durchmesser beschreibt. Weiters soll man einen Text innerhalb des XAML angeben können.
      Was aber wenn der Text länger wird als der Durchmesser der Ellipse zulässt? In diesem Fall soll der gesetzte Wert des Durchmessers überschrieben werden damit sich die Ellipse automatisch dementsprechend vergrößert. Unabhängig davon was zuvor im XAML als Wert für den Durchmesser angegeben wurde.

      Wir legen uns eine neue Klasse mit dem Namen TextEllipse an.
      Diese Klasse erbt von FrameworkElement (da wir das Element auch Rendern möchten).

      VB.NET-Quellcode

      1. Public Class TextElipse
      2. Inherits FrameworkElement
      3. End Class




      Hinweis: FrameworkElement erbt von UIElement-> UIElement erbt von Visual-> Visual erbt von DependencyObject



      Das ist der Grund warum wir hier auch Dependency Properties verwenden können.




      Wir wissen also dass wir zwei DependecyProperty benötigen. Eines für den Text und eines für Diameter.

      Wir beginnen mit dem Property Text:

      VB.NET-Quellcode

      1. Public Property Text As String
      2. Get
      3. CType(GetValue(TextProperty), String)
      4. End Get
      5. Set(ByVal value As String)
      6. SetValue(TextProperty, value)
      7. End Set
      8. End Property
      9. Public Shared ReadOnly TextProperty As DependencyProperty =
      10. DependencyProperty.Register("Text",
      11. GetType(String), GetType(TextEllipse))


      Sehen wir uns dieses Konstrukt mal genauer an. Das eigentliche Property sieht wie ein normales .Net Property mit einem Getter und einem Setter aus. Ist es auch.
      Normalerweise haben wir beim einem Property mit einem Getter und Setter ein sogenanntes BackingField welches einen Wert in einer privaten Variable speichert welcher im Getter abgerufen und übergeben wird (Return Anweisung) und im Setter neu gesetzt wird. Das ist hier nicht der Fall. Wie im vorigen Kapitel bereits angesprochen wird der Wert eines Dependency Property immer mit den Methoden GetValue und SetValue aus der Klasse DependencyObject abgerufen bzw. gesetzt.

      Das eigentliche Dependency Property befindet sich als Feld darunter und wird per Konvention immer mit dem Namen des Property + „Property“ benamst wie im obigen Beispiel gut zu sehen ist, und ist vom Typ DependencyProperty. Dieses ist außerdem immer Shared ReadOnly und muss Public sein. Dies ist quasi der bereits erwähnte „Schlüssel“ für den eigentlichen Wert.
      Dieses Feld wird initialisiert indem die Statische Methode Register der Klasse DependencyProperty aufgerufen wird.
      Diese Methode verlangt im ersten Konstruktor zumindest den Namen der .Net Eigenschaft (hier „Text“), den Typ der Eigenschaft (hier vom Typ String) und den Typ des Objekts welches die Eigenschaft besitzt. (Hier ist das die Klasse „TextEllipse“).



      Tipp: Visual Studio erstellt automatisch ein Grundgerüst eines Dependency Property. Hierfür besitzt Visual Studio ein Snippet welche mit dem Keyword „wpfdp“ aufgerufen werden kann.Tippen Sie im VisualBasic Editor „wpfdp“ ein und drücken 2x TAB wird ein Grundgerüst eingefügt in welchem nur noch die Platzhalter ausgefüllt werden müssen. Näheres seht ihr im Video zu diesem Kapitel.



      Es gibt aber weitere Überladungen der Register Methode. Hier die Definitionen:

      VB.NET-Quellcode

      1. Register(name As String, propertyType As Type, ownerType As Type) As DependencyProperty
      2. Register(name As String, propertyType As Type, ownerType As Type, typeMetadata As PropertyMetadata) As DependencyProperty
      3. Register(name As String, propertyType As Type, ownerType As Type, typeMetadata As PropertyMetadata, validateValueCallback As ValidateValueCallback) As DependencyProperty


      Die zweite Überladung nimmt auch sogenannte Property-Metadaten an. Metadaten bestimmen Dinge wie Beispielsweise den Standardwert. Ihnen kann auch ein Delegate für den PropertyChangedCallback übergeben werden. Diese Methode wird dann aufgerufen wenn sich der Wert eines Dependency Property ändert.
      Machen wir dies mal Anhand des Beispiels mit dem Text-Property.

      VB.NET-Quellcode

      1. Public Shared ReadOnly TextProperty As DependencyProperty =
      2. DependencyProperty.Register("Text",
      3. GetType(String), GetType(TextElipse),
      4. New PropertyMetadata("", AddressOf OnTextChanged))
      5. Private Shared Sub OnTextChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      6. End Sub


      Wir übergeben hier an die zweite Überladung von Register eine neue Instanz von PropertyMetadata welche in der vierten Überladung als ersten Parameter einen Default-Value erwartet welchen wir auf einen Leerstring festlegen.
      Als zweiten Parameter erwartet die Überladung einen PropertyChangedCallback.Die Methode welche wir hier angeben wird immer aufgerufen wenn sich der Wert des Property ändert.
      Jetzt fragt sich der ein oder andere warum man denn nicht einfach die Änderung des Wertes im Setter des Property abfängt. Aus dem Grund das dies von „außen“ umgangen werden könnte da man den Wert nicht nur über das Property selbst sondern auch mit der Methode SetValue aus der Klasse DependencyObject überschreiben könnte. Dies würde den Setter „umgehen“.

      Nun Fehlt noch das zweite Dependency Property:

      VB.NET-Quellcode

      1. Public Property Diameter As Double
      2. Get
      3. Return CDbl(GetValue(DiameterProperty))
      4. End Get
      5. Set(ByVal value As Double)
      6. SetValue(DiameterProperty, value)
      7. End Set
      8. End Property
      9. Public Shared ReadOnly DiameterProperty As DependencyProperty =
      10. DependencyProperty.Register("Diameter",
      11. GetType(Double), GetType(TextElipse),
      12. New PropertyMetadata(Double.Parse("40"))


      Um nun das Objekt zu Rendern überschreiben wir die Methode OnRender der Basisklasse:

      VB.NET-Quellcode

      1. Protected Overrides Sub OnRender(drawingContext As DrawingContext)
      2. MyBase.OnRender(drawingContext)
      3. drawingContext.DrawEllipse(New SolidColorBrush(Colors.Black), New Pen(New SolidColorBrush(Colors.Yellow), 2), New Point(Diameter / 2, Diameter / 2), Diameter / 2, Diameter / 2)
      4. Dim formatted = GetFormattedText()
      5. drawingContext.DrawText(formatted, New Point((Diameter - formatted.Width) / 2, (Diameter - formatted.Height) / 2))
      6. End Sub
      7. Public Function GetFormattedText() As FormattedText
      8. Return New FormattedText(Text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, New Typeface("Arial"), 20, New SolidColorBrush(Colors.Green), 1.25)
      9. End Function


      Der Code inkl. der Hilfsmethode GetFormattedText ist im Moment nicht wichtig, wichtig ist das ihr wisst das man OnRender überschreiben muss um selbst ein FrameworkElement Rendern zu können.

      In unserem Projekt binden wir nun unsere TextEllipse in unserem MainWindow.xaml.
      Haben wir das Projekt nun einmahl erfolgreich kompiliert können wir unsere TextEllipse ganz normal im XAML Editor verwenden und haben sogar Intellisense für unsere selbst erstellen DependecyProperties.

      XML-Quellcode

      1. <local:TextElipse x:Name="MyElipse" Grid.Column="0" Text="Hier steht Text" Diameter="150"/>


      Starten wir das Projekt nun sehen wir genau das was wir erwarten würden. Aber auch bereits zur Designzeit haben wir das Ergebnis gesehen.


      Das Ergebnis sieht genauso aus wie erwartet. Fügen wir nun noch einen Slider und eine TextBox hinzu um die Größe und den Text der Ellipse ändern zu können.

      XML-Quellcode

      1. <Grid>
      2. <Grid.ColumnDefinitions>
      3. <ColumnDefinition Width="*"/>
      4. <ColumnDefinition Width="*"/>
      5. </Grid.ColumnDefinitions>
      6. <local:TextElipse x:Name="MyElipse" Grid.Column="0" Text="Hier steht Text" Diameter="150"/>
      7. <StackPanel Grid.Column="1">
      8. <Slider Minimum="1" Maximum="500" Value="{Binding ElementName=MyElipse,Path=Diameter,Mode=TwoWay}"/>
      9. <TextBox Text="{Binding ElementName=MyElipse,Path=Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}/>
      10. </StackPanel>
      11. </Grid>


      Starten wir nun erneut das Projekt werden wir merken das wir in die TextBox schreiben können was wir wollen oder des Slider hin und her bewegen können – es wird sich nichts an unserer Ellipse ändern. Weder verändert sich die Größe noch der Text. Aber warum?
      Das liegt daran das wir unser „Control“ selbst rendern. Wir müssen der WPF nun auch noch bekanntgeben dass sie doch bitte neu Rendern soll wenn wir den Wert eines Property ändern.

      Hierfür übergeben wir statt PropertyMetadata diesmal FrameworkPropertyMetadata als letzten Parameter der RegisterMethode. Die Klasse FrameworkPropertyMetadata besitzt im Gegensatz zu PropertyMetadata die interne Eigenschaft Flags welche in insgesamt vier Überladungen mit übergeben werden kann.

      VB.NET-Quellcode

      1. Public Property Diameter As Double
      2. Get
      3. Return CDbl(GetValue(DiameterProperty))
      4. End Get
      5. Set(ByVal value As Double)
      6. SetValue(DiameterProperty, value)
      7. End Set
      8. End Property
      9. Public Shared ReadOnly DiameterProperty As DependencyProperty =
      10. DependencyProperty.Register("Diameter",
      11. GetType(Double), GetType(TextElipse),
      12. New FrameworkPropertyMetadata(Double.Parse("40"), FrameworkPropertyMetadataOptions.AffectsRender))
      13. Public Property Text As String
      14. Get
      15. Return CType(GetValue(TextProperty), String)
      16. End Get
      17. Set(ByVal value As String)
      18. SetValue(TextProperty, value)
      19. End Set
      20. End Property
      21. Public Shared ReadOnly TextProperty As DependencyProperty =
      22. DependencyProperty.Register("Text",
      23. GetType(String), GetType(TextElipse),
      24. New FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, AddressOf OnTextChanged))


      Wir teilen somit der WPF mit das neu „gerendert“ werden muss wenn sich der Wert dieses Property`s ändert. Die WPF wird in diesem Fall bei jeder Wertänderung die OnRender Methode aufrufen.

      Als kleine „Fleisaufgabe“ werden wir noch sicherstellen das der eingegebene Text immer in die Ellipse passt indem wir die Textgröße berechnen und den Durchmesser der Ellipse in dem Fall das der Text breite sein sollte als der Durchmesser der Ellipse, diese vergrößern.

      VB.NET-Quellcode

      1. Private Shared Sub OnTextChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      2. Dim te As TextElipse = DirectCast(d, TextElipse)
      3. If te.GetFormattedText().Width > Double.Parse(CType(te.GetValue(DiameterProperty), String)) Then
      4. te.SetValue(DiameterProperty, te.GetFormattedText().Width + 5)
      5. End If
      6. End Sub




      Wir haben also nun gelernt das man Dependency Properties immer mit GetValue abfragen und mittels SetValue neu setzten soll. Dependecy Properties können über Ihre Metadaten einiges an Logik mitbekommen und können direkt gebunden werden. Im nächsten Kapitel werden wir ein Dependency Property in ein UserControl einbauen um hier von „außen“ das Verhalten dieses Control steuern zu können.

      Und auch heute gibt es wieder ein Video:


      Hier die Solution: 2_1_5_2.zip
      Und wie immer auch das PDF für euer E-Book: 2.1.5.2.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.5.3 Eigene DependencyProperties in einem UserControl implementieren

      2.1.5.3

      Eigene DependencyProperties in einem UserControl implementieren




      In diesem Kapitel kommen wir dazu dass wir ein Dependency Property in ein UserControl implementieren. Dies ist ein Scenario welches in der Praxis schon um einiges öfter vorkommt.

      In diesem Beispiel gehen wir davon aus das wir ein UserControl haben welches uns Personendaten darstellen soll. Das Control soll aber so flexibel wie möglich sein um es möglichst wiederverwenden zu können.
      Weiters sollen wir die Möglichkeit erhalten das wir die TextBoxen in diesem UserControl auf ReadOnly setzen. Und zwar von außen und via Binding. Klingt jetzt etwas kompliziert aber ihr werdet sehen dass es das gar nicht ist.
      Wir erstellen ein neuen UserControl und nennen dieses ShowOrEditPersonControl und gestalten es mit zwei Labels und zwei TextBoxen.



      Sieht unspektakulär aus, ist es auch. :P

      Zusätzlich, damit es ein wenig klarer wird ob die TextBox nun ReadOnly ist oder nicht habe ich einen Style hinzugefügt welcher bewirkt das der Background einer TextBox Grau wird wenn diese ReadOnly ist.

      XML-Quellcode

      1. <UserControl x:Class="ShowOrEditPersonControl"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      6. xmlns:local="clr-namespace:DependencyProperties_Demo"
      7. mc:Ignorable="d"
      8. d:DesignHeight="72.541" d:DesignWidth="460.656">
      9. <UserControl.Resources>
      10. <Style TargetType="{x:Type TextBox}">
      11. <Setter Property="Background" Value="White"/>
      12. <Setter Property="Margin" Value="3"/>
      13. <Style.Triggers>
      14. <Trigger Property="IsReadOnly" Value="True">
      15. <Setter Property="Background" Value="LightGray"/>
      16. </Trigger>
      17. </Style.Triggers>
      18. </Style>
      19. </UserControl.Resources>
      20. <Grid>
      21. <Grid.RowDefinitions>
      22. <RowDefinition Height="Auto"/>
      23. <RowDefinition Height="Auto"/>
      24. </Grid.RowDefinitions>
      25. <Grid.ColumnDefinitions>
      26. <ColumnDefinition Width="Auto"/>
      27. <ColumnDefinition Width="*"/>
      28. </Grid.ColumnDefinitions>
      29. <Label Content="Vorname"/>
      30. <Label Content="Nachname" Grid.Row="1"/>
      31. <TextBox Text="Sascha" Grid.Column="1"/>
      32. <TextBox Text="Patschka" Grid.Column="1" Grid.Row="1"/>
      33. </Grid>
      34. </UserControl>


      Im Moment haben wir den Vornamen sowie den Nachnamen noch fix gesetzt. Wie möchten aber dass dieser gebunden wird. Außen z.b. im MainWindow können wir dieses UserControl nun problemlos wie jedes andere UserControl im XAML verwenden.

      <local:ShowOrEditPersonControl/>

      Um nun direkt vom MainWindow aus den Text für den Vornamen und den Nachnamen zu setzen, benötigen wie zwei DependencyProperties in der CodeBehind des UserControls. Eines für den Vornamen und eines für den Nachnamen.

      Unsere ShowOrEditPersonControl.vb sieht nun wie folgt aus:

      VB.NET-Quellcode

      1. Public Class ShowOrEditPersonControl
      2. Public Property FirstName As String
      3. Get
      4. Return CType(GetValue(FirstNameProperty), String)
      5. End Get
      6. Set(ByVal value As String)
      7. SetValue(FirstNameProperty, value)
      8. End Set
      9. End Property
      10. Public Shared ReadOnly FirstNameProperty As DependencyProperty =
      11. DependencyProperty.Register("FirstName",
      12. GetType(String), GetType(ShowOrEditPersonControl),
      13. New PropertyMetadata(Nothing))
      14. Public Property LastName As String
      15. Get
      16. Return CType(GetValue(LastNameProperty), String)
      17. End Get
      18. Set(ByVal value As String)
      19. SetValue(LastNameProperty, value)
      20. End Set
      21. End Property
      22. Public Shared ReadOnly LastNameProperty As DependencyProperty =
      23. DependencyProperty.Register("LastName",
      24. GetType(String), GetType(ShowOrEditPersonControl),
      25. New PropertyMetadata(Nothing))
      26. End Class


      Zwei völlig normale aus dem „Default-Gerüst“ bestehende Dependency Property`s.
      Mehr benötigt es auch schon nicht mehr.

      Wir kompilieren das Projekt und sehen uns unser UserControl mal vom MainWindow aus an.
      Wir merken dass wir nun bereits die Properties zur Verfügung haben:



      Die Abbildung zeigt dass wir nun bereits Intellisenseunterstützung haben. Wir können die Werte bereits angeben und könnten nun auch darauf Binden. Setzen wir mal den Vornamen auf „Max“ und den Nachnamen auf „Mustermann“.

      <local:ShowOrEditPersonControl FirstName="Max" LastName="Mustermann"/>

      Aber wie kommen die Daten welche ich hier angebe oder Binde, in die TextBox?
      Berechtigte Frage, denn wer versucht hier Werte anzugeben wird schnell merken das man dies zwar machen kann, es sich aber an der TextBox nichts ändert. Das liegt daran das wir die TextBoxen selbst auch noch Binden müssen.
      Hierfür gibt es mehrere Wege, ich gehe auf einen ein bei welchem ich der Meinung bin das es der „sicherste“ ist.

      Wir ändern also der XAML im ShowOrEditPersonControl wie folgt:

      XML-Quellcode

      1. <TextBox Text="{Binding FirstName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ShowOrEditPersonControl}}}" Grid.Column="1"/>
      2. <TextBox Text="{Binding LastName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ShowOrEditPersonControl}}}"
      3. Grid.Column="1" Grid.Row="1"/>


      Das Binding in diesem Codeblock sucht im ElementTree der WPF nach oben, das nächste Element vom Typ „ShowOrEditPersonControl“. Wird es fündig verwendet es in diesem das Property „LastName bzw. „FirstName“.
      Werfen wir nun (nach einem nochmaligem Buildvorgang) nochmals einen Blick in das MainWindow sehen wir bereits ein Bild welches wir schon eher erwarten würden.



      Toll, das funktioniert und wir können von außen nun Binden und steuern was in den TextBoxen für Inhalt stehen soll.
      Da war ja noch was? Genau, wir wollten das wir steuern können ob sich das UserControl im Editiermodus befindet oder nicht. Fügen wir uns erstmal im MainWindow eine Checkbox ein welche der Editiermodus ein bzw. ausschalten soll.

      XML-Quellcode

      1. <StackPanel>
      2. <CheckBox x:Name="chkEditMode" Content="Im Editiermodus?" Margin="10"/>
      3. <local:ShowOrEditPersonControl FirstName="Max" LastName="Mustermann"/>
      4. </StackPanel>


      Nun benötigen wir ein weiteres Dependency Property innerhalb unseres UserControls:


      VB.NET-Quellcode

      1. Public Property IsInEditMode As Boolean
      2. Get
      3. Return CBool(GetValue(IsInEditModeProperty))
      4. End Get
      5. Set(ByVal value As Boolean)
      6. SetValue(IsInEditModeProperty, value)
      7. End Set
      8. End Property
      9. Public Shared ReadOnly IsInEditModeProperty As DependencyProperty =
      10. DependencyProperty.Register("IsInEditMode",
      11. GetType(Boolean), GetType(ShowOrEditPersonControl),
      12. New PropertyMetadata(False))


      Diesem Property übergeben wir über die Metadaten False als Default-Wert.
      Die Bindung auf die TextBoxen kann nun genauso wie beim Text-Property erfolgen oder – da wir einen Style definiert haben gleich in diesem damit wir uns nicht wiederholen müssen.

      XML-Quellcode

      1. <Style TargetType="{x:Type TextBox}">
      2. <Setter Property="Background" Value="White"/>
      3. <Setter Property="Margin" Value="3"/>
      4. <Setter Property="IsReadOnly" Value="{Binding IsInEditMode, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ShowOrEditPersonControl}}}"/>
      5. <Style.Triggers>
      6. <Trigger Property="IsReadOnly" Value="True">
      7. <Setter Property="Background" Value="LightGray"/>
      8. </Trigger>
      9. </Style.Triggers>
      10. </Style>


      Wenn wir dies nun ausprobieren indem wir im MainWindow wieder das Property setzen werden wir sehen dass sich unser UserControl nicht ganz so verhält wie wir das wollen. Nämlich genau anders rum.



      Wir haben im Kapitel 2.1.1.2 einiges über Trigger gelöst und könnten hiermit das verhalten umkehren. Die schönere Lösung wäre allerdings ein Converter wie in Kapitel 2.1.4.4 gelernt.
      Schon aus dem Grund da dieser wiederverwendbar ist, wobei ich Trigger immer wieder neu runter tippen muss.

      Wir legen uns also einen Converter an und nennen die Klasse BooleanReverseConverter:

      VB.NET-Quellcode

      1. Public Class BooleanReverseConverter
      2. Implements IValueConverter
      3. Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
      4. Return Not Boolean.Parse(value.ToString())
      5. End Function
      6. Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      7. Return Not Boolean.Parse(value.ToString())
      8. End Function
      9. End Class


      Nun müssen wir diesen Converter im unseren XAML einbinden. Am einfachsten geht dies über das Eigenschaftenfenster, aber auch direkt im Code indem wir den Converter als Resource hinzufügen und einen Key vergeben. Diesen Key verwenden wir anschließend im Binding:

      XML-Quellcode

      1. <UserControl.Resources>
      2. <local:BooleanReverseConverter x:Key="BooleanReverseConverter"/>
      3. <Style TargetType="{x:Type TextBox}">
      4. <Setter Property="Background" Value="White"/>
      5. <Setter Property="Margin" Value="3"/>
      6. <Setter Property="IsReadOnly" Value="{Binding IsInEditMode,
      7. Converter={StaticResource BooleanReverseConverter},
      8. RelativeSource={RelativeSource FindAncestor,
      9. AncestorType={x:Type local:ShowOrEditPersonControl}}}"/>
      10. <Style.Triggers>
      11. <Trigger Property="IsReadOnly" Value="True">
      12. <Setter Property="Background" Value="LightGray"/>
      13. </Trigger>
      14. </Style.Triggers>
      15. </Style>
      16. </UserControl.Resources>


      Nach einem Buildvorgang zurück im MainWindow sehen wir das nun alles so funktioniert wie erwünscht. Nun können wir noch die Checkbox auf unser DependencyProperty binden:

      XML-Quellcode

      1. <StackPanel>
      2. <CheckBox x:Name="chkEditMode" Content="Im Editiermodus?" Margin="10"/>
      3. <local:ShowOrEditPersonControl IsInEditMode="{Binding ElementName=chkEditMode,Path=IsChecked}" FirstName="Max" LastName="Mustermann"/>
      4. </StackPanel>


      Hier nun zwei Abbildungen die zeigen wie unser Binding zur Laufzeit funktioniert:


      Fazit: Dependency Properties sind eine feine Sache wenn man diese Versteht und weis was im Hintergrund abläuft.
      Auf Dependency Properties kann gebunden werden, können einen Standardwert haben und eine gewisse Logik insich implementiert haben.



      Und auch heute gibt es wieder ein Video:


      Hier die Solution: 2.1.5.3 - DependencyProperties-Demo.zip
      Und wie immer auch das PDF für euer E-Book: 2.1.5.3.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.6

      Markuperweiterungen (Markup Extensions)




      Markup Extensions nutzen wir jeden Tag in der WPF.
      Bekannte Markup Extensions sind Beispielsweise {StaticResource}, {TemplateBinding} oder {Binding}.
      Wir verwenden diese ständig und sind ein fester Bestandteil der WPF, aber wie sieht es aus mit eigenen Extensions? Können wir eigene Extensions schreiben und was für einen Mehrwert bringen uns diese Extensions?

      Dies will ich in diesem Kapitel erläutern.


      2.1.6.1

      Markuperweiterungen - Theorie




      Ich habe ja bereits einige Markup Extensions erwähnt. Aber es gibt noch einige weitere im XAML Namespace.

      x:Name, x:Static, x:Null sind einige welche wir immer wieder verwenden. Ich will auch nicht zu sehr ins Detail der Theorie gehen da dieses Thema nicht allzu oft Verwendung finden wird.
      Ich finde allerdings das die Markup Extension eine nette Möglichkeit ist wieder etwas Code, welcher immer und immer wieder benötigt wird auszulagern.

      Eigene Markup Extension können wir implementieren indem wir entweder von der Basisklasse MarkupExtension erben, oder das Interface IMarkupExtension in eine Klasse implementieren.
      Die Basisklasse MarkupExtension besitzt eine Methode ProvideValue(System.IServiceProvider) As Object) welche als MustOverride deklariert ist. Diese Methode gilt es zu überschreiben und muss ein Ergebnis zurückliefern.

      Im nächsten Kapitel werden wir Beispiele erstellen und ich werde ein wenig auf besonderheiten eingehen.

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.6.2 Markuperweiterungen - Beispiel

      2.1.6.2

      Markuperweiterungen - Beispiel




      Als ersten möchten wir anhand eines einfachen Beispiels einfach nur ein „Hallo Welt“ zurückgeben, nur damit wir sehen wie die Implementierung einer Markup Extension von statten geht und was dabei evtl. zu beachten ist.
      Unsere Markup Extension soll den Namen HelloWorld bekommen. Die Namenskonvention erwartet von uns dass die Klasse somit den Namen „HelloWorldExtension“ bekommt. Diese Namenskonvention ist keine Pflicht, wird aber empfohlen. Wir legen also eine neue Klasse mit diesem Namen an und erben von MarkupExtension. Tun wir dies generiert uns Visual Studio automatisch die Methode ProvideValue da diese in der Basisklasse ja mit MustOverride deklariert ist. Außerdem empfehle ich immer solche Extensions genauso wie z.b. Konverter in einen eigenen Namespace zu packen:

      VB.NET-Quellcode

      1. Namespace MyWpfExtensions
      2. Public Class HelloWorldExtension
      3. Inherits Markup.MarkupExtension
      4. Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
      5. Throw New NotImplementedException()
      6. End Function
      7. End Class
      8. End Namespace


      Dies ist also ein Grundkonstrukt einer Markup Extension.
      In unserem Beispiel möchten wir einfach das unsere Extension schlicht ein „Hallo Welt“ zurückgibt. Also machen wir dies:

      VB.NET-Quellcode

      1. Namespace MyWpfExtensions
      2. Public Class HelloWorldExtension
      3. Inherits Markup.MarkupExtension
      4. Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
      5. Return "Hallo Welt"
      6. End Function
      7. End Class
      8. End Namespace


      Jetzt geht es darum diese Extension in unseren XAML einzubinden.
      Hierfür muss das Projekt erstmal kompiliert werden damit auch der Designer etwas von unserer neuen Klasse mitbekommt. Anschließend können wir erstmal den Namespace importieren:

      XML-Quellcode

      1. xmlns:ext="clr-namespace:_2_1_6_1_MarkupExtensions.MyWpfExtensions"


      Info: Mein Projekt hat den Namen und somit den Stammnamespace „2_1_6_1_MarkupExtensions“. Dem folgt einfach unser Namespace für die Extensions.

      Nun können wir unsere Extension auch schon in unserem XAML nutzen:



      OK, zugegeben. So etwas hat nun im Moment wenig nutzen.Wie sieht es mit Parametern aus? Ja, machen wir ein Beispiel welches einfach zwei Ziffern Multiplizieren soll.
      Wieder legen wir eine neue Klasse an welche von MarkupExtension erbt und nun aber zusätzlich zwei Eigenschaften besitzen soll. Hierfür implementieren wir neben dem paramterlosem Konstruktor (welcher Pflicht ist) einen weiteren welcher zwei Ziffern entgegennehmen kann:

      VB.NET-Quellcode

      1. Imports System.Windows.Markup
      2. Namespace MyWpfExtensions
      3. Public Class MultiplyExtension
      4. Inherits MarkupExtension
      5. Public Sub New()
      6. End Sub
      7. Public Sub New(value1 As String, value2 As String)
      8. Me.Value1 = value1 : Me.Value2 = value2
      9. End Sub
      10. <ConstructorArgument("value1")>
      11. Public Property Value1 As Object
      12. <ConstructorArgument("value2")>
      13. Public Property Value2 As Object
      14. Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
      15. Throw New NotImplementedException()
      16. End Function
      17. End Class
      18. End Namespace


      Die Annotation der Eigenschaften ist übrigens nicht Pflicht wird aber empfohlen.
      Nun haben wir einen zweiten Konstruktor um müssen nun die Werte nur noch Multiplizieren:

      VB.NET-Quellcode

      1. Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
      2. If Value1 Is Nothing Then Value1 = 0
      3. If Value2 Is Nothing Then Value2 = 0
      4. Return Double.Parse(Value1.ToString) * Double.Parse(Value2.ToString)
      5. End Function


      Wenn wir nun unsere Extension in unserer TextBox anwenden sehen wir auch dass diese nun korrekt zwei Ziffern addieren kann.



      Bis jetzt sind wir aber noch nicht Typsicher unterwegs, das soll sich nun aber ändern.
      Die zu überschreibende Methode ProvideValue gibt uns ein IServiceProvider mit auf dem Weg.
      Über diesen Serviceprovider kommen wir an die Information an welches Property das Ergebnis geht und somit auch welchen Datentyp dieses Property besitzt. Somit können wir auch versuchen eine Konvertierung durchzuführen.
      Erstellen wir uns eine Klasse mit dem Namen MultiplyTypesaveExtension.
      Unsere Konstruktoren und die Eigenschaften Value1 und Value2 ändern sich insofern das diese nun nur noch Double als Parameter erwarten. Die zu überschreibende Methode ProvideValue ändern wir wie folgt ab:

      VB.NET-Quellcode

      1. Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
      2. If serviceProvider Is Nothing Then Return Nothing
      3. Dim valueTarget = DirectCast(serviceProvider.GetService(GetType(IProvideValueTarget)), IProvideValueTarget)
      4. Dim calcResult = Value1 * Value2
      5. If valueTarget.TargetProperty.GetType Is GetType(DependencyProperty) Then
      6. Dim dp = DirectCast(valueTarget.TargetProperty, DependencyProperty)
      7. Return Convert.ChangeType(calcResult, dp.PropertyType)
      8. Else
      9. Dim info = DirectCast(valueTarget.TargetProperty, PropertyInfo)
      10. Return Convert.ChangeType(calcResult, info.PropertyType)
      11. End If
      12. End Function


      Im ersten Schritt holen wir uns das IProvideValueTarget-Service. Dieses gibt uns Informationen über die Eigenschaft zurück welche wir benötigen. Nachdem wir das Ergebnis wie bereits gehabt errechnen müssen wir prüfen ob es sich bei der Eigenschaft um ein Dependency Property oder ein normales Property handelt. Den Unterschied haben wir ja bereits in Kapitel 2.1.5 gelernt.
      Bewaffnet mit unseren Informationen können wir nun in den richtigen Typ Konvertieren.
      Zu guter letzt bringen wir auf Höhe der Klasse selbst noch eine Annotation an. Diese in nicht Plicht aber ich empfehle diese, warum zeige ich gleich noch.

      VB.NET-Quellcode

      1. <MarkupExtensionReturnType(GetType(Object))>
      2. Public Class MultiplyTypesaveExtension
      3. ...
      4. ...


      Ist er nun komplett verrückt? Da arbeiten wir nun Typsicher und dann eine Annotation mit Object als Datentyp? OK, das muss ich erklären.
      Diese Annotation gibt dem Designer der WPF die Möglichkeit zu wissen für was für einen Datentyp diese Extension gültig sein soll.
      Machen wir ein Beispiel. Gebe ich in der Annotation als Datentyp Double an kann ich in einem TextBox.Text Property zwar die Extension nutzen, habe aber keine Intellisens.



      Dafür aber Beispielsweise bei der Eigenschaft Opacity der TextBox, da diese von Typ Double ist.



      Gebe ich in der Annotation aber Object an, habe ich die Intellisens überall zur Verfügung. Da wir die Konvertierung im Code der Extension korrekt durchführen ist dies auch kein Problem.

      Ich hoffe das Kapitel hat euch nun einmal mehr von der Flexibilität der WPF überzeugt, ich bin mir sicher das bei euch im Kopf gerade ein paar Ideen für den Einsatz von MarkupExtensions herumschwirren. ;)
      Ein Typisches Beispiel wäre die Lokalisierung (Übersetzung in die aktuelle Sprache des Benutzers).


      Und auch heute gibt es wieder ein Video:


      Hier die Solution: 2_1_6_1_MarkupExtensions.zip
      Und wie immer auch das PDF für euer E-Book: 2.1.6.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.7 - Attached Properties

      2.1.7

      Attached Properties




      Jetzt wo wir über Dependency Properties Bescheid wissen kommen wir zu den Attached Properties. Diese sind eine andere Art von Dependency Properties. Wir benutzen Attached Properties im Grunde täglich während der Arbeit unter WPF. Viele Panels haben Attached Properties wie z.b. das DockPanel. Auch das Grid besitzt Attached Properties.
      Haben wir ein Grid in welchen sich Steuerelemente befinden können wir bestimmen wo diese Steuerelemente befinden sollen (Spalte oder Zeile) indem wir Attached Properties nutzen.

      XML-Quellcode

      1. <Grid>
      2. <Grid.RowDefinitions>
      3. <RowDefinition Height="1*"/>
      4. <RowDefinition Height="3*"/>
      5. </Grid.RowDefinitions>
      6. <Grid.ColumnDefinitions>
      7. <ColumnDefinition Width="*"/>
      8. <ColumnDefinition Width="*"/>
      9. </Grid.ColumnDefinitions>
      10. <Button Content="Test" Grid.Column="1" Grid.Row="0"/>
      11. </Grid>


      Grid.Column ist ein Attached Property welches für Dependency Object implementiert wurde. Das bedeutet das jedes Steuerelement der WPF (wir haben ja gelernt das in der WPF jedes Steuerelement indirekt von DependencyObject ableitet) dieses Attached Property nutzen kann.
      So können wir auf einfache Art und Weise bestimmen wo genau der Button reingepackt wird.

      Info: Der oben stehenden XAML würde wie folgt in Code aussehen.


      VB.NET-Quellcode

      1. Dim btn = TestButton
      2. btn.SetValue(Grid.ColumnProperty, 1)
      3. btn.SetValue(Grid.RowProperty, 0)
      4. 'oder
      5. Grid.SetColumn(btn, 1)
      6. Grid.SetRow(btn, 0)


      Als nächsten gehen wir daran eigene Attached Properties zu implementieren.

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.7.2 - Eigene Attached Properties

      2.1.7.2

      Eigene Attached Properties




      Kommen wir zur Praxis
      So würde ein Attached Property aussehen:

      VB.NET-Quellcode

      1. Public Class MyFirstAttachedProperty
      2. Inherits DependencyObject
      3. Public Shared Function GetMyFirstProp(ByVal element As DependencyObject) As String
      4. If element Is Nothing Then
      5. Throw New ArgumentNullException("element")
      6. End If
      7. Return CType(element.GetValue(MyFirstPropProperty), String)
      8. End Function
      9. Public Shared Sub SetMyFirstProp(ByVal element As DependencyObject, ByVal value As String)
      10. If element Is Nothing Then
      11. Throw New ArgumentNullException("element")
      12. End If
      13. element.SetValue(MyFirstPropProperty, value)
      14. End Sub
      15. Public Shared ReadOnly MyFirstPropProperty As _
      16. DependencyProperty = DependencyProperty.RegisterAttached("MyFirstProp",
      17. GetType(String), GetType(MyFirstAttachedProperty),
      18. New PropertyMetadata(Nothing))
      19. End Class


      Über die Signatur der Methoden Get und Set kann bestimmt werden für welche Controls ein Attached Property gültig sein soll. In diesem Fall wird bei beiden Methoden ein Element vom Typ Dependency Object erwartet, was bedeutet das dieses Attached Property auf jedes Objekt welches vom Typ DependencyObject ableitet angewandt werden kann. Dieses Argument kann auch angepasst werden um dies Beispielsweise nur für einen Button zu erlauben.
      Erstellen wir mal ein einfaches Beispiel welches auf alle Controls angewandt werden kann:

      Wir wollen zum Testen mal ein Attached Property erstellen welches eine Drehung unterstützt.
      Dies ist in der WPF zwar mit RenderTransform über XAML für jedes ObjeKt verfügbar aber es benötigt einige Zeilen Code.
      Um mit Attached Properties warm zu werden möchten wir dies mal einfacher gestalten. Nachstehend der XAML mit welchem man ein Element normalerweise drehen würde:

      XML-Quellcode

      1. <Button Content="Test" RenderTransformOrigin="0.5,0.5">
      2. <Button.RenderTransform>
      3. <TransformGroup>
      4. <RotateTransform Angle="11"/>
      5. </TransformGroup>
      6. </Button.RenderTransform>
      7. </Button>


      Dieser XAML Rendert einen Button um 11 Grad verdreht wobei der Mittelpunkt der Drehung sowohl horizontal als auch vertikal in der Mitte des Control ist.
      Um dies in Zukunft einfacher zu gestalten werden wir ein Attached Property hierfür erstellen welches wir Angle nennen werden.
      Wir erstellen uns also mit Hilfe des CodeSnippets wpfdpa ein Attached Property welches den Namen Angle besitzt und den Wertetyp Double besitzt.
      Die Klasse habe ich in diesem Fall MyRenderTransformHelper benannt.

      VB.NET-Quellcode

      1. Public Class MyRenderTransformHelper
      2. Inherits DependencyObject
      3. Public Shared Function GetAngle(ByVal element As DependencyObject) As Double
      4. If element Is Nothing Then
      5. Throw New ArgumentNullException("element")
      6. End If
      7. Return CDbl(element.GetValue(AngleProperty))
      8. End Function
      9. Public Shared Sub SetAngle(ByVal element As DependencyObject, ByVal value As Double)
      10. If element Is Nothing Then
      11. Throw New ArgumentNullException("element")
      12. End If
      13. element.SetValue(AngleProperty, value)
      14. End Sub
      15. Public Shared ReadOnly AngleProperty As _
      16. DependencyProperty = DependencyProperty.RegisterAttached("Angle",
      17. GetType(Double), GetType(MyRenderTransformHelper),
      18. New PropertyMetadata(Double.Parse("0"))
      19. End Class


      Kompilieren wir nun unser Projekt werden wir sehen das unser neues Property bereits für jedes Control zur Verfügung steht.



      Aber noch haben wir keine Logik implementiert welche sicherstellen würde dass der Button auch wirklich gedreht wird. Bei den Dependency Properties haben wir bereits gelernt das wir auf Änderungen eines Property reagieren können indem wir einen Callback für das PropertyChangedCallback in den Metadaten des Properties setzen.

      VB.NET-Quellcode

      1. Public Shared ReadOnly AngleProperty As _
      2. DependencyProperty = DependencyProperty.RegisterAttached("Angle",
      3. GetType(Double), GetType(MyRenderTransformHelper),
      4. New PropertyMetadata(Double.Parse("0"), AddressOf AngleProperty_Changed))
      5. Private Shared Sub AngleProperty_Changed(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      6. Dim elem As UIElement = TryCast(d, UIElement)
      7. If elem IsNot Nothing Then
      8. elem.RenderTransformOrigin = New Point(0.5, 0.5)
      9. elem.RenderTransform = New RotateTransform(CDbl(e.NewValue))
      10. End If
      11. End Sub


      Zurück im Designer – sobald wir kompiliert haben – sehen wir den nun gedrehten Button mit folgendem um einiges kürzeren Code:

      XML-Quellcode

      1. <Button VerticalAlignment="Center"
      2. HorizontalAlignment="Center"
      3. Content="TestButton"
      4. local:MyRenderTransformHelper.Angle="15">
      5. </Button>




      Im nächsten Beispiel möchte ich zum einen zeigen wie wir ein Attached Property nur auf einen gewissen Control-Typ zulassen und zum anderen wie wir von einem Control aus ein anderes Control steuern. Ähnlich der Funktionalität des Grids.

      Situation: Wir möchten von einem UserControl aus den Titel des Fensters in welchem sich dieses befindet setzen. Dies ist oft in MVVM Anwendungen von nöten.
      Ich werde die Klasse WindowHelper nennen und nur auf UserControls zulassen.

      VB.NET-Quellcode

      1. Public Class WindowHelper
      2. Inherits DependencyObject
      3. Public Shared Function GetWindowTitle(obj As UserControl) As String
      4. Return DirectCast(obj.GetValue(WindowTitleProperty), String)
      5. End Function
      6. Public Shared Sub SetWindowTitle(obj As UserControl, value As String)
      7. obj.SetValue(WindowTitleProperty, value)
      8. End Sub
      9. Public Shared ReadOnly WindowTitleProperty As DependencyProperty =
      10. DependencyProperty.RegisterAttached("WindowTitle",
      11. GetType(String),
      12. GetType(WindowHelper),
      13. New UIPropertyMetadata("", AddressOf WindowTitle_Changed))
      14. Private Shared Sub WindowTitle_Changed(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      15. End Class


      Nun müssen wir nur noch die Methode WindowTitle_Changed mit Leben füllen.
      Im Argument „d“ bekommen wir das Objekt herein welches das Property verwendet. In unserem Fall kann/darf dies nur ein UserControl sein da wir nur dieses zulassen was wir schön an den Argumenten der Methoden SetWindowTitle und GetWindowTitle sehen können. Hier haben wir die Möglichkeit ausschließlich auf UserControls beschränkt.
      Das Argument „e“ stellt und unter anderem die Eigenschaft NewValue zur Verfügung, das kennen wir aber bereits aus dem Kapitel DependencyProperties.

      Nach ein wenig überlegen kommen wir drauf das wir nach laden des UserControl den Fenstertitel überschreiben müssen. Folgender Code steht anschließend in der Methode:

      VB.NET-Quellcode

      1. Private Shared Sub WindowTitle_Changed(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      2. Dim ctl = TryCast(d, UserControl)
      3. AddHandler ctl.Loaded, Sub(sender, e2)
      4. If Window.GetWindow(ctl) IsNot Nothing Then
      5. DirectCast(Window.GetWindow(ctl), Window).Title = GetWindowTitle(ctl)
      6. End If
      7. End Sub
      8. End Sub


      Nun können wir ein UserControl erstellen und unser neues Attached Property probieren.
      Erstellen wir ein UserControl mit dem Namen TestUserControl.
      In dieses setzen wir zum Testen einfach nur ein Label mit ein wenig Text und setzen unser soeben erstelltes Attached Property.
      Im MainWindow unseres Programms werden wir dieses UserControl mal einbinden und das Projekt starten…



      Wir merken schon dass sich durch Attached Properties einige neue Möglichkeiten ergeben.
      Was mir persönlich gut an Attached Properties gefällt ist das man nicht unbedingt für jede kleine „Änderung“ an einem Control immer gleich ein neues Control erstellen muss sondern einfach ein wenig Logik „einpflanzt“. Ohne das Control selbst zu verändern. Der Vorteil dabei, ich kann die Klassen mit meinen Attached Properties in eine Klassenbibliothek legen und jederzeit in jedem Projekt einbinden und habe diese Funktionalitäten zur Verfügung.
      Ich hoffe das hat euch gefallen. Nachstehend wieder ein Video zu diesem Kapitel, hinterlasst mir doch ein Kommentar was euch gefallen hat oder vielleicht auch was euch nicht gefallen hat.



      Hier die Solution wieder als Download: 2.1.7.2_AttachedProperties.zip
      Hier das PDF für Leute mit EBook:
      2.1.7.2.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.8.1 Die Input API

      Neu

      2.1.8.1

      Die Input API




      Die Primäre API für die Eingabe ist in den Basiselementen UIElement, ContentElement, FrameworkElement und FrameworkContentElement enthalten.
      Einige davon haben wir ja bereits ausführlicher kennengelernt.

      Alle diese Klassen bieten Funktionen für Eingabeereignisse die im Zusammenhang mit Tastatureingaben, Maustasten, dem Mausrad, sowie der Mausbewegung stehen.
      Dieses System unterscheidet sich von dem System wie es in WinForms implementiert ist und ermöglicht eine andere Eingabearchitektur so das ein eigenes EreignisRouting-Schema möglich wird, bei dem mehr als ein Element ein Ereignis behandeln kann. In der WPF ist sehr vielen Ereignissen ein Ereignispaar zugeordnet wie Beispielsweise das KeyDown-Ereignis und das PreviewKeyDown-Ereignis. Der Unterschied zwischen diesen Ereignissen liegt darin wie an das Zielelement weitergeleitet wird.

      Vorschauereignisse (mit dem Präfix Preview) „Tunneln“ die Elementstruktur vom Stammelement nach unten wobei „Bubbling“-Ereignisse (ohne dem Präfix Preview) vom Zielelement an das Stammelement übergeben. Der Unterschied zwischen Bubbling und Tunneling wird aber in Kapitel 2.1.8.3 noch ausführlicher erläutert. Es ist auch wichtig dieses System zu verstehen da es hier gerne mal zu Verwirrung kommen kann wenn einem der Unterschied nicht klar ist. Aber gehen wir vorher mal zu Tastatur und Mauseingaben…

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.8.2 - Tastatur und Mausklassen

      Neu

      2.1.8.2

      Tastatur und Mausklassen




      Zusätzlich zur Input API in den Basiselement-Klassen stellen die Keyboard-Klasse und die Mouse-Klasse weitere APIs für die Arbeit mit der Tastatur und Maus zur Verfügung.

      Tastatur

      VB.NET-Quellcode

      1. If (Keyboard.GetKeyStates(Key.Return) And KeyStates.Down) > 0 Then
      2. Debug.WriteLine("Return is pressed!")
      3. End If


      Maus

      Dasselbe gilt für die Mouse-Klasse. Im folgenden Beispiel wird ermittelt ob die linke Maustaste gedrückt wurde.

      VB.NET-Quellcode

      1. If Mouse.LeftButton = MouseButtonState.Pressed Then
      2. Debug.WriteLine("Left Mousebutton pressed!")
      3. End If


      Stifteingabe

      Die WPF verfügt über integrierte Unterstützung für Stylus-Eingaben.

      Stylus sind Stifte zur Eingabe an einem Tablet, gerade in letzter Zeit und nicht zuletzt durch die Surface Produkte von Microsoft erfreut sich diese Art der Eingabe einer neuen Beliebtheit. WPF Anwendungen können den Stift mithilfe der Mause-API als Maus behandeln aber auch als Stift-Gesten. Hierfür verfügt die WPF über Stylus-Api. All Stiftbezogenen APIs enthalten „Stylus“ in ihrem Namen.

      Wie Beispielsweise folgende Events: PreviewStylusButtonDown, StylusButtonDown, PreviewStylusInAirMove, StylusInAirMove.
      Wie man gut erkennen kann gibt es auch hier die „Paare“ mit und ohne dem Präfix „Preview“.
      Da der Stift aber auch als Maus fungieren kann, können bestehende Anwendungen welche nur die Mauseingabe unterstützen trotzdem bis zu einem gewissen Teil bedient werden, bestimmte einem Stift vorbehaltene Features können in diesem Fall allerdings nicht genutzt werden.

      Hier das PDF für Leute mit EBook:
      2.1.8.2.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.8.3 - Eventrouting

      Neu

      2.1.8.3

      Eventrouting




      Die ist meiner Meinung nach ein recht wichtiges Thema in der WPF um das Eventhandlich der WPF verstehen zu lernen.

      Ich versuche es möglichst nicht zu Theoretisch zu erklären.
      In der WPF gibt es einen ElementTree. In diesem ElementTree sind die Elemente (Control) enthalten. Für Ereignisse aller Control innerhalb eines Elementrees gibt es mehrere Routingmechanissmen welche in den Metadaten des Events hinterlegt sind.
      • Direct
      • Bubbling
      • Tunneling
      In der WPF kann ein Ereignis nicht nur von einem einzelnen Element „bearbeitet“ werden.
      Ich weis nicht ob „bearbeitet“ das richtige Wort ist, aber entscheide selbst.

      Das Direkte Routing-Ereignis (Direct) wird direkt auf diesem Element ausgelöst und auch nur auf diesem. Wie man dies aus WinForms kennt.Bubbling arbeitet sich die Elementstruktur nach oben. Es Blubbert also nach ob die Elemente durch, indem zuerst das Element benachrichtigt wird, von dem das Ereignis stammt, und dann das übergeordnete Element benachrichtigt, dann wieder eines höher usw.
      Tunneling beginnt am Stamm der Elementstruktur (z.b. UserControl) und Arbeitet sich Element für Element nach unten bis es bei dem Element angelangt ist welches das Ereignis ursprünglich ausgelöst hatte.

      Folgende Abbildung zeigt welche Ereignisse auf welchem Objekt ausgelöste werden wenn auf einen Button geklickt wird welches sich innerhalb eines Stackpanels befindet:



      Per Konvention sind Events welche mit dem Präfix „Preview“ versehen sind Events welche das Tunneling als Routingsstrategie verwenden während Events ohne diesem Präfix immer ein Bubbling als Strategie verwenden.
      Wenn also wie in der Abbildung zu sehen ist auf den Button geklickt wird zuerst am Button das Click-Event geworfen. Anschließend wird auf dem StackPanel das Click-Event geworfen und danach am Grid.
      Schreiben wir eine kleine Demoanwendung welche dies besser veranschaulicht. Ihr werdet überrascht sein.
      Wir erstellen eine neue WPF Anwendung und ersetzen im MainWindow den Code des per Default erstellten Grids gegen folgenden:

      XML-Quellcode

      1. <Window x:Class="MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      6. xmlns:local="clr-namespace:BubblingTunnelingDemo"
      7. mc:Ignorable="d"
      8. Title="MainWindow" Height="260" Width="300">
      9. <Grid x:Name="MainGrid" Margin="10" Background="LightYellow"
      10. PreviewMouseDown="PreviewMouseDown"
      11. PreviewMouseUp="PreviewMouseUp"
      12. MouseDown="MouseDown"
      13. MouseUp="MouseUp">
      14. <StackPanel x:Name="MainStackPanel" Margin="10" Background="LightBlue"
      15. PreviewMouseDown="PreviewMouseDown"
      16. PreviewMouseUp="PreviewMouseUp"
      17. MouseDown="MouseDown"
      18. MouseUp="MouseUp">
      19. <Button x:Name="Button1" Content="Button 1" Margin="10"
      20. Click="ButtonClick"
      21. PreviewMouseDown="PreviewMouseDown"
      22. PreviewMouseUp="PreviewMouseUp"/>
      23. <Button x:Name="Button2" Content="Button 2" Margin="10"
      24. Click="ButtonClick"
      25. PreviewMouseDown="PreviewMouseDown"
      26. PreviewMouseUp="PreviewMouseUp"/>
      27. <Button x:Name="Button3" Content="Button 3" Margin="10"
      28. Click="ButtonClick"
      29. PreviewMouseDown="PreviewMouseDown"
      30. PreviewMouseUp="PreviewMouseUp"/>
      31. </StackPanel>
      32. </Grid>
      33. </Window>


      Vielleicht legen wir noch die Größe des Fensters auf: Height="260" Width="300"



      und fügen folgenden Code in die MainWindow.vb – CodeBehind Datei ein:

      VB.NET-Quellcode

      1. Private Sub ButtonClick(sender As Object, e As RoutedEventArgs)
      2. Debug.WriteLine($"{GetMethodName()} from Element with Name: '{DirectCast(sender, FrameworkElement).Name}'")
      3. End Sub
      4. Private Shadows Sub PreviewMouseDown(sender As Object, e As MouseButtonEventArgs)
      5. Debug.WriteLine($"{GetMethodName()} from Element with Name: '{DirectCast(sender, FrameworkElement).Name}'")
      6. End Sub
      7. Private Shadows Sub PreviewMouseUp(sender As Object, e As MouseButtonEventArgs)
      8. Debug.WriteLine($"{GetMethodName()} from Element with Name: '{DirectCast(sender, FrameworkElement).Name}'")
      9. End Sub
      10. Private Shadows Sub MouseDown(sender As Object, e As MouseButtonEventArgs)
      11. Debug.WriteLine($"{GetMethodName()} from Element with Name: '{DirectCast(sender, FrameworkElement).Name}'")
      12. End Sub
      13. Private Shadows Sub MouseUp(sender As Object, e As MouseButtonEventArgs)
      14. Debug.WriteLine($"{GetMethodName()} from Element with Name: '{DirectCast(sender, FrameworkElement).Name}'")
      15. End Sub
      16. Private Function GetMethodName(<Runtime.CompilerServices.CallerMemberName>
      17. Optional memberName As String = Nothing) As String
      18. Return memberName
      19. End Function


      Starten wir die Applikation und sehen wir uns das ganze mal genauer an und klicken auf den Button 1.
      Hierdurch wird folgender Text in der Ausgabe generiert:

      Quellcode

      1. PreviewMouseDown from Element with Name: 'MainGrid'
      2. PreviewMouseDown from Element with Name: 'MainStackPanel'
      3. PreviewMouseDown from Element with Name: 'Button1'
      4. PreviewMouseUp from Element with Name: 'MainGrid'
      5. PreviewMouseUp from Element with Name: 'MainStackPanel'
      6. PreviewMouseUp from Element with Name: 'Button1'
      7. ButtonClick from Element with Name: 'Button1'


      Wir sehen schön, dass hier auf jedem Element an welchem wir das Event mit dem Preview-Präfix abonniert haben das Tunneling greift und die Events von oben nach unten geworfen werden.
      Erst nachdem auch für den Button das PreviewMouseUp geworfen wurde wird das Click-Event für den Button geworfen. PreviewMouseDown und PreviewMouseUp Tunneln von Oben nach unten zum Zielelement.


      Sehen wir uns nun an was passiert wenn wir einen Klick auf das StackPanel machen:

      Quellcode

      1. PreviewMouseDown from Element with Name: 'MainGrid'
      2. PreviewMouseDown from Element with Name: 'MainStackPanel'
      3. MouseDown from Element with Name: 'MainStackPanel'
      4. MouseDown from Element with Name: 'MainGrid'
      5. PreviewMouseUp from Element with Name: 'MainGrid'
      6. PreviewMouseUp from Element with Name: 'MainStackPanel'
      7. MouseUp from Element with Name: 'MainStackPanel'
      8. MouseUp from Element with Name: 'MainGrid'


      Nun sehen wir schön das die Bubbling-Events MouseUp und MouseDown in die Gegenrichtung ausgelöst werden. Nämlich zuerst am StackPanel und erst dann am Grid.
      Um nun zu verhindern das Events weiter nach oben Bubbeln oder nach unten Tunneln kann das Handled Property des EventArgs auf True gesetzt werden. In diesem Fall gilt das Ereignis als behandelt und geht nicht mehr zum nächsten Element weiter.


      Hier das PDF für Leute mit EBook-Reader: 2.1.8.3.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.8.4 - Touch und Multitouch

      Neu

      2.1.8.4

      Touch und Multitouch




      Auch ein immer wichtiger werdendes Thema heute. Touch und Multitouch-Bildschirme werden immer günstiger und in vielen Geräten befinden sich bereits solche Bildschirme. Auch in der Industrie finden die Bildschirme immer mehr Beliebtheit da auch diese immer Robuster werden und die Bedienung schlichtweg intuitiver ist als mit einer Tastatur.

      Seit .Net 4.0 und Windows 7 bieten die Klassen UIElement, UIElement3D und ContentElement Events an um die Funktionalität von Multitouch-Displays zu nutzen. Die Events werden geworfen wenn der Benutzer mit dem Finger ein WPF Element berührt, den Finger darin bewegt oder loslässt.
      • TouchDown
        Tritt beim berühren des Bildschirms innerhalb eines Elements auf
      • TouchUp
        Tritt auf wenn der Finger den Bildschirm wieder loslässt, wenn dieser vorher innerhalb eines Elements befand
      • TouchLeave
        Tritt beim verlassen des Elements auf, also wenn der Finger aus dem Element herausfährt
      • TouchMove
        Tritt auf wenn der Finger sich innerhalb des Elements bewegt
      Alle Touch-Events sind als Bubbling-Events implementiert, Blubbern also den ElementTree nach oben und verwenden die TouchEventArgs.
      Die Klasse TouchEventArgs besitzt unter anderem zwei interessante Eigenschaften bzw. Methoden. GetTouchPoint() gibt einen TouchPoint zurück welcher wiederum die Eigenschaft Position enthält und gibt die relative Position zum Berührungspunkt zurück.

      Die TouchDevice Eigenschaft vom Typ TouchDevice enthält eine ID vom Typ Integer und enthält einen eindeutigen Wert für den Berührungspunkt. Die ID ist dabei abhängig vom Treiber und dem Betriebssystem. Da es bei einem MultiTouch-Display ja mehrere Berührungspunkte geben kann (auch mehr oder weniger gleichzeitig) ist die Auswertung der ID wichtig um Rückschlüsse auf die „Geste“ zu bekommen, welche der User gerade macht.

      Aber neben den genannten „Low-Level-Touch“ Events bietet die Klasse UIElement auch die sehr interessanten Manipulation-Events.

      Die Manipulation-Events werden zum Skalieren, Rotieren und Verschieben eines Elements genutzt. Damit die Events überhaupt geworfen werden muss die Eigenschaft IsManipulationEnabled des Elements auf True gesetzt werden da diese per Default deaktiviert ist. Das geht sowohl über XAML als auch über die CodeBehind.
      Es gibt für die Manipulation einige Events: ManipulationStarting, ManimulationStarted, ManimulationDelta, ManipulationCompleted.
      Da die MultiTouch Funktionalität hier an dieser Stelle und auch in einem WebCast Video recht schwer zu „zeigen“ ist verzichte ich an dieser Stelle darauf. Für alle die ein MultiTouch-Display zu Verfügung haben können sich mit folgendem Beispiel aus der MSDN ein wenig spielen:

      docs.microsoft.com/en-us/dotne…r-first-touch-application


      Hier das PDF für Leute mit EBook: 2.1.8.4.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.8.5 - Focus

      Neu

      2.1.8.5

      Focus




      Wie jetzt ein eigenes Kapitel über den Fokus? In der Tat. Fokus ist nicht gleich Fokus.
      Es gibt in der WPF zwei Hauptkonzepte. Den Tastaturfokus und den logischen Fokus.
      Es ist wichtig den Unterschied zwischen den beiden Konzepten zu kennen.

      Die beiden Keyplayer sind die Klassen Keyboard und FocusManager. Die Klasse Keyboard betrifft den Tastaturfokus während der FocusManager den logischen Fokus in der Hand hat.

      Tastaturfokus



      Der Tastaturfokus bezieht sich immer auf das Element welches gerade Tastatureingaben empfängt. Es kann am gesamten Desktop nur ein Element geben welches Tastatureingaben empfängt. Innerhalb eine WPF Anwendung ist auf diesem Element die Eigenschaft IsKeyboardFocused in diesem Fall auf True festgelegt. Die statische Eigenschaft FocusedElement der Klasse Keyboard gibt immer das Element zurück welches gerade den Tastaturfokus besitzt.
      Damit ein Element den Tastaturfokus erhalten kann muss die Eigenschaft Focusable auf True festgelegt sein. Dies ist bei Elementen wie der TextBox oder der CheckBox per Default auf True, bei Elementen wie Panels allerdings auf False, wodurch diese keinen Tastaturfokus erhalten können solange die Eigenschaft nicht auf True gesetzt wird.

      Der Benutzer kann den Tastaturfokus auf ein Steuerelement festlegen indem er in dieses Klickt oder die Tabulatortaste benutzt. Aber der Focus kann auch über Code gesteuert werden.

      Folgende Methode setzt nach dem laden des Fensters den KeyboardFocus auf den Button mit dem Namen firstButton.

      VB.NET-Quellcode

      1. Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
      2. Keyboard.Focus(firstButton)
      3. End Sub


      Mit der Eigenschaft IsKeyboardFocused eines Elements kann abgerufen werden ob dieses gerade den Tastaturfokus besitzt. Interessant ist auch die Eigenschaft IsKeyboardFocusWithin.
      Diese gibt einen Wert zurück welcher aussagt ob ein diesem Element untergeordnetes Element gerade den Focus besitzt.

      Logischer Fokus

      Der Logische Focus wird von der statischen Klasse FocusManager jeweils für einen gewissen Bereich verwaltet. Diese Bereiche nennt man auch Focusbereich (Focus-Scope) und wird definiert indem man das AttachedProperty FocusManager.IsFocusScope eines Elements auf True setzt.

      Elemente wie Window, Menu, Toolbar oder ContextMenü haben diese Eigenschaft per Default auf True gesetzt und definieren damit ihren eigenen FocusScope, also ihren Fokus-Bereich.

      Angenommen es gibt ein Window mit einem Menu welches ein MenuItem enthält und zusätzlich einer TextBox innerhalb des Window. Wechselt nun der Tastaturfokus von der TextBox auf das MenuItem weil der User mit TAB navigiert, verliert die TextBox den Tastaturfokus, behält aber den logischen Fokus innerhalb des Fokusbereichs des Windows. Das MenuItem erhält also den Tastaturfokus UND den logischen Fokus innerhalb des Fokusbereichs des Menu.

      Geht nun der Tastaturfokus zurück zum Window, erhält das Element mit dem logischen Focus innerhalb dieses Fokusbereichs wieder den Tastaturfokus, was in diesem Fall die TextBox ist.
      Das MenuItem verliert also den Tastaturfokus aber behält aber innerhalb des Fokusbereichs des Menu den logischen Fokus.

      Es können also mehrere Elemente im logischen Fokusbereich liegen aber nur ein Element innerhalb des Fokusbereichs den logischen Fokus haben.
      Folgendes Beispiel ruft das fokussierte Element eines Fokusbereichs ab (hier im Fokusbereichs des Menu):

      FocusManager.GetFocusedElement(meineMenuInstanz)
      Falls kein Element in diesem Fokusbereich den logischen Fokus besitzt, gibt die Methode Nothing zurück.

      Fazit:
      Wichtig ist das wir den Unterschied zwischen dem logischen Fokus und dem Tastaturfokus kennen oder einfach wissen das es hier einen Unterschied gibt. Unser Hauptaugenmerk sollte immer auf dem Tastaturfokus liegen da uns der logische Fokus im Normalfall nicht so oft über den Weg läuft und wir uns nur bei komplexeren Szenarien beschäftigen müssen. Kommt es allerdings mal zu einem unerwünschten Verhalten was den Fokus betrifft wissen wir nun dass wir uns den Focus-Scope genauer ansehen müssen um der Sache auf den Grund zu gehen.



      Hier das PDF für Leute mit EBook:
      2.1.8.5.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      2.1.8.6 - Commands

      Neu

      2.1.8.6

      Commands




      Commands sind ein sehr wichtiger Mechanismus in der WPF. Der Zeck von Command ist zum einen die Trennung zwischen dem Element welches den Command auslöst und der Logik welche hinter einem Command steht und zum anderen hat ein Command eine Eigenschaft welche bestimmt ob dieser ausgeführt werden kann. Dazu kommen wir aber noch.
      Wenn wir einen EventHandler für einen Button-Klick setzen können wir innerhalb dieses EventHandler Logik unterbringen. Wir können denselben EventHandler allerdings nicht verwenden wenn wir die Logik zu einem anderen Ereignis als einem Klick ausführen möchten.
      Weiters müssen wir uns selbst darum kümmern das ein Button „disabled“ wird wenn die Aktion im Moment nicht ausgeführt werden kann. Ein Beispiel hierfür wäre das Speichern eines Formulars wenn noch nicht alle Pflichtfelder ausgefüllt sind. In diesem Fall darf der User noch nicht auf Speichern klicken können. Wir müssen also immer prüfen ob alle Felder ausgefüllt sind und wenn dies der Fall ist den Button „enablen“. Dies kann uns alles ein Command abnehmen.
      Außerdem können Command sehr einfach eine Tastenkombination zugeordnet werden und so Beispielsweise F5 für das Speichern zu verwenden.

      Ein einfaches Beispiel

      Die einfachste Möglichkeit einen Command zu verwenden ist es einen vordefinierten RoutedCommand zu verwenden. Microsoft war so nett uns eine Reihe von Commands zu definieren welche wir überall Verwenden können.
      Der Command Paste ist einer dieser vordefinierten Commands und befindet sich in der statischen Klasse ApplicationCommands.

      Erstellen wir uns ein neues Projekt und ersetzen das Grid des MainWindow gegen folgenden Code:

      XML-Quellcode

      1. <StackPanel>
      2. <Menu>
      3. <MenuItem Command="ApplicationCommands.Paste" />
      4. </Menu>
      5. <TextBox />
      6. </StackPanel>


      Debuggen wir nun dieses Projekt sehen wir das das MenuItem „Einfügen“ benannt ist obwohl wir diesen Text nicht im XAML definiert haben. Weiters ist das MenuItem Enabled=False.
      Setzen wir nun den Keyboardfokus auf die TextBox wird sofort das MenuItem Enabled=True.

      Haben wir im Moment einen Text in der Zwischenablage können wir diesen Text nun durch einen Klick auf das MenuItem einfach in die Textbox einfügen. OK, das sieht jetzt alles ein wenig nach Magie und Hexenwerk aus, gebe ich zu.
      Das Modell der RoutedCommands kann in vier Hauptkonzepte aufgeteilt werden.
      • Der Befehl (Command) – ist die Aktion die ausgeführt werden soll
      • Die Befehlsquelle (Command-Source) – das Objekt welches den Befehl aufruft
      • Das Befehlsziel (Command-Target) – das Objekt auf das der Befehl ausgeführt wird
      • Die Befehlsbindung (Command-Binding) – das Objekt das den Befehl der Logik zuordnet

      Im vorherigen Beispiel ist Paste der Befehl, MenuItem ist die Befehlsquelle, die TextBox das Befehlsziel und die Befehlsbindung wird von der TextBox bereitgestellt. Ein Command-Objekt implementiert die Schnittstelle ICommand. Die ICommand Schnittstelle hat zwei Methoden. Die Execute und die CanExecute Methode sowie ein Event CanExecuteChanged.
      Wir können es uns bereits denken, Execute führt den Befehl aus und CanExecute bestimmt ob der Befehl ausgeführt werden kann.
      Das Ereignis CanExecutChanged wird ausgeführt wenn der CommandManager der WPF eine Änderung einer Befehls-Quelle erkennt und CanExecute kann somit aktualisiert werden.

      Nun ist es ja schon nicht mehr so viel Magie. Da einem RoutedCommand also die Quelle kennt (im obigem Beispiel das MenuItem) kann es dessen Header auch schreiben wodurch „Einfügen“ als Header eingesetzt wird. Allerdings nur wenn durch den Entwickler kein Header angegeben wurde. Dass das MenuItem automatisch Enabled oder Disabled wird, gibt Methode CanExecute zurück.


      Input-Gestures


      Imput-Gestures (Eingabegesten) können als Befehlsquelle verwendet werden. Es gibt z.b. KeyGesture und MouseGesture um zwei Eingabearten zu nennen.
      Beispielsweise ist dem Command Paste die KeyGesture STRG+V zugeordnet.
      Adaptieren wir das vorherige Beispiel um die Möglichkeit mit der Taste F3 den Einfügebefehl auszuführen.
      Wir ersetzen den Code des StackPanels und fügen stattdessen folgenden Code ein:

      XML-Quellcode

      1. <Window.InputBindings>
      2. <KeyBinding Key="F3"
      3. CommandTarget="{Binding ElementName=txtTest}"
      4. Command="ApplicationCommands.Paste" />
      5. </Window.InputBindings>
      6. <StackPanel>
      7. <Menu>
      8. <MenuItem Command="ApplicationCommands.Paste" />
      9. </Menu>
      10. <TextBox x:Name="txtTest" />
      11. </StackPanel>


      Da der Command nun nicht auf dem Ziel Definiert ist auf welchem es ausgeführt wird, müssen wir über die Eigenschaft CommandTarget das Ziel festlegen was die TextBox sein soll. Das ist auch der Grund warum die TextBox nun einen Namen bekommen hat.
      Starten wir nun das Programm können wir Text aus der Zwischenablage mit F3 einfügen, unabhängig davon ob die TextBox den Fokus hat oder hatte, da wir das Ziel explizit angegeben haben.
      Aber wir können einem vorhandenem Command auch eigene Logik implementieren. Hierfür können wir sogenannte CommandBindings im XAML definieren.

      XML-Quellcode

      1. <Window.CommandBindings>
      2. <CommandBinding Command="ApplicationCommands.Open"
      3. Executed="OpenExecuted"
      4. CanExecute="OpenCanExecute"/>
      5. </Window.CommandBindings>


      Sowohl bei „OpenExecute“ als auch bei „OpenCanExecute“ können wir mit einemRechtsklick -> Gehe zu Definition nun die EventHandler im CodeBehind erstellen lassen.

      Folgende CodeBehind sollten wir nun vor uns finden:

      VB.NET-Quellcode

      1. Class MainWindow
      2. Private Sub OpenExecuted(sender As Object, e As ExecutedRoutedEventArgs)
      3. End Sub
      4. Private Sub OpenCanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
      5. End Sub
      6. End Class


      In diese beiden Methoden können wir nun Logik platzieren welche beim ausführen dieses Command ausgeführt werden soll.
      In OpenExecute möchten wir in diesem Beispiel einfach nur eine MessageBox zeigen in welcher wir uns anzeigen lassen möchten welcher Command auf welchem Objekt ausgeführt wurde um zu sehen was passiert.

      VB.NET-Quellcode

      1. Private Sub OpenExecuted(sender As Object, e As ExecutedRoutedEventArgs)
      2. Dim command = CType(e.Command, RoutedCommand).Name
      3. Dim targetobj = CType(sender, FrameworkElement).Name
      4. MessageBox.Show($"Der Command '{command}' auf dem Objekt '{targetobj}' wurde ausgeführt.")
      5. End Sub


      Über die ExecutedRoutedEventArgs kommen wir unter anderem an den Command welcher ausgeführt wird und über den Parameter Sender können wir den Namen des Objekts abrufen.
      In der OpenCanExecute Methode setzen wir die CanExecute Eigenschaft des Parameters e vom Typ CanExecuteRoutedEventArg auf True um die Ausführung des Commands immer zu erlauben, da wir in diesem Fall keine Abhängigkeiten haben welche die Ausführung behindern nicht erlauben würde.

      VB.NET-Quellcode

      1. Private Sub OpenCanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
      2. e.CanExecute = True
      3. End Sub


      Starten wir das Programm wird nichts passieren, wir haben nämlich weder eine InputBinding definiert noch einem Button, MenuItem oder einem anderen Steuerelement diesen Command zugewiesen. Das werden wir nachholen.

      XML-Quellcode

      1. <Window.CommandBindings>
      2. <CommandBinding Command="ApplicationCommands.Open"
      3. Executed="OpenExecuted"
      4. CanExecute="OpenCanExecute"/>
      5. </Window.CommandBindings>
      6. <Window.InputBindings>
      7. <KeyBinding Key="F3"
      8. CommandTarget="{Binding ElementName=txtTest}"
      9. Command="ApplicationCommands.Paste" />
      10. <KeyBinding Modifiers="Ctrl"
      11. Key="O"
      12. Command="ApplicationCommands.Open"/>
      13. </Window.InputBindings>
      14. <StackPanel>
      15. <Menu>
      16. <MenuItem Command="ApplicationCommands.Paste" />
      17. <MenuItem Command="ApplicationCommands.Open" />
      18. </Menu>
      19. <TextBox x:Name="txtTest" />
      20. </StackPanel>


      Starten wir nun das Programm können wir sowohl über das MenuItem als auch über die Tastenkombination STRG+O das Command ausführen und erhalten die MessageBox:



      Wir haben also gelernt was es mit Commands, CommandBindings und InputBindings auf sich hat. Am besten ist es man spielt ein wenig damit rum, und versucht ein wenig Logik in vorhandene Command zu bekommen. Die vorhandenen ApplicationCommands eigenen sich hervorragend um einen kleinen TextEditor zu basteln.

      Eine Übersicht zu allen vordefinierten Command findet man hier: docs.microsoft.com/de-de/dotne…nds?view=netframework-4.8





      Eigene Commands erstellen

      Um eigene Command zu erstellen gibt es zwei Möglichkeiten. Zum einen kann man eine Klasse erstellen welche die Schnittstelle ICommand implementiert oder, die gängigere Methode, die Erstellung eines RoutedCommand oder eines RoutedUiCommands.

      Da wir die Schnittstelle im nächsten Kapitel „RelayCommands“ kennenlernen werden, gehe ich hier erstmal auf die letztere Möglichkeit ein.
      Wir erstellen in diesem Beispiel einen RoutedCommand welcher bei seiner Ausführung einfach eine MessageBox zeigen soll. Um einen RoutedCommand zu erstellen gehen wir in die CodeBehind des Window und erstellen eine Statische Variable vom Typ RoutedCommand.
      Ich erstelle hierfür ein neues Fenster mit dem Namen CustomRoutedCommand.

      VB.NET-Quellcode

      1. Public Shared ShowMessageCommand As RoutedCommand = New RoutedUICommand


      Im XAML definieren wir nun ein CommandBinding für diesen Command. Da die Variable als Shared gekennzeichnet ist können wir im XAML über x:Static darauf zugreifen.
      Wir kompilieren vorher kurz um im XAML Intellisense zu erhalten.

      XML-Quellcode

      1. <Window.CommandBindings>
      2. <CommandBinding Command="{x:Static local:CustomRoutedCommand.ShowMessageCommand}"
      3. Executed="ShowMessageExecuted"
      4. CanExecute="ShowMessageCanExecute"/>
      5. </Window.CommandBindings>


      Nachdem wir uns die Handler generieren haben lassen, sieht unsere CodeBehind folgendermaßen aus:

      VB.NET-Quellcode

      1. Public Class CustomRoutedCommand
      2. Public Shared ShowMessageCommand As RoutedCommand
      3. Private Sub ShowMessageExecuted(sender As Object, e As ExecutedRoutedEventArgs)
      4. End Sub
      5. Private Sub ShowMessageCanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
      6. End Sub
      7. End Class


      Gehen wir zurück zum XAML, dort möchten wir nun noch ein MenuItem erstellen welches diesen Command ausführen soll.

      XML-Quellcode

      1. <StackPanel>
      2. <Menu>
      3. <MenuItem Header="Zeige Messagebox" Command="{x:Static local:CustomRoutedCommand.ShowMessageCommand}"/>
      4. </Menu>
      5. <TextBox />
      6. </StackPanel>


      Zum Schluss noch den Code in die EventHandler eingetragen:

      VB.NET-Quellcode

      1. Public Class CustomRoutedCommand
      2. Public Shared ShowMessageCommand As RoutedCommand = New RoutedUICommand()
      3. Private Sub ShowMessageExecuted(sender As Object, e As ExecutedRoutedEventArgs)
      4. MessageBox.Show("Hallo aus einem eigenen Command")
      5. End Sub
      6. Private Sub ShowMessageCanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
      7. e.CanExecute = True
      8. End Sub
      9. End Class


      Starten wir nun das Programm bekommen wir die MessageBox nach einem Klick auf das MenuItem zu sehen.



      Wir sehen also dass es nicht schwer ist eigene Commands zu implementieren. Auch Tastenkombinationen und das automatische „Labeln“ des Objekts an welches der Command „gebunden“ ist, ist möglich und durch die CanExecute Methode haben wir sogar Beispielsweise einen Button automatisch Disabled wenn der Command nicht ausgeführt werden kann.





      Hier die Solution wieder als Download: 2.1.8.6_CommandsDemo.zip
      Hier das PDF für Leute mit EBook:
      2.1.8.6.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##