2012-12-09 19 views
9

Xem bài tiếp theo. Nội dung câu hỏi ban đầu này đã bị xóa, vì không có bất kỳ ý nghĩa nào. Tóm lại, tôi hỏi làm thế nào để ràng buộc XML (mà tôi tạo ra do nhầm lẫn trong khi phân tích cú pháp DLL lắp ráp) để TreeView sử dụng XmlDataProvider theo cách MVVM. Nhưng sau đó tôi hiểu rằng cách tiếp cận này là sai, và tôi chuyển sang thế hệ mô hình thực thể dữ liệu (chỉ viết các lớp đại diện cho tất cả các thực thể mà tôi muốn trưng ra trong cây) thay vì XML.WPF: Ràng buộc TreeView trong MVVM cách từng bước hướng dẫn

Vì vậy, kết quả trong bài đăng tiếp theo. Hiện tại, thỉnh thoảng tôi cập nhật "bài viết" này, vì vậy F5 và

Thích đọc!

Trả lời

24

Giới thiệu

Cách đúng tôi đã tìm thấy đọc this bài viết

Đó là một câu chuyện dài, hầu hết các bạn có thể chỉ cần bỏ qua nó :) Nhưng những người muốn tìm hiểu vấn đề và giải pháp, phải đọc tất cả điều này!

Tôi là QA và một thời gian trước đây đã trở thành chịu trách nhiệm về Tự động hóa sản phẩm tôi nhấp chuột. May mắn thay, automaton này diễn ra không phải trong một số công cụ kiểm tra, nhưng trong Visual Studio, do đó, nó là tối đa gần với sự phát triển.

Để tự động hóa, chúng tôi sử dụng một khung bao gồm MbUnit (Gallio as runner) và MINT (ngoài MbUnit, được viết bởi khách hàng mà chúng tôi làm việc). MbUnit cung cấp cho chúng tôi Kiểm tra Đồ đạc và Kiểm tra, và MINT bổ sung thêm lớp nhỏ hơn - Hành động bên trong các thử nghiệm. Thí dụ. Lịch thi đấu được gọi là 'FilteringFixture'. Nó bao gồm số lượng các bài kiểm tra như 'TestingFilteringById', hoặc 'TestingFilteringWithSpecialChars', vv Mỗi bài kiểm tra bao gồm các hành động, đó là đơn vị nguyên tử của bài kiểm tra của chúng tôi. Ví dụ về các hành động là - 'Mở ứng dụng (tham số)', 'OpenFilterDialog', v.v.

Chúng tôi đã có nhiều thử nghiệm, chứa nhiều hành động, đó là một mớ hỗn độn. Họ sử dụng API nội bộ của sản phẩm chúng tôi QA. Ngoài ra, chúng tôi bắt đầu điều tra một cách tiếp cận Tự động hóa mới - Tự động hóa giao diện người dùng thông qua Microsoft UI Automation (xin lỗi cho tautology). Vì vậy, sự cần thiết của một số công cụ "xuất khẩu" hoặc "phóng viên" trở nên nghiêm trọng đối với các nhà quản lý. Một số thời gian trước đây tôi đã có một nhiệm vụ để phát triển một số ứng dụng, có thể phân tích một DLL (chứa tất cả các đồ đạc, kiểm tra và hành động), và xuất cấu trúc của nó ở định dạng có thể đọc được của con người (TXT, HTML, CSV, ...) XML, bất kỳ loại nào khác). Nhưng, ngay sau đó, tôi đã đi nghỉ (2 tuần).

Nó xảy ra như vậy, mà bạn gái của tôi đã đi đến gia đình của mình cho đến khi nghỉ (cô ấy cũng có nó), và tôi vẫn ở nhà một mình. Suy nghĩ về những gì tôi làm tất cả thời gian này (2 tuần), tôi nhớ về điều đó "viết nhiệm vụ công cụ xuất khẩu" và bao lâu tôi đã có kế hoạch để bắt đầu học WPF. Vì vậy, tôi quyết định thực hiện nhiệm vụ của mình trong kỳ nghỉ, và cũng ăn mặc một ứng dụng cho WPF. Vào thời điểm đó tôi đã nghe về MVVM và tôi đã quyết định triển khai nó bằng MVVM thuần túy.

DLL có thể phân tích cú pháp DLL với fixrtures vv đã được viết khá nhanh (~ 1-2 ngày). Sau đó tôi đã bắt đầu với WPF, và bài viết này sẽ chỉ cho bạn cách nó kết thúc.

Tôi đã dành phần lớn kỳ nghỉ của mình (gần 8 ngày!), Cố gắng sắp xếp nó trong đầu và mã của tôi, và cuối cùng, nó được thực hiện (gần như). Bạn gái của tôi sẽ không tin những gì tôi đã làm tất cả thời gian này, nhưng tôi có một bằng chứng!

Chia sẻ bước giải pháp của tôi theo từng bước trong mã giả, để giúp người khác tránh các sự cố tương tự. Câu trả lời này trông giống như hướng dẫn =) (Thật sao?).Nếu bạn quan tâm đến những thứ phức tạp nhất trong khi học WPF từ đầu, tôi sẽ nói - làm cho nó thực sự là MVVM và f * g TreeView ràng buộc!

Nếu bạn muốn tệp được lưu trữ có giải pháp, tôi có thể cung cấp cho nó sau này một chút, ngay khi tôi đưa ra quyết định, điều đó đáng giá. Một hạn chế, tôi không chắc chắn tôi có thể chia sẻ MINT.dll, mà mang lại hành động, vì nó đã được phát triển bởi khách hàng của công ty chúng tôi. Nhưng tôi chỉ có thể loại bỏ nó, và chia sẻ các ứng dụng, mà có thể hiển thị thông tin về Đồ đạc và xét nghiệm, nhưng không phải về hành động.

Từ khoe khoang. Chỉ với một chút C#/WinForms/HTML nền và không có thực hành tôi đã có thể thực hiện phiên bản này của ứng dụng trong gần 1 tuần (và viết bài viết này). Vì vậy, không thể là có thể! Chỉ cần có một kỳ nghỉ như tôi, và dành nó cho WPF học tập!

Từng bước hướng dẫn (w/o file đính kèm nào)

lặp lại sơ lược về nhiệm vụ:

Một thời gian trước, tôi đã có một công việc để phát triển một ứng dụng, mà có thể phân tích một DLL (có chứa các đồ thử nghiệm, phương pháp thử nghiệm và hành động - các đơn vị của khung tự động dựa trên thử nghiệm đơn vị của chúng tôi) và xuất cấu trúc của nó ở định dạng có thể đọc được của con người (TXT, HTML, CSV, XML, bất kỳ loại nào khác). Tôi quyết định thực hiện nó bằng cách sử dụng WPF và MVVM thuần túy (cả hai đều là những điều hoàn toàn mới đối với tôi). Hai vấn đề khó khăn nhất đối với tôi đã trở thành chính MVVM, và sau đó MVVM ràng buộc với điều khiển TreeView. Tôi bỏ qua phần về phân chia MVVM, đó là một chủ đề cho bài viết riêng biệt. Các bước dưới đây liên quan đến TreeView theo cách MVVM.

  1. Không quá quan trọng: Tạo DLL có thể mở DLL với đơn vị xét nghiệm và phát hiện đồ đạc, phương pháp thử nghiệm và hành động (hơn mức nhỏ hơn của đơn vị kiểm tra, được viết trong công ty của chúng tôi) sử dụng phản ánh. Nếu bạn quan tâm đến cách nó đã được thực hiện, hãy xem tại đây: Parsing function/method content using Reflection
  2. Dll: Các lớp riêng biệt được tạo cho cả đồ đạc, kiểm tra và hành động (mô hình dữ liệu, mô hình thực thể?). Bạn nên suy nghĩ một mình, những gì sẽ là một mô hình thực thể cho cây của bạn. Ý tưởng chính - mỗi cấp độ cây phải được trưng ra bởi lớp thích hợp, với những thuộc tính này, giúp bạn đại diện cho mô hình trong cây (và, lý tưởng, sẽ diễn ra đúng trong MVVM của bạn, làm mô hình hoặc một phần của mô hình). Trong trường hợp của tôi, tôi đã quan tâm đến tên thực thể, danh sách trẻ em và số thứ tự. Số thứ tự là một số, đại diện cho thứ tự của một thực thể trong mã bên trong DLL. Nó giúp tôi hiển thị số thứ tự trong TreeView, vẫn không chắc chắn đó là cách tiếp cận đúng, nhưng nó hoạt động!
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, tôi cũng đã tạo ra một giao diện dưới đây, mà thực hiện tất cả các đối tượng. Giao diện như vậy có thể giúp bạn trong tương lai. Vẫn không chắc chắn, tôi có nên thêm vào đó Childrens thuộc tính của List<IMintEntity> loại hoặc một cái gì đó tương tự không?

public interface IMintEntity 
{ 
    string Name { get; } 
    bool IsParent { get; } 
    int OrdinalNumber { get; } 
} 
  1. DLL - xây dựng mô hình dữ liệu: DLL có một phương pháp mà mở DLL với đơn vị xét nghiệm và dữ liệu liệt kê. Trong quá trình liệt kê, nó xây dựng một mô hình dữ liệu như dưới đây. Ví dụ phương pháp thực được đưa ra, lõi phản chiếu + Mono.Reflection.dll được sử dụng, đừng nhầm lẫn với độ phức tạp. Tất cả những gì bạn cần - xem cách phương thức điền vào danh sách _fixtures với các thực thể.
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: tài sản công cộng Fixtures loại List<MintFixutre> được tạo ra để trở lại mô hình dữ liệu vừa tạo (Danh sách Đèn chiếu sáng, có chứa danh sách các bài kiểm tra, trong đó có chứa danh sách các hành động). Đây sẽ là nguồn ràng buộc của chúng tôi cho TreeView.
public List<MintFixutre> Fixtures 
{ 
    get { return _fixtures; } 
} 
  1. ViewModel của MainWindow (với TreeView bên trong): Chứa đối tượng/lớp từ DLL mà có thể phân tích cú pháp kiểm tra đơn vị DLL. Đồng thời tiết lộ Fixtures thuộc tính công khai từ loại tệp DLL thuộc loại List<MintFixutre>. Chúng tôi sẽ liên kết với nó từ XAML của MainWindow. Một cái gì đó như thế (giản thể):
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 của MainWindow - mẫu dữ liệu cài đặt: Bên trong một lưới điện, trong đó có TreeView, chúng tôi tạo <Grid.Resources> phần, trong đó chứa một bộ mẫu cho TreeViewItem s của chúng tôi. HierarchicalDataTemplate (Đồ đạc và Thử nghiệm) được sử dụng cho những người có vật phẩm con và DataTemplate được sử dụng cho các mục "lá" (Hành động). Đối với mỗi mẫu, chúng tôi chỉ định Nội dung của nó (văn bản, hình ảnh TreeViewItem, v.v.), ItemsSource (trong trường hợp mục này có con, ví dụ như Đồ đạc là {Binding Path=Tests}) và ItemTemplate (một lần nữa, chỉ trong trường hợp mục này có con, ở đây chúng tôi thiết lập mối liên kết giữa các mẫu - FixtureTemplate sử dụng TestTemplate cho con của nó, TestTemplate sử dụng ActionTemplate cho con của nó, mẫu hành động không sử dụng bất cứ điều gì, nó là một lá!). QUAN TRỌNG: Đừng quên, rằng để "liên kết" "một" mẫu "khác", mẫu "khác" phải được định nghĩa trong XAML phía trên "một"! (Chỉ liệt kê sai lầm ngớ ngẩn của riêng tôi :))

  2. XAML - Liên kết TreeView tuổi: Chúng tôi thiết lập TreeView với: liên kết với mô hình dữ liệu từ ViewModel (nhớ thuộc tính công cộng?) và chỉ với các mẫu đã chuẩn bị, đại diện cho nội dung, hình thức, nguồn dữ liệu và lồng các mục cây! Một lưu ý quan trọng hơn. Không xác định ViewModel của bạn là tài nguyên "tĩnh" bên trong XAML, như <Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>. Nếu bạn làm như vậy, bạn sẽ không thể thông báo cho nó về tài sản đã thay đổi. Tại sao? Tài nguyên tĩnh là tài nguyên tĩnh, nó khởi tạo tài nguyên, và sau đó vẫn giữ nguyên bất biến. Tôi có thể sai ở đây, nhưng đó là một trong những sai lầm của tôi. Vì vậy, đối TreeView sử dụng ItemsSource="{Binding Fixtures}" thay vì ItemsSource="{StaticResource myStaticViewModel}"

  3. ViewModel - ViewModelBase - Sở hữu Changed: Hầu như tất cả. Dừng lại! Khi người dùng mở một ứng dụng, sau đó ban đầu TreeView là trống của khóa học, như người dùng đã không mở bất kỳ DLL nào được nêu ra!Chúng ta phải chờ cho đến khi người dùng mở một DLL, và chỉ sau đó thực hiện ràng buộc. Nó được thực hiện thông qua sự kiện OnPropertyChanged. Để làm cho cuộc sống dễ dàng hơn, tất cả các ViewModels của tôi được kế thừa từ ViewModelBase, quyền hiển thị chức năng này cho tất cả ViewModel của tôi.

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 và chỉ huy. Người dùng nhấp vào một nút để mở tệp DLL chứa dữ liệu kiểm tra đơn vị. Khi chúng tôi sử dụng MVVM, sau đó nhấp được xử lý thông qua lệnh. Vào cuối của OpenDllExecuted handler OnPropertyChanged("Fixtures") được thực hiện, thông báo cho Tree, rằng thuộc tính, mà nó gắn kết với đã được thay đổi, và bây giờ là thời gian để làm mới chính nó. RelayCommand lớp trợ giúp có thể được lấy ví dụ từ there). BTW, như tôi biết, có một số thư viện helper và bộ công cụ tồn tại cái gì đó giống như điều đó xảy ra trong XAML:

  2. Và ViewModel - chỉ huy

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. Giao diện người dùng cuối cùng (nhưng không phải là cuối cùng đối với tôi, rất nhiều điều nên được thực hiện!). Mở rộng bộ công cụ WPF đã được sử dụng ở đâu đó: http://wpftoolkit.codeplex.com/

+0

wow, điều này là rất tốt – CodeFanatic

+0

Đây là một trong những câu trả lời tốt nhất mà tôi từng đọc! –