2012-12-09 15 views
9

Siehe den nächsten Beitrag. Dieser ursprüngliche Frage-Inhalt wurde entfernt, da er keinen Sinn hat. Kurz gesagt, ich fragte, wie man XML (das ich irrtümlich beim Analysieren der DLL-Assembly erzeugt habe) an TreeView bindet, indem ich XmlDataProvider auf MVVM-Art verwende. Aber später verstand ich, dass dieser Ansatz falsch war, und ich wechselte zur Generierung des Datenentitätsmodells (schreibe einfach Klassen, die alle Entitäten darstellen, die ich in der Baumstruktur darstellen möchte) anstelle von XML.WPF: Binding TreeView in MVVM Weg Schritt für Schritt Tutorial

Also, das Ergebnis im nächsten Post. Von Zeit zu Zeit aktualisiere ich diesen "Artikel", also F5, und

Viel Spaß beim Lesen!

Antwort

24

Einführung

Der richtige Weg, ich this Artikel lesen

Es ist eine lange Geschichte gefunden hatte, die meisten von euch kann es nur überspringen :) Aber diejenigen, die das Problem und Lösung verstehen wollen, Muss das alles lesen!

Ich bin QA, und vor einiger Zeit war verantwortlich für die Automatisierung des Produkts, das ich klicke. Glücklicherweise findet dieser Automat nicht in einem Testing Tool statt, sondern in Visual Studio, so dass er der Entwicklung maximal nahe kommt.

Für unsere Automatisierung verwenden wir einen Rahmen, der aus MbUnit (Gallio als Läufer) und MINT (Ergänzung zu MbUnit, die von dem Kunden, mit dem wir arbeiten, besteht) besteht. MbUnit gibt uns Test Fixtures und Tests, und MINT fügt zusätzliche kleinere Layer hinzu - Aktionen innerhalb von Tests. Beispiel. Fixture heißt "FilteringFixture". Es besteht aus einer Menge von Tests wie 'TestingFilteringById' oder 'TestingFilteringWithSpecialChars', usw. Jeder Test besteht aus Aktionen, die die atomare Einheit unseres Tests sind. Beispiele für Aktionen sind - 'App öffnen (Parameter)', 'OpenFilterDialog', etc.

Wir haben bereits viele Tests, die eine Menge Aktionen enthalten, es ist ein Durcheinander. Sie verwenden interne API des Produkts, das wir QA. Außerdem untersuchen wir einen neuen Automatisierungsansatz - UI-Automatisierung über Microsoft UI Automation (Entschuldigung für die Tautologie). So wurde die Notwendigkeit eines "Exporteur" oder "Reporter" -Tools für Manager ernst.

Vor einiger Zeit habe ich eine Aufgabe, eine Anwendung zu entwickeln, die eine DLL analysieren kann (die alle Fixtures, Tests und Aktionen enthält), und ihre Struktur im lesbaren Format (TXT, HTML, CSV, XML, andere). Aber gleich danach ging ich in den Urlaub (2 Wochen).

Es passiert so, dass meine Freundin zu ihrer Familie ging bis zum Urlaub (sie bekam es auch), und ich blieb so allein zu Hause. Ich denke daran, was ich die ganze Zeit machen soll (2 Wochen), ich erinnere mich an diese "Schreibwerkzeug-Aufgabe schreiben" und daran, wie lange ich geplant habe, WPF zu lernen. Also habe ich beschlossen, meine Aufgabe während des Urlaubs zu erledigen, und auch eine Bewerbung an WPF zu kleiden. Zu der Zeit habe ich etwas über MVVM gehört, und ich habe beschlossen, es mit reinem MVVM zu implementieren.

DLL, die DLL mit fixtrures usw. analysieren kann, wurde ziemlich schnell geschrieben (~ 1-2 Tage). Danach habe ich mit WPF begonnen, und dieser Artikel wird Ihnen zeigen, wie es endete.

Ich habe einen Großteil meines Urlaubs (fast 8 Tage!) Verbracht, versucht, es in meinem Kopf und Code zu sortieren, und schließlich ist es (fast) fertig. Meine Freundin würde nicht glauben was ich die ganze Zeit gemacht habe, aber ich habe einen Beweis!

Teilen Sie meine Lösung Schritt für Schritt in Pseudocode, um anderen ähnliche Probleme zu vermeiden. Diese Antwort sieht eher wie Tutorial =) aus (Wirklich?).Wenn Sie interessiert sind, was die kompliziertesten Dinge beim Lernen von WPF von Grund auf waren, würde ich sagen - machen Sie es wirklich MVVM und f * g TreeView Bindung!

Wenn Sie eine archivierte Datei mit Lösung wollen, kann ich es später etwas geben, nur wenn ich eine Entscheidung getroffen haben, dass es das wert ist. Eine Einschränkung, ich bin mir nicht sicher, ob ich die MINT.dll teilen darf, die Actions bringt, wie sie vom Kunden unserer Firma entwickelt wurde. Aber ich kann es einfach entfernen und die Anwendung freigeben, die nur Informationen über Fixtures und Tests, aber nicht über Aktionen anzeigen kann.

Boastful Worte. Mit nur ein wenig C#/WinForms/HTML-Hintergrund und ohne Übung konnte ich diese Version der Anwendung in fast 1 Woche implementieren (und diesen Artikel schreiben). Also, unmöglich ist möglich! Nimm einfach einen Urlaub wie ich, und verbringe ihn zum WPF-Lernen!

Schritt für Schritt Anleitung (w/o angehängten Dateien noch)

Kurze Wiederholung der Aufgabe:

Vor einiger Zeit habe ich eine neue Aufgabe bekommen haben, eine Anwendung zu entwickeln, die eine DLL analysieren kann (die Testvorrichtungen, Testmethoden und Aktionen enthält - Einheiten unseres Unit-Testing-basierten Automatisierungsframeworks), und ihre Struktur in einem für Menschen lesbaren Format (TXT, HTML, CSV, XML, beliebige andere) exportieren. Ich entschied mich, es mit WPF und reinem MVVM zu implementieren (beides waren absolut neue Dinge für mich). Die 2 schwierigsten Probleme für mich wurden MVVM Ansatz selbst, und dann MVVM Bindung an TreeView-Steuerelement. Ich überspringe den Teil über die MVVM-Abteilung, es ist ein Thema für einen separaten Artikel. In den folgenden Schritten geht es darum, in MVVM an TreeView zu binden.

  1. nicht so wichtig: DLL erstellen, die DLL mit Unit-Tests öffnen und finden Vorrichtungen, Prüfverfahren und Aktionen (mehr kleinere Niveau des Unit-Tests, in unserer Gesellschaft geschrieben) mit Reflexion. Wenn Sie daran interessiert sind, wie es gemacht wurde, schauen Sie hier: Parsing function/method content using Reflection
  2. DLL: Getrennte Klassen werden für beide Fixtures, Tests und Aktionen erstellt (Datenmodell, Entitätsmodell?). Wir werden sie für die Bindung verwenden. Sie sollten selbst darüber nachdenken, was ein Entity-Modell für Ihren Baum sein wird. Grundgedanke - jede Ebene des Baumes sollte durch geeignete Klassen mit diesen Eigenschaften offengelegt werden, die Ihnen helfen, das Modell im Baum darzustellen (und idealerweise in Ihrem MVVM als Modell oder Teil des Modells den richtigen Platz einnehmen). In meinem Fall interessierte mich der Name der Entität, die Liste der Kinder und die Ordnungszahl. Ordnungszahl ist eine Zahl, die die Reihenfolge einer Entität im Code innerhalb der DLL darstellt. Es hilft mir, die Ordnungszahl im TreeView anzuzeigen, immer noch nicht sicher, ob es richtig ist, aber es funktioniert!
public class MintFixutre : IMintEntity 
{ 
    private readonly string _name; 
    private readonly int _ordinalNumber; 
    private readonly List<MintTest> _tests = new List<MintTest>(); 
    public MintFixutre(string fixtureName, int ordinalNumber) 
    { 
     _name = fixtureName; 
     if (ordinalNumber <= 0) 
      throw new ArgumentException("Ordinal number must begin from 1"); 
     _ordinalNumber = ordinalNumber; 
    } 
    public List<MintTest> Tests 
    { 
     get { return _tests; } 
    } 
    public string Name { get { return _name; }} 
    public bool IsParent { get { return true; } } 
    public int OrdinalNumber { get { return _ordinalNumber; } } 
} 

public class MintTest : IMintEntity 
{ 
    private readonly string _name; 
    private readonly int _ordinalNumber; 
    private readonly List<MintAction> _actions = new List<MintAction>(); 
    public MintTest(string testName, int ordinalNumber) 
    { 
     if (string.IsNullOrWhiteSpace(testName)) 
      throw new ArgumentException("Test name cannot be null or space filled"); 
     _name = testName; 
     if (ordinalNumber <= 0) 
      throw new ArgumentException("OrdinalNumber must begin from 1"); 
     _ordinalNumber = ordinalNumber; 
    } 
    public List<MintAction> Actions 
    { 
     get { return _actions; } 
    } 
    public string Name { get { return _name; } } 
    public bool IsParent { get { return true; } } 
    public int OrdinalNumber { get { return _ordinalNumber; } } 
} 

public class MintAction : IMintEntity 
{ 
    private readonly string _name; 
    private readonly int _ordinalNumber; 
    public MintAction(string actionName, int ordinalNumber) 
    { 
     _name = actionName; 
     if (ordinalNumber <= 0) 
      throw new ArgumentException("Ordinal numbers must begins from 1"); 
     _ordinalNumber = ordinalNumber; 

    } 
    public string Name { get { return _name; } } 
    public bool IsParent { get { return false; } } 
    public int OrdinalNumber { get { return _ordinalNumber; } } 
} 

BTW, habe ich auch eine unter-Schnittstelle, die alle Entitäten implementieren. Eine solche Schnittstelle kann Ihnen in Zukunft helfen. Immer noch nicht sicher, sollte ich auch dort Childrens Eigenschaft von List<IMintEntity> Typ hinzufügen, oder so ähnlich?

public interface IMintEntity 
{ 
    string Name { get; } 
    bool IsParent { get; } 
    int OrdinalNumber { get; } 
} 
  1. DLL - Gebäudedatenmodell: DLL verfügt über eine Methode, die DLL mit Unit-Tests und Aufzählen von Daten wird geöffnet. Während der Enumeration wird ein Datenmodell wie unten erstellt. Reales Methodenbeispiel wird angegeben, Reflektionskern + Mono.Reflection.dll wird verwendet, sollte nicht mit Komplexität verwechselt werden. Alles was Sie brauchen - sehen Sie, wie die Methode _fixtures mit Entitäten füllt.
private void ParseDllToEntityModel() 
{ 
    _fixutres = new List<MintFixutre>(); 

    // enumerating Fixtures 
    int f = 1; 
    foreach (Type fixture in AssemblyTests.GetTypes().Where(t => t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Length > 0)) 
    { 
     var tempFixture = new MintFixutre(fixture.Name, f); 

     // enumerating Test Methods 
     int t = 1; 
     foreach (var testMethod in fixture.GetMethods().Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Length > 0)) 
     { 
      // filtering Actions 
      var instructions = testMethod.GetInstructions().Where(
       i => i.OpCode.Name.Equals("newobj") && ((ConstructorInfo)i.Operand).DeclaringType.IsSubclassOf(typeof(BaseAction))).ToList(); 

      var tempTest = new MintTest(testMethod.Name, t); 

      // enumerating Actions 
      for (int a = 1; a <= instructions.Count; a++) 
      { 
       Instruction action = instructions[a-1]; 
       string actionName = (action.Operand as ConstructorInfo).DeclaringType.Name; 
       var tempAction = new MintAction(actionName, a); 
       tempTest.Actions.Add(tempAction); 
      } 

      tempFixture.Tests.Add(tempTest); 
      t++; 
     } 

     _fixutres.Add(tempFixture); 
     f++; 
    } 
} 
  1. DLL: öffentliche Eigenschaft Fixtures des List<MintFixutre> Typs erstellt wird gerade erstellt Datenmodell (Liste des Fixtures zurückzukehren, die Listen von Tests enthalten, das enthält Listen von Aktionen). Dies wird unsere verbindliche Quelle für TreeView sein.
public List<MintFixutre> Fixtures 
{ 
    get { return _fixtures; } 
} 
  1. Ansichtsmodell von Mainwindow (mit TreeView innen): Enthält Objekt/Klasse von DLL, die Unit-Tests DLLs analysieren kann. Zeigt auch Fixtures öffentliche Eigenschaft von der DLL List<MintFixutre> Typ. Wir binden es von XAML von MainWindow. So etwas Ähnliches (vereinfacht):
var _exporter = MySuperDllReaderExporterClass(); 
// public property of ViewModel for TreeView, which returns property from #4 
public List<MintFixture> Fixtures { get { return _exporter.Fixtures; }} 
// Initializing exporter class, ParseDllToEntityModel() is called inside getter 
// (from step #3). Cool, we have entity model for binding. 
_exporter.PathToDll = @"open file dialog can help"; 
// Notifying all those how are bound to the Fixtures property, there are work for them, TreeView, are u listening? 
// will be faced later in this article, anticipating events 
OnPropertyChanged("Fixtures"); 
  1. XAML von Mainwindow - Setup-Datenvorlagen: Innerhalb eines Grid, das TreeView enthält, erstellen wir <Grid.Resources> Abschnitt, der enthält eine Reihe von Vorlagen für unsere TreeViewItem s. HierarchicalDataTemplate (Fixtures and Tests) wird für diejenigen verwendet, die untergeordnete Elemente haben, und DataTemplate wird für "Blatt" -Elemente (Aktionen) verwendet. Für jede Vorlage geben wir an, welchen Inhalt (Text, TreeViewItem-Bild usw.), ItemsSource (im Fall dieses Elements hat er Kinder, zB für Fixtures {Binding Path=Tests}) und ItemTemplate (wiederum nur für den Fall, dass dieser Gegenstand Kinder hat, Hier legen wir die Verknüpfung zwischen den Vorlagen fest - FixtureTemplate verwendet TestTemplate für seine untergeordneten Elemente, TestTemplate verwendet ActionTemplate für seine untergeordneten Elemente, Action template verwendet nichts, es ist ein Blatt!). WICHTIG: Vergessen Sie nicht, dass, um "eine" Vorlage mit einer "anderen" zu verknüpfen, die "andere" Vorlage in XAML über der "Eins" definiert werden muss! (Nur meine eigenen Fehler aufzählt :))

  2. XAML - TreeView Link Alter: Wir haben TreeView mit: Verknüpfung mit Datenmodell von ViewModel (erinnern Sie sich an öffentliche Eigenschaft?) und mit gerade vorbereiteten Vorlagen, die Inhalt, Aussehen, Datenquellen und Verschachtelung von Baumelementen darstellen! Noch eine wichtige Anmerkung. Definieren Sie Ihr ViewModel nicht als "statische" Ressource in XAML, z. B. <Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>. Wenn Sie dies tun, können Sie die Änderung der Eigenschaft nicht mitteilen. Warum? Statische Ressource ist eine statische Ressource, initialisiert eine und bleibt danach unveränderlich. Ich könnte hier falsch liegen, aber es war einer meiner Fehler. Also für TreeView verwenden ItemsSource="{Binding Fixtures}" statt ItemsSource="{StaticResource myStaticViewModel}"

  3. Ansichtsmodell - ViewModelBase - Objekt geändert: Fast alle. Halt! Wenn der Benutzer eine Anwendung öffnet, ist TreeView natürlich leer, da der Benutzer noch keine DLL geöffnet hat!Wir müssen warten, bis der Benutzer eine DLL öffnet, und erst dann die Bindung durchführen. Es ist über OnPropertyChanged Ereignis erfolgt. Um das Leben einfacher zu machen, werden alle meine ViewModels von ViewModelBase geerbt, was diese Funktionalität für alle meine ViewModels verfügbar macht.

public class ViewModelBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
      handler(this, args); 
    }   
} 
  1. XAML - OnPropertyChanged und befehlen. Der Benutzer klickt auf eine Schaltfläche, um die DLL zu öffnen, die Komponententestdaten enthält. Wie wir verwenden MVVM, dann klicken Sie auf wird über befehligt. Am Ende des OpenDllExecuted wird der Handler OnPropertyChanged("Fixtures") ausgeführt, der den Baum benachrichtigt, dass die Eigenschaft, an die er gebunden ist, geändert wurde, und dass es nun an der Zeit ist, sich selbst zu aktualisieren. RelayCommand Hilfsklasse kann beispielsweise aus there entnommen werden). BTW, wie ich weiß, gibt es einige Helfer Bibliotheken und Toolkits existieren So etwas in der XAML passiert:

  2. Und Ansichtsmodell - Kommandier

private ICommand _openDllCommand; 

     //... 

public ICommand OpenDllCommand 
{ 
    get { return _openDllCommand ?? (_openDllCommand = new RelayCommand(OpenDllExecuted, OpenDllCanExecute)); } 
} 

     //... 

// decides, when the <OpenDll> button is enabled or not 
private bool OpenDllCanExecute(object obj) 
{ 
    return true; // always true for Open DLL button 
} 

     //... 

// in fact, handler 
private void OpenDllExecuted(object obj) 
{ 
    var openDlg = new OpenFileDialog { ... }; 
    _pathToDll = openDlg.FileName; 
    _exporter.PathToDll = _pathToDll; 
       // Notifying TreeView via binding that the property <Fixtures> has been changed, 
       // thereby forcing the tree to refresh itself 
    OnPropertyChanged("Fixtures"); 
} 
  1. Endgültige UI (aber nicht endgültig für mich, eine Menge Dinge sollten getan werden!). Erweiterte WPF-Toolkit wurde irgendwo verwendet: http://wpftoolkit.codeplex.com/

+0

wow, das ist großartig – CodeFanatic

+0

Dies ist eine der besten Antworten, die ich je gelesen habe! –