C# - C Sharp: Model-View-ViewModel (MVVM)


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

Trong bài viết này

  1. Mẫu MVVM
  2. Kết nối các view model với các view
  3. Tạo view model theo cách khai báo
  4. Tạo view model theo chương trình
  5. Cập nhật view để đáp ứng với các thay đổi trong model hoặc view model cơ bản
  6. Framework MVVM
  7. Tương tác giao diện người dùng bằng lệnh và hành vi
  8. Thực hiện các lệnh
  9. Gọi lệnh từ view
  10. Thực hiện các hành vi
  11. Gọi hành vi từ view
  12. Tổng kết

 

Trải nghiệm của nhà phát triển .NET MAUI thường liên quan đến việc tạo giao diện người dùng trong XAML, sau đó thêm mã phía sau hoạt động trên giao diện người dùng. Các vấn đề bảo trì phức tạp có thể phát sinh khi ứng dụng được sửa đổi và phát triển về quy mô cũng như phạm vi. Những vấn đề này bao gồm sự kết hợp chặt chẽ giữa các control UI và logic nghiệp vụ, làm tăng chi phí thực hiện sửa đổi UI và khó khăn trong việc kiểm tra đơn vị mã đó.

Pattern MVVM giúp tách biệt rõ ràng logic trình bày và nghiệp vụ của ứng dụng khỏi giao diện người dùng (UI) của nó. Việc duy trì sự tách biệt rõ ràng giữa logic ứng dụng và giao diện người dùng giúp giải quyết nhiều vấn đề phát triển và giúp ứng dụng dễ dàng kiểm tra, bảo trì và phát triển hơn. Nó cũng có thể cải thiện đáng kể cơ hội sử dụng lại mã và cho phép các nhà phát triển và nhà thiết kế giao diện người dùng cộng tác dễ dàng hơn khi phát triển các phần tương ứng của ứng dụng.

Pattern (mẫu) MVVM

Có ba thành phần cốt lõi trong mẫu MVVM: Model, View và View Model. Mỗi thành phần phục vụ một mục đích riêng biệt. Sơ đồ dưới đây cho thấy mối quan hệ giữa ba thành phần.

Pattern (Mẫu) MVVM

Ngoài việc hiểu rõ nhiệm vụ của từng thành phần, điều quan trọng là phải hiểu cách chúng tương tác. Ở mức cao, view "biết về" view model và view model "biết về" model, nhưng model không biết về view model và view model không biết về view. Do đó, view model tách biệt view khỏi model và cho phép model phát triển độc lập với view.

Lợi ích của việc sử dụng mẫu MVVM như sau:

  • Nếu việc triển khai mô hình hiện tại gói gọn logic nghiệp vụ hiện có thì việc thay đổi nó có thể khó khăn hoặc rủi ro. Trong trường hợp này, view model hoạt động như một bộ điều hợp cho các lớp model và ngăn bạn thực hiện các thay đổi lớn đối với code model.
  • Nhà phát triển có thể tạo các bài kiểm thử đơn vị cho view model và model mà không cần sử dụng view. Các bài kiểm tra đơn vị cho view model có thể thực hiện chính xác chức năng tương tự như được sử dụng bởi view.
  • Giao diện người dùng ứng dụng có thể được thiết kế lại mà không cần chạm vào view model và code model, miễn là view được triển khai hoàn toàn bằng XAML hoặc C#. Do đó, phiên bản mới của view sẽ hoạt động với view model hiện có.
  • Nhà thiết kế và nhà phát triển có thể làm việc độc lập và đồng thời trên các thành phần của họ trong quá trình phát triển. Nhà thiết kế có thể tập trung vào view, trong khi nhà phát triển có thể làm việc trên view model và các thành phần model.

Chìa khóa để sử dụng MVVM một cách hiệu quả nằm ở việc hiểu cách đưa mã ứng dụng vào các lớp chính xác và cách các lớp tương tác. Các phần dưới đây sẽ thảo luận về nhiệm vụ của từng lớp trong mẫu MVVM.

View

View chịu trách nhiệm xác định cấu trúc, bố cục và hình thức của những gì người dùng nhìn thấy trên màn hình. Lý tưởng nhất là mỗi view được xác định trong XAML, với code-behind giới hạn không chứa logic nghiệp vụ. Tuy nhiên, trong một số trường hợp, code-behind có thể chứa logic giao diện người dùng thực hiện hành vi trực quan khó diễn đạt trong XAML, chẳng hạn như hoạt ảnh.

Trong ứng dụng .NET MAUI, một view thường là lớp ContentPage được thừa kế hoặc lớp ContentView được thừa kế. Tuy nhiên, view cũng có thể được biểu thị bằng mẫu dữ liệu, mẫu này chỉ định các thành phần giao diện người dùng được sử dụng để thể hiện trực quan một đối tượng khi nó được hiển thị. Mẫu dữ liệu dưới dạng view không có bất kỳ code-behind nào và được thiết kế để liên kết với một loại view model cụ thể.

Mẹo

Tránh bật và tắt các thành phần giao diện người dùng ở phần code-behind.

Đảm bảo rằng các view model chịu trách nhiệm xác định các thay đổi trạng thái logic ảnh hưởng đến một số khía cạnh hiển thị của view, chẳng hạn như lệnh có sẵn hay không hoặc dấu hiệu cho thấy một thao tác đang chờ xử lý. Do đó, hãy bật và tắt các thành phần giao diện người dùng bằng cách liên kết các property của view model, thay vì bật và tắt chúng ở code-behind.

Có một số tùy chọn để thực thi mã trên view model để phản hồi các tương tác trên view, chẳng hạn như nhấp vào nút hoặc chọn mục. Nếu một control hỗ trợ các lệnh, thì property Command của control đó có thể được liên kết dữ liệu với property ICommand trên view model. Khi lệnh của control được gọi, mã trong view model sẽ được thực thi. Ngoài các lệnh, các hành vi có thể được gắn vào một đối tượng trong view và có thể lắng nghe lệnh được gọi hoặc sự kiện được nêu ra. Để đáp lại, hành vi sau đó có thể gọi ICommand trên view model hoặc một phương thức trên view model.

View Model

View Model triển khai các property và lệnh mà view có thể liên kết dữ liệu và thông báo cho view về bất kỳ thay đổi trạng thái nào thông qua các sự kiện thông báo thay đổi. Các property và lệnh mà view model cung cấp xác định chức năng được giao diện người dùng cung cấp, nhưng view xác định cách hiển thị chức năng đó.

Mẹo

Giữ giao diện người dùng phản hồi nhanh với các hoạt động không đồng bộ.

Các ứng dụng đa nền tảng phải giữ cho luồng giao diện người dùng không bị chặn để cải thiện nhận thức của người dùng về hiệu suất. Do đó, trong view model, hãy sử dụng các phương thức không đồng bộ cho các hoạt động I/O và nâng cao các sự kiện để thông báo không đồng bộ cho các view về các thay đổi property.

View model cũng chịu trách nhiệm điều phối các tương tác của view với bất kỳ lớp model nào được yêu cầu. Thông thường có mối quan hệ một-nhiều giữa view model và các lớp model. View model có thể chọn hiển thị trực tiếp các lớp model cho view để các control trong view có thể liên kết dữ liệu trực tiếp với chúng. Trong trường hợp này, các lớp model sẽ cần được thiết kế để hỗ trợ các sự kiện thông báo thay đổi và liên kết dữ liệu.

Mỗi view model cung cấp dữ liệu từ một model ở dạng mà view có thể dễ dàng sử dụng. Để thực hiện điều này, view model đôi khi thực hiện chuyển đổi dữ liệu. Việc đặt chuyển đổi dữ liệu này vào view model là một ý tưởng hay vì nó cung cấp các property mà view có thể liên kết. Ví dụ: view model có thể kết hợp các giá trị của hai property để giúp view hiển thị dễ dàng hơn.

Mẹo

Tập trung chuyển đổi dữ liệu trong một lớp chuyển đổi.

Bạn cũng có thể sử dụng trình chuyển đổi làm lớp chuyển đổi dữ liệu riêng biệt nằm giữa view model và view. Điều này có thể cần thiết, chẳng hạn như khi dữ liệu yêu cầu định dạng đặc biệt mà view model không cung cấp.

Để view model tham gia liên kết dữ liệu hai chiều với view, các property của nó phải đưa ra sự kiện PropertyChanged. Các view model đáp ứng yêu cầu này bằng cách triển khai interface INotifyPropertyChanged và đưa ra sự kiện PropertyChanged khi property được thay đổi.

Đối với các collection, thì view thân thiện ObservableCollection<T> sẽ được cung cấp. Collection triển khai thông báo thay đổi collection, giúp nhà phát triển không phải triển khai interface INotifyCollectionChanged trên các collection.

Model

Các lớp model là các lớp không trực quan đóng gói dữ liệu của ứng dụng. Do đó, model có thể được coi là đại diện cho mô hình miền của ứng dụng, thường bao gồm mô hình dữ liệu cùng với logic nghiệp vụ và xác thực. Ví dụ về các đối tượng model bao gồm các đối tượng truyền dữ liệu (DTO), Đối tượng CLR cũ đơn giản (POCO) cũng như các đối tượng proxy và thực thể được tạo.

Các lớp model thường được sử dụng cùng với các dịch vụ hoặc kho lưu trữ đóng gói quyền truy cập dữ liệu và bộ nhớ đệm.

Kết nối các view model với các view

Các view model có thể được kết nối với các view bằng cách sử dụng khả năng liên kết dữ liệu của .NET MAUI. Có nhiều cách tiếp cận có thể được sử dụng để xây dựng các view và các view model cũng như liên kết chúng trong thời gian chạy. Các cách tiếp cận này chia thành hai loại, được gọi là thành phần view đầu tiên và thành phần view model đầu tiên. Việc lựa chọn giữa bố cục view đầu tiên và bố cục view đầu tiên của model là một vấn đề về mức độ ưu tiên và độ phức tạp. Tuy nhiên, tất cả các cách tiếp cận đều có chung một mục đích, đó là view có view model được gán cho property BindingContext của nó.

Với view đầu tiên, ứng dụng về mặt khái niệm bao gồm các view kết nối với các view model mà chúng phụ thuộc vào. Lợi ích chính của phương pháp này là giúp dễ dàng xây dựng các ứng dụng có thể kiểm thử đơn vị, được ghép nối lỏng lẻo vì các view model không phụ thuộc vào chính các view. Bạn cũng có thể dễ dàng hiểu cấu trúc của ứng dụng bằng cách tuân theo cấu trúc trực quan của nó, thay vì phải theo dõi quá trình thực thi mã để hiểu cách các lớp được tạo và liên kết. Ngoài ra, cấu trúc view đầu tiên phù hợp với hệ thống điều hướng của Microsoft Maui. Hệ thống này chịu trách nhiệm xây dựng các trang khi điều hướng xảy ra, điều này làm cho cấu trúc view model đầu tiên trở nên phức tạp và không khớp với nền tảng.

Với thành phần đầu tiên của view model, ứng dụng về mặt khái niệm bao gồm các view model, với một dịch vụ chịu trách nhiệm định vị view cho view model. Thành phần đầu tiên của view model mang lại cảm giác tự nhiên hơn đối với một số nhà phát triển, vì việc tạo view có thể được trừu tượng hóa, cho phép họ tập trung vào cấu trúc logic phi giao diện người dùng của ứng dụng. Ngoài ra, nó cho phép các view model được tạo bởi các view model khác. Tuy nhiên, cách tiếp cận này thường phức tạp và có thể khó hiểu cách tạo và liên kết các phần khác nhau của ứng dung.

Mẹo

Giữ view model và view độc lập.

Việc ràng buộc các view với một property trong nguồn dữ liệu phải là phần phụ thuộc chính của view vào view model tương ứng của nó. Cụ thể, không tham chiếu các loại view, chẳng hạn như Button và ListView, từ view model. Bằng cách tuân theo các nguyên tắc được nêu ở đây, các view model có thể được kiểm tra một cách độc lập, do đó làm giảm khả năng xảy ra lỗi phần mềm bằng cách giới hạn phạm vi.

Các phần tiếp theo sẽ thảo luận về các cách tiếp cận chính để kết nối các view model với các view.

Tạo view model theo cách khai báo

Cách tiếp cận đơn giản nhất là để view khai báo view model tương ứng của nó trong XAML. Khi view được xây dựng, đối tượng view model tương ứng cũng sẽ được xây dựng. Cách tiếp cận này được thể hiện trong ví dụ mã sau:

<ContentPage xmlns:local="clr-namespace:eShop">
    <ContentPage.BindingContext>
        <local:LoginViewModel />
    </ContentPage.BindingContext>
    <!-- Omitted for brevity... -->
</ContentPage>

Khi ContentPage được tạo, một phiên bản của view LoginViewModel sẽ được tự động tạo và đặt làm view BindingContext.

Việc xây dựng khai báo và gán view model theo view có ưu điểm là đơn giản nhưng có nhược điểm là nó yêu cầu một hàm tạo mặc định (không có tham số) trong view model.

Tạo view model theo chương trình

Một view có thể có mã trong tệp mã phía sau, dẫn đến view model được gán cho property BindingContext của nó. Điều này thường được thực hiện trong hàm tạo của view, như trong ví dụ về mã sau:

public LoginView()
{
    InitializeComponent();
    BindingContext = new LoginViewModel(navigationService);
}

Việc xây dựng và gán view model theo chương trình trong code-behind của view có ưu điểm là đơn giản. Tuy nhiên, nhược điểm chính của phương pháp này là view cần cung cấp cho view model mọi phụ thuộc cần thiết. Việc sử dụng vùng chứa DI có thể giúp duy trì khớp nối lỏng lẻo giữa view và view model.

Cập nhật view để đáp ứng với các thay đổi trong model hoặc view model cơ bản

Tất cả các lớp model và view model mà một view có thể truy cập là phải triển khai interface INotifyPropertyChanged. Việc triển khai interface này trong view model hoặc lớp model cho phép lớp cung cấp thông báo thay đổi cho bất kỳ điều khiển ràng buộc dữ liệu nào trong view khi giá trị property cơ bản thay đổi.

Ứng dụng phải được thiết kế để sử dụng đúng thông báo thay đổi property bằng cách đáp ứng các yêu cầu sau:

  • Luôn đưa ra một sự kiện PropertyChanged nếu giá trị của property public thay đổi. Đừng cho rằng việc nâng cao sự kiện PropertyChanged có thể bị bỏ qua vì đã biết về cách xảy ra ràng buộc XAML.
  • Luôn đưa ra một sự kiện PropertyChanged cho bất kỳ property được tính toán nào có giá trị được các property khác sử dụng trong model hoặc view model.
  • Luôn đưa ra sự kiện PropertyChanged ở cuối phương thức làm thay đổi property hoặc khi đối tượng được biết là ở trạng thái an toàn. Việc đưa ra sự kiện sẽ làm gián đoạn hoạt động bằng cách gọi các trình xử lý sự kiện một cách đồng bộ. Nếu điều này xảy ra ở giữa một thao tác, nó có thể khiến đối tượng phải thực hiện các chức năng gọi lại khi nó ở trạng thái không an toàn, được cập nhật một phần. Ngoài ra, các sự kiện có thể kích hoạt các thay đổi xếp tầng PropertyChanged. Các thay đổi xếp tầng thường yêu cầu phải hoàn tất cập nhật trước khi thực hiện thay đổi xếp tầng một cách an toàn.
  • Không bao giờ đưa ra sự kiện PropertyChanged nếu property không thay đổi. Điều này có nghĩa là bạn phải so sánh giá trị cũ và mới trước khi đưa ra sự kiện PropertyChanged.
  • Không bao giờ đưa ra sự kiện PropertyChanged trong hàm tạo của view model nếu bạn đang khởi tạo một property. Các điều khiển ràng buộc dữ liệu trong view sẽ không được đăng ký nhận thông báo thay đổi tại thời điểm này.
  • Không bao giờ đưa ra nhiều sự kiện PropertyChanged có cùng đối số tên property trong một lệnh gọi đồng bộ duy nhất của phương thức public của một lớp. Ví dụ: với một property NumberOfItems có kho dự phòng là trường _numberOfItems, thì nếu một phương thức tăng _numberOfItems năm mươi lần trong khi thực hiện vòng lặp thì phương thức đó chỉ nên đưa ra thông báo thay đổi property trên property NumberOfItems một lần sau khi tất cả công việc đã hoàn tất. Đối với các phương thức không đồng bộ, hãy tăng sự kiện PropertyChanged cho tên property nhất định trong mỗi phân đoạn đồng bộ của chuỗi tiếp tục không đồng bộ.

Một cách đơn giản để cung cấp chức năng này là tạo một phần mở rộng của lớp BindableObjectlớp. Trong ví dụ này, lớp ExtendedBindableObject cung cấp thông báo thay đổi, được hiển thị trong ví dụ mã sau:

public abstract class ExtendedBindableObject : BindableObject
{
    public void RaisePropertyChanged<T>(Expression<Func<T>> property)
    {
        var name = GetMemberInfo(property).Name;
        OnPropertyChanged(name);
    }

    private MemberInfo GetMemberInfo(Expression expression)
    {
        // Omitted for brevity ...
    }
}

Lớp BindableObject của .NET MAUI triển khai interface INotifyPropertyChanged và cung cấp một phương thức OnPropertyChanged. Lớp ExtendedBindableObject cung cấp phương thức RaisePropertyChanged để gọi thông báo thay đổi property và khi làm như vậy sẽ sử dụng chức năng do lớp BindableObject cung cấp.

Xem các lớp model sau đó có thể xuất phát từ lớp ExtendedBindableObject đó. Do đó, mỗi lớp view model sử dụng phương thức RaisePropertyChanged trong lớp ExtendedBindableObject để cung cấp thông báo thay đổi property. Ví dụ về mã sau đây cho thấy cách ứng dụng đa nền tảng eShopOnContainers gọi thông báo thay đổi property bằng cách sử dụng biểu thức lambda:

public bool IsLogin
{
    get => _isLogin;
    set
    {
        _isLogin = value;
        RaisePropertyChanged(() => IsLogin);
    }
}

Việc sử dụng biểu thức lambda theo cách này sẽ tốn một ít chi phí hiệu năng vì biểu thức lambda phải được đánh giá cho mỗi lệnh gọi. Mặc dù chi phí hiệu suất nhỏ và thường không ảnh hưởng đến ứng dụng nhưng chi phí có thể tích lũy khi có nhiều thông báo thay đổi. Tuy nhiên, lợi ích của phương pháp này là nó cung cấp sự hỗ trợ tái cấu trúc và an toàn kiểu thời gian biên dịch khi đổi tên các property.

Framework MVVM

MVVM được thiết lập tốt trong .NET và cộng đồng đã tạo ra nhiều framework giúp tạo điều kiện thuận lợi cho sự phát triển này. Mỗi framework cung cấp một bộ tính năng khác nhau, nhưng tiêu chuẩn để chúng cung cấp một view model chung với việc triển khai interface INotifyPropertyChanged là tiêu chuẩn. Các tính năng bổ sung của framework MVVM bao gồm các lệnh tùy chỉnh, trình trợ giúp điều hướng, các thành phần định vị dịch vụ/DI và tích hợp nền tảng giao diện người dùng. Mặc dù không cần thiết phải sử dụng các framework này nhưng chúng có thể tăng tốc và tiêu chuẩn hóa quá trình phát triển của bạn. Ứng dụng đa nền tảng eShopOnContainers sử dụng Bộ công cụ MVVM cộng đồng .NET. Khi chọn một framework, bạn nên xem xét nhu cầu của ứng dụng và điểm mạnh của nhóm bạn. Danh sách bên dưới bao gồm một số framework MVVM phổ biến hơn cho .NET MAUI.

Tương tác giao diện người dùng bằng lệnh và hành vi

Trong các ứng dụng đa nền tảng, các hành động thường được gọi để phản hồi lại hành động của người dùng, chẳng hạn như thao tác bấm nút, có thể được triển khai bằng cách tạo trình xử lý sự kiện trong file code-behind. Tuy nhiên, trong mẫu MVVM, nhiệm vụ thực hiện hành động thuộc về view model và nên tránh đặt mã ở code-behind.

Lệnh cung cấp một cách thuận tiện để thể hiện các hành động có thể bị ràng buộc với các điều khiển trong giao diện người dùng. Chúng đóng gói mã thực hiện hành động và giúp tách mã đó khỏi biểu diễn trực quan của nó trong view. Bằng cách này, các view model của bạn trở nên linh hoạt hơn với các nền tảng mới vì chúng không phụ thuộc trực tiếp vào các sự kiện do framework giao diện người dùng của nền tảng cung cấp. .NET MAUI bao gồm các control có thể được kết nối khai báo với một lệnh và các control này sẽ gọi lệnh khi người dùng tương tác với control.

Các hành vi cũng cho phép các control được kết nối khai báo với một lệnh. Tuy nhiên, hành vi có thể được sử dụng để gọi một hành động liên quan đến một loạt sự kiện do control đưa ra. Do đó, hành vi giải quyết nhiều tình huống giống như điều khiển kích hoạt bằng lệnh, đồng thời mang lại mức độ linh hoạt và kiểm soát cao hơn. Ngoài ra, các hành vi cũng có thể được sử dụng để liên kết các đối tượng hoặc phương thức lệnh với các control không được thiết kế đặc biệt để tương tác với các lệnh.

Thực hiện các lệnh

Các view model thường hiển thị các property public để liên kết từ view triển khai interface ICommand. Nhiều control và cử chỉ .NET MAUI cung cấp một property là Command, có thể là dữ liệu được liên kết với một đối tượng ICommand do view model cung cấp. Control Button là một trong những control được sử dụng phổ biến nhất, cung cấp property lệnh thực thi khi nhấp vào nút.

Ghi chú

Mặc dù có thể hiển thị cách triển khai thực tế của interface ICommand mà view model của bạn sử dụng (ví dụ: Command<T> hoặc RelayCommand), nhưng bạn nên hiển thị công khai các lệnh của mình dưới dạng ICommand. Bằng cách này, nếu sau này bạn cần thay đổi cách triển khai, bạn có thể dễ dàng hoán đổi nó.

Interface ICommand định nghĩa phương thức Execute đóng gói chính hoạt động đó, phương thức CanExecute cho biết liệu lệnh có thể được gọi hay không và sự kiện CanExecuteChanged xảy ra khi có thay đổi ảnh hưởng đến việc lệnh có được thực thi hay không.

.NET MAUI được cung cấp là các lớp Command và Command<T> triển khai interface ICommand, trong đó T là đối số kiểu cho Execute và CanExecuteCommand và Command<T> là những triển khai cơ bản cung cấp bộ chức năng tối thiểu cần thiết cho interface ICommand.

Ghi chú

Nhiều framework MVVM cung cấp nhiều triển khai interface ICommand giàu tính năng hơn.

Hàm khởi tạo Command hoặc Command<T> yêu cầu một đối tượng callback Action được gọi khi phương thức ICommand.Execute được gọi. Phương thức CanExecute là một tham số hàm tạo tùy chọn và là một Func trả về một bool.

Ứng dụng đa nền tảng eShopOnContainers sử dụng RelayCommand và AsyncRelayCommand. Lợi ích chính của các ứng dụng hiện đại là AsyncRelayCommand cung cấp chức năng tốt hơn cho các hoạt động không đồng bộ.

Đoạn mã sau đây cho thấy cách một phiên bản Command đại diện cho lệnh đăng ký được xây dựng bằng cách chỉ định một delegate cho phương thức view model Register:

public ICommand RegisterCommand { get; }

Lệnh được hiển thị trong view thông qua property trả về tham chiếu đến file ICommand. Khi phương thức Execute được gọi trên đối tượng Command, nó chỉ chuyển tiếp lệnh gọi đến phương thức trong view model thông qua delegate đã được chỉ định trong hàm tạo Command. Một phương thức không đồng bộ có thể được gọi bằng lệnh bằng cách sử dụng từ khóa async và chờ khi chỉ định delegate Execute của lệnh. Điều này cho biết rằng callback là một Task và nên được chờ đợi. Ví dụ: đoạn mã sau cho thấy cách một phiên bản ICommand đại diện cho lệnh đăng nhập được xây dựng bằng cách chỉ định một delegate cho phương thức view model SignInAsync:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

Các tham số có thể được truyền cho các hành động Execute và CanExecute bằng cách sử dụng lớp AsyncRelayCommand<T> để khởi tạo lệnh. Ví dụ: đoạn mã sau cho biết cách AsyncRelayCommand<T> sử dụng một thể hiện để chỉ ra rằng phương thức NavigateAsync sẽ yêu cầu một đối số kiểu chuỗi:

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

Trong cả hai lớp RelayCommand và RelayCommand<T>, việc ủy ​​quyền cho phương thức CanExecute trong mỗi hàm tạo là tùy chọn. Nếu delegate không được chỉ định, kết quả Command sẽ trả về true cho CanExecute. Tuy nhiên, view model có thể chỉ ra sự thay đổi trạng thái CanExecute của lệnh bằng cách gọi phương thức ChangeCanExecute trên đối tượng Command. Điều này khiến sự kiện CanExecuteChanged được nâng lên. Sau đó, mọi control được liên kết với lệnh sẽ cập nhật trạng thái đã bật của chúng để phản ánh tính khả dụng của lệnh liên kết dữ liệu.

Gọi lệnh từ view

Ví dụ mã sau đây cho thấy cách một Grid trong LoginView liên kết với RegisterCommand trong lớp LoginViewModel bằng cách sử dụng một thể hiện TapGestureRecognizer:

<Grid Grid.Column="1" HorizontalOptions="Center">
    <Label Text="REGISTER" TextColor="Gray"/>
    <Grid.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
    </Grid.GestureRecognizers>
</Grid>

Một tham số lệnh cũng có thể được xác định tùy ý bằng property CommandParameter. Đây là loại đối số dự kiến ​​được chỉ định trong phương thức mục tiêu Execute và CanExecute. TapGestureRecognizer sẽ tự động gọi lệnh đích khi người dùng tương tác với control đính kèm, nếu được cung cấp, sẽ được chuyển làm đối số CommandParameter cho delegate Execute của lệnh.

Thực hiện các hành vi

Hành vi cho phép thêm chức năng vào control mà không cần phải phân lớp chúng. Thay vào đó, chức năng này được triển khai trong một lớp hành vi và được gắn vào control như thể nó là một phần của chính control đó. Hành vi cho phép bạn triển khai mã mà bạn thường phải viết dưới dạng code-behind vì nó tương tác trực tiếp với API của control, theo cách mà nó có thể được gắn chính xác vào control và được đóng gói để sử dụng lại trên nhiều hơn một view hoặc ứng dụng. Trong bối cảnh MVVM, hành vi là một cách tiếp cận hữu ích để kết nối các control với lệnh.

Hành vi được gắn với control thông qua các property được đính kèm được gọi là hành vi được đính kèm. Sau đó, hành vi có thể sử dụng API hiển thị của phần tử được gắn vào để thêm chức năng cho control đó hoặc các control khác trong cây trực quan của view.

Hành vi .NET MAUI là một lớp bắt nguồn từ lớp Behaviorhoặc Behavior<T>, trong đó T là loại control mà hành vi đó sẽ áp dụng. Các lớp này cung cấp các phương thức OnAttachedTo và OnDetachingFrom cần được ghi đè để cung cấp logic sẽ được thực thi khi hành vi được gắn vào và tách khỏi các control.

Trong ứng dụng đa nền tảng eShopOnContainers, lớp BindableBehavior<T> thừa kế từ lớp Behavior<T>. Mục đích của lớp BindableBehavior<T> là cung cấp một lớp cơ sở cho các hành vi .NET MAUI để yêu cầu BindingContext của hành vi đó phải được đặt thành control đính kèm.

Lớp BindableBehavior<T> cung cấp phương thức OnAttachedTo có thể ghi đè để thiết lập hành vi BindingContext và phương thức OnDetachingFrom có thể ghi đè để dọn sạch BindingContext.

Ứng dụng đa nền tảng eShopOnContainers bao gồm lớp EventToCommandBehavior được cung cấp bởi bộ công cụ Cộng đồng MAUI. EventToCommandBehavior thực hiện một lệnh để đáp lại một sự kiện xảy ra. Lớp này thừa kế từ lớp BaseBehavior<View> để hành vi có thể liên kết và thực thi ICommand được chỉ định bởi property Command khi hành vi được sử dụng. Ví dụ mã sau đây cho thấy lớp EventToCommandBehavior:

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

Các phương thức OnAttachedTo và OnDetachingFrom được sử dụng để đăng ký và hủy đăng ký một trình xử lý sự kiện cho sự kiện được xác định trong property EventName. Sau đó, khi sự kiện xảy ra, phương thức OnTriggerHandled sẽ được gọi và thực thi lệnh.

Ưu điểm của việc sử dụng lệnh EventToCommandBehavior để thực thi lệnh khi sự kiện xảy ra là các lệnh có thể được liên kết với các control không được thiết kế để tương tác với lệnh. Ngoài ra, điều này sẽ di chuyển mã xử lý sự kiện đến các view model, nơi nó có thể được kiểm tra đơn vị.

Gọi hành vi từ view

EventToCommandBehavior đặc biệt hữu ích khi đính kèm lệnh vào control không hỗ trợ lệnh. Ví dụ: loginView sử dụng EventToCommandBehavior để thực thi ValidateCommand khi người dùng thay đổi giá trị mật khẩu như trong đoạn mã sau:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

Trong thời gian chạy, EventToCommandBehavior sẽ phản hồi tương tác với Entry. Khi người dùng nhập vào trường Entry, thì sự kiện TextChanged sẽ kích hoạt, sự kiện này sẽ thực thi ValidateCommand trong file LoginViewModel. Theo mặc định, các đối số sự kiện cho sự kiện này được truyền cho lệnh. Nếu cần, property EventArgsConverter có thể được sử dụng để chuyển đổi giá trị EventArgs do sự kiện cung cấp thành giá trị mà lệnh mong đợi làm đầu vào.

Tổng kết

Mẫu Model-View-ViewModel (MVVM) giúp tách biệt rõ ràng logic trình bày và nghiệp vụ của ứng dụng khỏi giao diện người dùng (UI). Việc duy trì sự tách biệt rõ ràng giữa logic ứng dụng và giao diện người dùng giúp giải quyết nhiều vấn đề phát triển và giúp ứng dụng dễ dàng kiểm tra, bảo trì và phát triển hơn. Nó cũng có thể cải thiện đáng kể cơ hội sử dụng lại mã và cho phép các nhà phát triển và các nhà thiết kế giao diện người dùng cộng tác dễ dàng hơn khi phát triển các phần tương ứng của ứng dung.

Bằng cách sử dụng mẫu MVVM, giao diện người dùng của ứng dụng cũng như logic nghiệp vụ và bản trình bày cơ bản được tách thành ba lớp riêng biệt: view dùng để đóng gói giao diện người dùng và logic giao diện người dùng; view model dùng để đóng gói trạng thái và logic trình bày; và model dùng để đóng gói dữ liệu và logic nghiệp vụ của ứng dụng.

» Tiếp: Tổng quan
« Trước: Bảng (Table) trong WPF
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!