Thật không may, không có một ứng dụng ví dụ MVVM tuyệt vời nào làm được mọi thứ và có rất nhiều cách tiếp cận khác nhau để thực hiện. Trước tiên, bạn có thể muốn làm quen với một trong các khung ứng dụng ngoài kia (Prism là một lựa chọn hợp lý), vì chúng cung cấp cho bạn các công cụ tiện lợi như tiêm phụ thuộc, chỉ huy, tổng hợp sự kiện, v.v để dễ dàng thử các mẫu khác nhau phù hợp với bạn .
Bản phát hành lăng kính:
http://www.codeplex.com/CompổngWPF
Nó bao gồm một ứng dụng ví dụ khá hay (nhà giao dịch chứng khoán) cùng với rất nhiều ví dụ nhỏ hơn và cách thực hiện. Ít nhất đó là một minh chứng tốt về một số mẫu con phổ biến mà mọi người sử dụng để làm cho MVVM thực sự hoạt động. Họ có ví dụ cho cả CRUD và hộp thoại, tôi tin thế.
Lăng kính không nhất thiết phải cho mọi dự án, nhưng đó là một điều tốt để làm quen.
CRUD:
Phần này khá dễ, các ràng buộc hai chiều của WPF giúp dễ dàng chỉnh sửa hầu hết dữ liệu. Bí quyết thực sự là cung cấp một mô hình giúp dễ dàng thiết lập giao diện người dùng. Ít nhất bạn muốn đảm bảo rằng ViewModel (hoặc đối tượng kinh doanh) của bạn thực hiện INotifyPropertyChanged
để hỗ trợ ràng buộc và bạn có thể liên kết các thuộc tính thẳng với các điều khiển UI, nhưng bạn cũng có thể muốn thực hiện IDataErrorInfo
để xác thực. Thông thường, nếu bạn sử dụng một số loại giải pháp ORM, thiết lập CRUD là một cách nhanh chóng.
Bài viết này cho thấy các thao tác đơn giản:
http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx
Nó được xây dựng trên LinqToSql, nhưng điều đó không liên quan đến ví dụ - tất cả những gì quan trọng là các đối tượng kinh doanh của bạn triển khai INotifyPropertyChanged
(những lớp do LinqToSql tạo ra). MVVM không phải là điểm của ví dụ đó, nhưng tôi không nghĩ nó quan trọng trong trường hợp này.
Bài viết này chứng minh xác thực dữ liệu
http://bloss.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx
Một lần nữa, hầu hết các giải pháp ORM tạo ra các lớp đã triển khai IDataErrorInfo
và thường cung cấp một cơ chế để dễ dàng thêm các quy tắc xác thực tùy chỉnh.
Hầu hết thời gian bạn có thể lấy một đối tượng (mô hình) được tạo bởi một số ORM và bọc nó trong ViewModel giữ nó và ra lệnh lưu / xóa - và bạn đã sẵn sàng liên kết UI trực tiếp với các thuộc tính của mô hình.
Khung nhìn sẽ trông giống như thế này (ViewModel có một thuộc tính Item
chứa mô hình, giống như một lớp được tạo trong ORM):
<StackPanel>
<StackPanel DataContext=Item>
<TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<Button Command="{Binding SaveCommand}" />
<Button Command="{Binding CancelCommand}" />
</StackPanel>
Hộp thoại:
Hộp thoại và MVVM có một chút khó khăn. Tôi thích sử dụng hương vị của cách tiếp cận Người hòa giải với các hộp thoại, bạn có thể đọc thêm một chút về nó trong câu hỏi StackOverflow này:
ví dụ về hộp thoại WPVM MVVM
Cách tiếp cận thông thường của tôi, không phải là MVVM khá cổ điển, có thể được tóm tắt như sau:
Một lớp cơ sở cho hộp thoại ViewModel hiển thị các lệnh cho các hành động cam kết và hủy bỏ, một sự kiện để cho chế độ xem biết rằng một hộp thoại đã sẵn sàng để đóng và bất cứ điều gì khác bạn sẽ cần trong tất cả các hộp thoại của mình.
Một khung nhìn chung cho hộp thoại của bạn - đây có thể là một cửa sổ hoặc điều khiển loại lớp phủ "phương thức" tùy chỉnh. Về cốt lõi, nó là một trình dẫn nội dung mà chúng ta đổ viewmodel vào và nó xử lý hệ thống dây để đóng cửa sổ - ví dụ về thay đổi bối cảnh dữ liệu, bạn có thể kiểm tra xem ViewModel mới có được kế thừa từ lớp cơ sở của bạn không và nếu nó là, đăng ký vào sự kiện đóng có liên quan (trình xử lý sẽ gán kết quả hộp thoại). Nếu bạn cung cấp chức năng đóng phổ quát thay thế (ví dụ nút X), bạn cũng nên đảm bảo chạy lệnh đóng có liên quan trên ViewModel.
Ở đâu đó bạn cần cung cấp các mẫu dữ liệu cho ViewModels của mình, chúng có thể rất đơn giản đặc biệt vì bạn có thể có chế độ xem cho mỗi hộp thoại được gói gọn trong một điều khiển riêng. Mẫu dữ liệu mặc định cho ViewModel sau đó sẽ trông giống như thế này:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
Chế độ xem hộp thoại cần có quyền truy cập vào các giao diện này, vì nếu không, nó sẽ không biết cách hiển thị ViewModel, ngoài giao diện người dùng hộp thoại được chia sẻ, nội dung của nó về cơ bản là:
<ContentControl Content="{Binding}" />
Mẫu dữ liệu ẩn sẽ ánh xạ khung nhìn tới mô hình, nhưng ai khởi chạy nó?
Đây là phần không phải là mvvm. Một cách để làm điều đó là sử dụng một sự kiện toàn cầu. Những gì tôi nghĩ là một điều tốt hơn để làm là sử dụng một thiết lập loại trình tổng hợp sự kiện, được cung cấp thông qua tiêm phụ thuộc - theo cách này sự kiện là toàn cầu đối với một container chứ không phải toàn bộ ứng dụng. Prism sử dụng khung thống nhất cho ngữ nghĩa container và nội dung phụ thuộc, và nói chung tôi thích Unity khá nhiều.
Thông thường, sẽ hợp lý khi cửa sổ gốc đăng ký sự kiện này - nó có thể mở hộp thoại và đặt bối cảnh dữ liệu của nó thành ViewModel được truyền vào với một sự kiện được nêu ra.
Thiết lập điều này theo cách này cho phép ViewModels yêu cầu ứng dụng mở hộp thoại và trả lời các hành động của người dùng ở đó mà không biết gì về giao diện người dùng, do đó, phần lớn MVVM-ness vẫn hoàn tất.
Tuy nhiên, có những lúc, UI phải nâng các hộp thoại lên, điều này có thể khiến mọi thứ trở nên phức tạp hơn một chút. Xem xét ví dụ, nếu vị trí hộp thoại phụ thuộc vào vị trí của nút mở nó. Trong trường hợp này, bạn cần có một số thông tin cụ thể về giao diện người dùng khi bạn yêu cầu mở hộp thoại. Tôi thường tạo một lớp riêng chứa ViewModel và một số thông tin UI có liên quan. Thật không may một số khớp nối dường như không thể tránh khỏi ở đó.
Mã giả của trình xử lý nút làm tăng hộp thoại cần dữ liệu vị trí phần tử:
ButtonClickHandler(sender, args){
var vm = DataContext as ISomeDialogProvider; // check for null
var ui_vm = new ViewModelContainer();
// assign margin, width, or anything else that your custom dialog might require
...
ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
// raise the dialog show event
}
Khung nhìn hộp thoại sẽ liên kết với dữ liệu vị trí và chuyển ViewModel được chứa vào bên trong ContentControl
. Bản thân ViewModel vẫn không biết gì về UI.
Nói chung, tôi không sử dụng thuộc tính DialogResult
return của ShowDialog()
phương thức hoặc hy vọng luồng sẽ bị chặn cho đến khi hộp thoại được đóng lại. Một hộp thoại phương thức không chuẩn không phải lúc nào cũng hoạt động như vậy và trong môi trường tổng hợp, bạn thường không thực sự muốn một trình xử lý sự kiện chặn như thế. Tôi thích để ViewModels giải quyết vấn đề này - người tạo ViewModel có thể đăng ký các sự kiện liên quan của nó, đặt phương thức cam kết / hủy, v.v., do đó không cần phải dựa vào cơ chế UI này.
Vì vậy, thay vì dòng chảy này:
// in code behind
var result = somedialog.ShowDialog();
if (result == ...
Tôi sử dụng:
// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container
Tôi thích nó theo cách này bởi vì hầu hết các hộp thoại của tôi là các điều khiển giả chế độ không chặn và thực hiện theo cách này có vẻ đơn giản hơn so với làm việc xung quanh nó. Dễ dàng để kiểm tra đơn vị là tốt.