Schneller Einstieg mit WPF. Teil 1. Bindung, INotifyPropertyChanged und MVVM

  • Tutorial

Hallo an alle!


Aus verschiedenen Gründen verwenden die meisten von uns Desktop-Anwendungen, zumindest einen Browser. :) Und einige von uns haben das Bedürfnis, eigene zu schreiben. In diesem Artikel möchte ich den Prozess der Entwicklung einer einfachen Desktopanwendung mithilfe der Windows Presentation Foundation (WPF) -Technologie und des MVVM-Musters erläutern. Diejenigen, die weiter lesen möchten, bitten um die Katze.


Ich denke, es ist nicht notwendig zu sagen, dass WPF die Entwicklung von Microsoft ist :) Diese Technologie wurde für die Entwicklung von Desktopanwendungen für Windows entwickelt, beginnend mit Windows XP. Warum so Dies ist auf die Tatsache zurückzuführen, dass WPF auf der .NET-Plattform ausgeführt wird, deren Mindestanforderungen Windows XP und höher sind. Leider funktioniert WPF nicht auf anderen Plattformen, auch wenn sich dies in naher Zukunft ändern wird: Das auf WPF basierende Avalonia Framework befindet sich in der Entwicklung .


Was ist das Besondere an WPF?


Die zwei Hauptunterschiede zu anderen WPF-Tools zum Erstellen von Desktopanwendungen:


  • Die XAML-Auszeichnungssprache, die die Fensteroberfläche selbst auszeichnet.
  • DirectX-Rendering, Hardwaregrafikbeschleunigung.

Ich werde nicht ins Detail gehen, weil Dies ist nicht genau das Thema des Artikels. Bei Interesse google XAML, WPF-Rendering, milcore.dll und DirectX :)


Worum geht es in diesem Artikel?


Dieser Artikel enthält ein Beispiel für eine Anwendung, die auf WPF-Technologie basiert:



Ich werde versuchen, das Material des Artikels im Erklärungsstil an die praktische Seite zu orientieren.


Was brauchen wir, um den Artikel zu wiederholen?


Wenig Erfahrung in der Entwicklung in C # :) Zumindest müssen Sie die Syntax der Sprache gut verstehen. Sie benötigen außerdem einen Windows-Computer (in den Beispielen wird Win 10 verwendet), auf dem Visual Studio installiert ist (in den Beispielen wird es 2017 eine kostenlose Community- Version geben). Bei der Installation von VS müssen Sie die Unterstützung für die Desktopentwicklung unter der .NET-Plattform aktivieren


Bild


In diesem Abschnitt werde ich auch die Erstellung des Projekts beschreiben.


Führen Sie VS aus, erstellen Sie ein neues Projekt, wählen Sie den Anwendungstyp WPF-App (.NET Framework) aus (Sie können ihn in die Suchleiste oben rechts eingeben), rufen Sie ihn nach Belieben auf.


Bild


Nachdem Sie ein neues Projekt erstellt haben, wird das Interface-Editor-Fenster geöffnet. Es sieht für mich so aus


Bild


Unten befindet sich ein Markup-Editor, eine Vorschau der Fensteroberfläche. Sie können jedoch die relative Position des Code-Editors und der Schnittstellenvorschau so ändern, dass sie mit diesen Schaltflächen (neben den beiden Bereichen) in horizontaler Reihenfolge angeordnet werden:


Bild


Bevor du anfängst


Fensterelemente (sie werden von Steuerwörtern genannt Control ) muss im Inneren des Behälters oder in einem anderen Typ Content Element platziert werden. Ein Container ist ein spezielles Steuerelement, mit dem Sie mehrere Partnersteuerelemente in sich platzieren und deren gegenseitige Anordnung festlegen können. Beispiele für Behälter:


  • Raster - Ermöglicht das Organisieren der Elemente in Spalten und Zeilen. Die Breite jeder Spalte oder Zeile wird individuell konfiguriert.
  • StackPanel - Mit dieser Option können Sie die untergeordneten Elemente in einer einzelnen Zeile oder Spalte anordnen.

Es gibt andere Behälter. Da es sich bei dem Container auch um ein Steuerelement handelt, können sich verschachtelte Container im Container befinden, die verschachtelte Container usw. enthalten. Auf diese Weise können Sie die Steuerelemente flexibel relativ zueinander positionieren. Mit Hilfe von Containern können wir außerdem das Verhalten verschachtelter Steuerelemente beim Ändern der Fenstergröße flexibel steuern.


MVVM- und INotifyPropertyChanged-Schnittstelle. Eine Kopie des Textes.


Das Ergebnis dieses Beispiels ist eine Anwendung mit zwei Steuerelementen, in denen Sie den Text bearbeiten können, und in der anderen können Sie nur anzeigen. Änderungen von einem zum anderen werden synchron übertragen, ohne dass der Text explizit über eine Bindung kopiert wird .


Wir haben also ein neu erstelltes Projekt (ich habe es Ex1 genannt ), in den Markup-Editor wechseln und zuerst den Standardcontainer ( <Grid> </ Grid> ) durch <StackPanel> </ StackPanel> ersetzen . Dieser Container reicht da aus Wir müssen nur zwei Steuerelemente übereinander platzieren. Durch das Hinzufügen der Eigenschaft Orientation = "Vertical" geben wir explizit an, wie die Komponenten lokalisiert werden . Fügen Sie im Panel-Stapel ein paar Elemente hinzu: ein Textfeld und ein Textfeld. Da diese Steuerelemente keinen verschachtelten Code enthalten, können Sie sie mit einem selbstschließenden Tag beschreiben (siehe den nachstehenden Code). Nach allen obigen Verfahren sollten der Container-Beschreibungscode und die verschachtelten Steuerelemente folgendermaßen aussehen:


<StackPanelOrientation="Vertical"><TextBox /><TextBlock /></StackPanel>

Konzentrieren Sie sich nun auf den Zweck dieses Beispiels. Wir möchten, dass derselbe Text beim Eingeben in das Textfeld synchron im Textblock angezeigt wird, während der explizite Textkopiervorgang vermieden wird. Wir werden eine Art verbindliche Entität brauchen, und hier kommen wir zu so etwas wie verbindlich , was oben erwähnt wurde. Das Verknüpfen in der WPF-Terminologie ist ein Mechanismus, mit dem Sie einige Eigenschaften von Steuerelementen mit einigen Eigenschaften eines C # -Klassenobjekts verknüpfen und diese Eigenschaften gegenseitig aktualisieren können, wenn sich einer der Verknüpfungsteile ändert (dies kann auf einer, der anderen oder beiden Seiten gleichzeitig funktionieren). Für diejenigen, die mit Qt vertraut sind, können Sie eine Analogie von Slots und Signalen zeichnen. Um die Zeit nicht zu verlängern, gehen wir weiter zum Code.


Um die Bindung zu organisieren, benötigen Sie also die Eigenschaften von Steuerelementen und eine bestimmte Eigenschaft einer bestimmten C # -Klasse. Betrachten wir zunächst den XAML-Code. Der Text beider Steuerelemente wird in der Text-Eigenschaft gespeichert. Daher werden wir für diese Eigenschaften eine Bindung hinzufügen. Das wird so gemacht:


<TextBox Text="{Binding}"/>
<TextBlock Text="{Binding}"/>

Wir haben eine Bindung gemacht, aber vorerst ist nicht klar, an was? :) Wir brauchen ein Objekt einer Klasse und eine Eigenschaft in diesem Objekt, für die die Bindung ausgeführt wird (wie sie sagen, die festgefahren sein muss).


Was ist diese Klasse? Diese Klasse wird als Ansichtsmodell bezeichnet und dient als Verbindung zwischen der Ansicht (Schnittstelle oder ihren Teilen) und dem Modell (Modell, dh denjenigen Teilen des Codes, die für die Anwendungslogik verantwortlich sind. Dies ermöglicht uns die Trennung (in gewissem Maße) Die Anwendungslogik der Schnittstelle (View, View) wird als Model-View-ViewModel-Muster (MVVM) bezeichnet . Innerhalb von WPF wird diese Klasse auch als DataContext bezeichnet .


Es reicht jedoch nicht aus, nur ein Ansichtsmodell zu schreiben. Es ist auch notwendig, den Bindungsmechanismus irgendwie zu benachrichtigen, dass die Ansichtsmodelleigenschaft oder die Ansichtseigenschaft geändert wurde. Dafür gibt es eine spezielle INotifyPropertyChanged- Schnittstelle , die das PropertyChanged- Ereignis enthält . Wir implementieren diese Schnittstelle als Teil der BaseViewModel- Basisklasse . In Zukunft werden wir alle unsere Modellansichten von dieser Basisklasse erben, um die Schnittstellenimplementierung nicht zu duplizieren. Fügen Sie dem Projekt das ViewModels- Verzeichnis hinzu , und fügen Sie diesem Verzeichnis die Datei BaseViewModel.cs hinzu . Wir erhalten folgende Projektstruktur:


Bild


Der Implementierungscode des Basisansichtmodells:


using System.ComponentModel;
namespaceEx1.ViewModels
{
    publicclassBaseViewModel : INotifyPropertyChanged
    {
        publicevent PropertyChangedEventHandler PropertyChanged;
        protectedvirtualvoidOnPropertyChanged(string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Erstellen wir unser Ansichtsmodell für unsere MainWindow- Klasse , das vom Basismodell erbt. Erstellen Sie dazu im selben ViewModels- Verzeichnis eine MainWindowViewModel.cs- Datei , in der sich folgender Code befindet:


namespaceEx1.ViewModels
{
    publicclassMainWindowViewModel : BaseViewModel
    {
    }
}

Herrlich! Jetzt müssen wir diesem Modell eine Eigenschaft hinzufügen, an die wir den Text unserer Steuerelemente binden werden. Da es sich um Text handelt, sollte der Typ dieser Eigenschaft eine Zeichenfolge sein :


publicstring SynchronizedText { get; set; }

Als Ergebnis erhalten wir diesen Code


namespaceEx1.ViewModels
{
    publicclassMainWindowViewModel : BaseViewModel
    {
        publicstring SynchronizedText { get; set; }
    }
}

Es scheint also so gewesen zu sein. Es muss noch auf diese Eigenschaft aus der Sicht gebogen werden und es ist fertig. Lass es uns gleich tun:


<TextBox Text="{Binding Path=SynchronizedText}"/>
<TextBlock Text="{Binding Path=SynchronizedText}"/>

Nishtyak, wir starten ein Projekt, wir schreiben Text in ein Textfeld und ... nichts passiert))) Nun, es ist in Ordnung, wir gehen in die richtige Richtung, haben den gewünschten Punkt noch nicht erreicht.


Ich möchte kurz innehalten und darüber nachdenken, was uns fehlt. Wir haben eine Aussicht. Viewmodel auch. Eigenschaften wie Zabindili. Die gewünschte Schnittstelle wurde implementiert. Wir haben viel gearbeitet, um eine pathetische Textzeile zu kopieren, warum brauchen wir sie?!?! 111


Okay, Witze beiseite. Wir haben vergessen, das Ansichtsmodellobjekt und etwas anderes zu erstellen (dazu später mehr). Wir haben die Klasse selbst beschrieben, aber sie hat nichts zu bedeuten, weil wir keine Objekte dieser Klasse haben. Ok, wo müssen Sie einen Link zu diesem Objekt speichern? Näher an der Spitze des Beispiel erwähnte ich einige der Datacontext , in WPF verwendet. Daher hat jede Ansicht eine DataContext- Eigenschaft , der wir unserem Ansichtsmodell einen Link zuweisen können. Lass es uns tun. Öffnen Sie dazu die Datei MainWindow.xaml und drücken Sie F7, um den Code für diese Ansicht zu öffnen. Es ist fast leer und enthält nur den Konstruktor der Fensterklasse. Fügen Sie die Erstellung unseres Ansichtsmodells hinzu und platzieren Sie es im DataContext des Fensters (vergessen Sie nicht, mit dem erforderlichen Namespace hinzuzufügen):


publicMainWindow()
{
    InitializeComponent();
    this.DataContext = new MainWindowViewModel();
}

Es war einfach, aber es reicht noch nicht. Wenn Sie die Anwendung starten, findet noch keine Textsynchronisierung statt. Was muss noch getan werden?


Sie müssen das PropertyChanged- Ereignis aufrufen, wenn Sie die SynchronizedText- Eigenschaft ändern und der Ansicht mitteilen, dass dieses Ereignis überwacht werden soll. Um ein Ereignis auszulösen, ändern Sie den Code des Ansichtsmodells:


publicclassMainWindowViewModel : BaseViewModel
{
    privatestring _synchronizedText;
    publicstring SynchronizedText
    {
        get => _synchronizedText;
        set
        {
            _synchronizedText = value;
            OnPropertyChanged(nameof(SynchronizedText));
        }
    }
}

Was haben wir hier gemacht? Fügen Sie ein verstecktes Feld Text zu speichern, wickelte es in eine vorhandene Eigenschaft, und die Änderung dieser Eigenschaft ändert sich nicht nur das versteckte Feld, sondern rufen auch die Methode OnPropertyChanged , definiert in der Basis vyumodeli und hebt das Ereignis der Property , in der Schnittstelle deklariert die INotifyPropertyChanged , wie es in der Basis implementiert Modelle anzeigen Es stellt sich heraus, dass bei jeder Änderung des Texts ein PropertyChanged- Ereignis auftritt , an das der Name der Ansichtsmodelleigenschaft übergeben wird, die geändert wurde.


Nun, fast alles, Ziellinie! Es muss noch die Ansicht angegeben werden, die das PropertyChanged- Ereignis abhören soll :


<TextBox Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/>
<TextBlock Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>

Abgesehen davon, dass wir angegeben haben, welcher Auslöser aktualisiert werden soll, haben wir auch angegeben, in welche Richtung diese Aktualisierung überwacht wird: von Ansicht zu Ansichtsmodell oder umgekehrt. Da wir Text in das Textfeld eingeben, interessieren wir uns nur für die Änderungen in der Ansicht. Daher wählen wir den OneWayToSource- Modus . Im Fall eines Textblocks ist alles genau das Gegenteil: Wir sind an Änderungen im Ansichtsmodell interessiert, um diese in einer Ansicht anzuzeigen. Daher wählen wir den OneWay- Modus . Wenn die Änderungen in beide Richtungen verfolgt werden mussten, war es möglich, den Modus überhaupt nicht oder TwoWay explizit anzugeben .


Starten Sie das Programm, geben Sie den Text und voi-la ein! Der Text ändert sich gleichzeitig und wir haben nichts kopiert!


Bild


Vielen Dank für Ihre Aufmerksamkeit, um fortzufahren. Wir werden uns mit dem DataTemplate und dem Command-Muster befassen.


Jetzt auch beliebt: