MVVM trong WPF - Làm cách nào để cảnh báo ViewModel về những thay đổi trong Model… hay tôi nên làm?


112

Tôi đang xem qua một số bài viết về MVVM, chủ yếu là bài nàybài này .

Câu hỏi cụ thể của tôi là: Làm cách nào để truyền đạt các thay đổi của Mô hình từ Mô hình sang Mô hình xem?

Trong bài viết của Josh, tôi không thấy rằng anh ấy làm điều này. ViewModel luôn hỏi Model về các thuộc tính. Trong ví dụ của Rachel, cô ấy có triển khai mô hình INotifyPropertyChangedvà nêu ra các sự kiện từ mô hình, nhưng chúng được sử dụng cho chính chế độ xem (xem bài viết / mã của cô ấy để biết thêm chi tiết về lý do tại sao cô ấy làm điều này).

Không nơi nào tôi thấy các ví dụ mà mô hình cảnh báo ViewModel về những thay đổi đối với thuộc tính mô hình. Điều này làm tôi lo lắng rằng có lẽ nó không được thực hiện vì một số lý do. Có mẫu nào để cảnh báo ViewModel về những thay đổi trong Model không? Có vẻ như cần thiết vì (1) có thể hiểu rằng có nhiều hơn 1 ViewModel cho mỗi mô hình và (2) ngay cả khi chỉ có một ViewModel, một số hành động trên mô hình có thể dẫn đến các thuộc tính khác bị thay đổi.

Tôi nghi ngờ rằng có thể có câu trả lời / nhận xét ở dạng "Tại sao bạn muốn làm điều đó?" nhận xét, vì vậy đây là mô tả về chương trình của tôi. Tôi mới sử dụng MVVM nên có lẽ toàn bộ thiết kế của tôi bị lỗi. Tôi sẽ mô tả ngắn gọn về nó.

Tôi đang lập trình thứ gì đó thú vị hơn (ít nhất là với tôi!) Hơn là các lớp "Khách hàng" hoặc "Sản phẩm". Tôi đang lập trình BlackJack.

Tôi có một Chế độ xem không có bất kỳ mã nào phía sau và chỉ dựa vào liên kết với các thuộc tính và lệnh trong ViewModel (xem bài viết của Josh Smith).

Dù tốt hay xấu, tôi mất thái độ rằng Model nên chứa không chỉ các lớp học như PlayingCard, Deck, mà còn là BlackJackGameđẳng cấp mà giữ trạng thái của toàn bộ trò chơi, và biết khi nào người chơi có bức tượng bán thân đi, các đại lý có trách nhiệm xây thẻ, và điểm số hiện tại của người chơi và nhà cái là bao nhiêu (nhỏ hơn 21, 21, phá sản, v.v.).

Từ việc BlackJackGametôi tiết lộ các phương thức như "DrawCard" và tôi nhận ra rằng khi một thẻ được rút ra, các thuộc tính như CardScore, và IsBustphải được cập nhật và các giá trị mới này được giao tiếp với ViewModel. Có lẽ đó là suy nghĩ sai lầm?

Một người có thể có thái độ mà ViewModel gọi là DrawCard()phương pháp để anh ta nên biết để yêu cầu điểm số được cập nhật và tìm hiểu xem anh ta có bị phá sản hay không. Ý kiến?

Trong ViewModel của tôi, tôi có logic để lấy hình ảnh thực tế của một thẻ chơi (dựa trên bộ đồ, thứ hạng) và cung cấp nó cho chế độ xem. Mô hình không nên quan tâm đến điều này (có lẽ ViewModel khác sẽ chỉ sử dụng số thay vì chơi hình ảnh thẻ). Tất nhiên, có lẽ một số người sẽ nói với tôi rằng Model thậm chí không nên có khái niệm về trò chơi BlackJack và điều đó nên được xử lý trong ViewModel?


3
Tương tác mà bạn đang mô tả nghe giống như một cơ chế sự kiện tiêu chuẩn là tất cả những gì bạn cần. Mô hình có thể hiển thị một sự kiện được gọi OnBustvà máy ảo có thể đăng ký sự kiện đó. Tôi đoán bạn cũng có thể sử dụng phương pháp IEA.
code4life

Thành thật mà nói, nếu tôi tạo ra một 'ứng dụng' xì dách thực sự ở đâu, dữ liệu của tôi sẽ ẩn sau một vài lớp dịch vụ / proxy và mức độ lớn của các bài kiểm tra đơn vị tương tự như A + B = C. Đó sẽ là proxy / dịch vụ thông báo về những thay đổi.
Meirion Hughes

1
Cảm ơn tất cả mọi người! Thật không may, tôi chỉ có thể chọn một câu trả lời. Tôi chọn câu hỏi của Rachel do tư vấn thêm về kiến ​​trúc và làm sạch câu hỏi ban đầu. Nhưng có rất nhiều câu trả lời tuyệt vời và tôi đánh giá cao chúng. -Dave
Dave


2
FWIW: Sau khi vật lộn trong vài năm với sự phức tạp của việc duy trì cả khái niệm VM và M trên mỗi miền, giờ tôi tin rằng cả hai đều không thành công DRY; việc tách các mối quan tâm cần thiết có thể được thực hiện dễ dàng hơn bằng cách có hai GIAO DIỆN trên một đối tượng duy nhất - "Giao diện miền" và "Giao diện mô hình". Đối tượng này có thể được chuyển cho cả logic nghiệp vụ và logic Chế độ xem mà không gây nhầm lẫn hoặc thiếu đồng bộ. Đối tượng đó là một "đối tượng nhận dạng" - nó đại diện duy nhất cho thực thể. Khi đó, việc duy trì sự tách biệt giữa mã miền và mã chế độ xem cần các công cụ tốt hơn để làm như vậy bên trong một lớp.
ToolmakerSteve

Câu trả lời:


61

Nếu bạn muốn Mô hình của mình thông báo cho các ViewModels về các thay đổi, chúng phải triển khai INotifyPropertyChanged và các ViewModels phải đăng ký để nhận thông báo về PropertyChange.

Mã của bạn có thể trông giống như sau:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

Nhưng thông thường, điều này chỉ cần thiết nếu có nhiều đối tượng thực hiện thay đổi đối với dữ liệu của Mô hình, điều này không thường xảy ra.

Nếu bạn đã từng gặp trường hợp bạn không thực sự có tham chiếu đến thuộc tính Model của mình để đính kèm sự kiện PropertyChanged vào đó, thì bạn có thể sử dụng hệ thống Nhắn tin như Prism's EventAggregatorhoặc MVVM Light's Messenger.

Tôi có một cái nhìn tổng quan ngắn gọn về các hệ thống nhắn tin trên blog của mình, tuy nhiên để tóm tắt lại, bất kỳ đối tượng nào cũng có thể phát thông báo và bất kỳ đối tượng nào cũng có thể đăng ký để nghe các thông điệp cụ thể. Vì vậy, bạn có thể phát một PlayerScoreHasChangedMessageđối tượng từ một đối tượng và một đối tượng khác có thể đăng ký để nghe các loại thông báo đó và cập nhật thuộc PlayerScoretính của nó khi nghe thấy một đối tượng.

Nhưng tôi không nghĩ rằng điều này là cần thiết cho hệ thống bạn đã mô tả.

Trong một thế giới MVVM lý tưởng, ứng dụng của bạn bao gồm các ViewModels và Models của bạn chỉ là các khối được sử dụng để xây dựng ứng dụng của bạn. Chúng thường chỉ chứa dữ liệu, do đó sẽ không có các phương thức như DrawCard()(sẽ có trong ViewModel)

Vì vậy, bạn có thể sẽ có các đối tượng dữ liệu Mô hình đơn giản như sau:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

và bạn có một đối tượng ViewModel như

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(Tất cả các đối tượng trên đều nên triển khai INotifyPropertyChanged, nhưng tôi đã bỏ qua vì đơn giản)


3
Nói một cách tổng quát hơn, có phải tất cả các quy tắc / logic nghiệp vụ đều có trong mô hình không? Tất cả logic đi đến đâu khi nói rằng bạn có thể lấy một thẻ lên đến 21 (nhưng người chia bài vẫn đặt ở mức 17), rằng bạn có thể chia thẻ, v.v. Tôi cho rằng tất cả đều thuộc về loại mô hình và vì lý do đó tôi cảm thấy mình cần một lớp điều khiển BlacJackGame trong mô hình. Tôi vẫn đang cố gắng hiểu điều này và sẽ đánh giá cao các ví dụ / tài liệu tham khảo. Ý tưởng về blackjack làm ví dụ được nêu ra từ một lớp iTunes về lập trình iOS, nơi logic / quy tắc nghiệp vụ chắc chắn nhất trong lớp mô hình của một mẫu MVC.
Dave

3
@Dave Có, DrawCard()phương thức sẽ nằm trong ViewModel, cùng với logic trò chơi khác của bạn. Trong một ứng dụng MVVM lý tưởng, bạn có thể chạy ứng dụng của mình hoàn toàn mà không có giao diện người dùng, đơn giản bằng cách tạo các ViewModels và chạy các phương thức của chúng, chẳng hạn như thông qua tập lệnh thử nghiệm hoặc cửa sổ nhắc lệnh. Mô hình thường chỉ là các mô hình dữ liệu chứa dữ liệu thô và xác thực dữ liệu cơ bản.
Rachel

6
Cảm ơn Rachel vì tất cả sự giúp đỡ. Tôi sẽ phải nghiên cứu thêm vấn đề này hoặc viết một câu hỏi khác; Tôi vẫn còn bối rối về vị trí của logic trò chơi. Bạn (và những người khác) ủng hộ việc đưa nó vào ViewModel, những người khác nói "logic nghiệp vụ" mà trong trường hợp của tôi, tôi cho rằng luật chơi và trạng thái trò chơi thuộc về mô hình (xem ví dụ: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) và stackoverflow.com/questions/10964003/… ). Tôi nhận ra rằng trong trò chơi đơn giản này, nó có lẽ không quan trọng lắm. Nhưng sẽ rất vui nếu biết. Thxs!
Dave

1
@Dave Tôi có thể đang sử dụng thuật ngữ "logic nghiệp vụ" không chính xác và trộn nó với logic ứng dụng. Để trích dẫn bài báo MSDN mà bạn đã liên kết "Để tối đa hóa cơ hội sử dụng lại, các mô hình không được chứa bất kỳ hành vi hoặc hành vi ứng dụng cụ thể cho trường hợp sử dụng hoặc tác vụ người dùng cụ thể""Thông thường, mô hình dạng xem sẽ xác định các lệnh hoặc hành động có thể được biểu diễn trong giao diện người dùng và người dùng có thể gọi " . Vì vậy, những thứ như một DrawCardCommand()sẽ là trong ViewModel, nhưng tôi đoán bạn có thể có một BlackjackGameModelđối tượng có chứa một DrawCard()phương pháp mà lệnh gọi nếu bạn muốn
Rachel

2
Tránh rò rỉ bộ nhớ. Sử dụng một mẫu WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

Câu trả lời ngắn gọn: nó phụ thuộc vào các chi tiết cụ thể.

Trong ví dụ của bạn, các mô hình đang được cập nhật "theo cách riêng của chúng" và những thay đổi này tất nhiên cần phải truyền bá bằng cách nào đó đến lượt xem. Vì các khung nhìn chỉ có thể truy cập trực tiếp vào các mô hình xem, điều đó có nghĩa là mô hình phải truyền đạt những thay đổi này tới mô hình khung nhìn tương ứng. Tất nhiên, cơ chế được thiết lập để làm như vậy INotifyPropertyChanged, có nghĩa là bạn sẽ nhận được một quy trình làm việc như sau:

  1. Viewmodel được tạo và kết thúc mô hình
  2. View Model đăng ký PropertyChangedsự kiện của người mẫu
  3. Viewmodel được đặt làm chế độ xem DataContext, thuộc tính bị ràng buộc, v.v.
  4. Xem hành động của trình kích hoạt trên mô hình xem
  5. Phương thức cuộc gọi mô hình trên mô hình
  6. Mô hình tự cập nhật
  7. Viewmodel xử lý mô hình PropertyChangedvà nâng cao mô hình của chính nó PropertyChangedđể đáp ứng
  8. Chế độ xem phản ánh những thay đổi trong các ràng buộc của nó, đóng vòng phản hồi

Mặt khác, nếu các mô hình của bạn chứa ít (hoặc không) logic nghiệp vụ hoặc nếu vì một số lý do khác (chẳng hạn như đạt được khả năng giao dịch) bạn quyết định để mỗi mô hình xem "sở hữu" mô hình được bao bọc của nó thì mọi sửa đổi đối với mô hình sẽ được chuyển mô hình khung nhìn nên việc sắp xếp như vậy sẽ không cần thiết.

Tôi mô tả một thiết kế như vậy trong một câu hỏi MVVM khác ở đây .


Xin chào, danh sách bạn đã lập, thật tuyệt vời. Tuy nhiên, tôi gặp sự cố với 7. và 8. Đặc biệt: Tôi có ViewModel, không triển khai INotifyPropertyChanged. Nó chứa một danh sách các con, trong đó có một danh sách các con (nó được sử dụng như một ViewModel cho một điều khiển WPF Treeview). Làm cách nào để làm cho UserControl DataContext ViewModel "lắng nghe" các thay đổi thuộc tính trong bất kỳ phần tử con nào (TreeviewItems)? Làm cách nào để đăng ký chính xác tất cả các phần tử con, ai thực hiện INotifyPropertyChanged? Hay tôi nên đặt một câu hỏi riêng?
Igor

4

Lựa chọn của bạn:

  • Triển khai INotifyPropertyChanged
  • Sự kiện
  • POCO với trình điều khiển Proxy

Như tôi thấy, nó INotifyPropertyChangedlà một phần cơ bản của .Net. tức là của nó trong System.dll. Việc triển khai nó trong "Model" của bạn cũng giống như việc triển khai một cấu trúc sự kiện.

Nếu bạn muốn POCO thuần túy, thì bạn phải thao tác hiệu quả các đối tượng của mình thông qua proxy / dịch vụ và sau đó ViewModel của bạn được thông báo về các thay đổi bằng cách lắng nghe proxy.

Cá nhân tôi chỉ triển khai INotifyPropertyChanged một cách lỏng lẻo và sau đó sử dụng FODY để thực hiện công việc bẩn thỉu cho tôi. Nó trông và cảm thấy POCO.

Một ví dụ (sử dụng FODY để IL Weave the PropertyChanged raisers):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

thì bạn có thể để ViewModel lắng nghe PropertyChanged để biết bất kỳ thay đổi nào; hoặc các thay đổi cụ thể về tài sản.

Vẻ đẹp của tuyến INotifyPropertyChanged là bạn kết nối nó với một Bộ sưu tập quan sát mở rộng . Vì vậy, bạn kết xuất các đối tượng gần poco của mình vào một bộ sưu tập và lắng nghe bộ sưu tập ... nếu có gì thay đổi, ở bất kỳ đâu, bạn sẽ tìm hiểu về nó.

Thành thật mà nói, điều này có thể tham gia cuộc thảo luận "Tại sao INotifyPropertyChanged không được trình biên dịch tự động xử lý", thảo luận này hướng đến: Mọi đối tượng trong c # phải có cơ sở để thông báo nếu bất kỳ phần nào của nó bị thay đổi; tức là thực hiện INotifyPropertyChanged theo mặc định. Nhưng nó không và cách tốt nhất, đòi hỏi ít nỗ lực nhất, là sử dụng IL Weaving (cụ thể là FODY ).


4

Chủ đề khá cũ nhưng sau rất nhiều lần tìm kiếm, tôi đã đưa ra giải pháp của riêng mình: A PropertyChangedProxy

Với lớp này, bạn có thể dễ dàng đăng ký NotifyPropertyChanged của người khác và thực hiện các hành động thích hợp nếu nó bị kích hoạt cho thuộc tính đã đăng ký.

Đây là một ví dụ về cách điều này có thể trông như thế nào khi bạn có thuộc tính mô hình "Trạng thái" có thể tự thay đổi và sau đó sẽ tự động thông báo cho ViewModel để kích hoạt PropertyChanged của chính nó trên thuộc tính "Trạng thái" để chế độ xem cũng được thông báo: )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

và đây chính là lớp:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}

1
Tránh rò rỉ bộ nhớ. Sử dụng một mẫu WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS - OTOH, hãy coi Mô hình sự kiện yếu là nguy hiểm . Cá nhân tôi muốn có nguy cơ bị rò rỉ bộ nhớ nếu tôi quên hủy đăng ký ( -= my_event_handler), vì điều đó dễ theo dõi hơn là một vấn đề zombie hiếm + không thể đoán trước có thể-hoặc-có thể-không bao giờ xảy ra.
ToolmakerSteve

@ToolmakerSteve cảm ơn bạn đã thêm một đối số cân bằng. Tôi khuyên các nhà phát triển nên làm những gì tốt nhất cho họ, trong tình huống của riêng họ. Đừng mù quáng áp dụng mã nguồn từ internet. Có rất nhiều bằng chứng khác như EventAggregator / EventBus thường được sử dụng gửi tin nhắn qua thành phần (mà cũng được rèn với những hiểm nguy của riêng mình)
JJS

2

Tôi thấy bài viết này hữu ích: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

Bản tóm tắt của tôi:

Ý tưởng đằng sau tổ chức MVVM là cho phép tái sử dụng các chế độ xem và mô hình dễ dàng hơn và cũng cho phép thử nghiệm tách rời. Mô hình khung nhìn của bạn là mô hình đại diện cho các thực thể khung nhìn, mô hình của bạn đại diện cho các thực thể kinh doanh.

Nếu bạn muốn chơi poker sau này thì sao? Phần lớn giao diện người dùng nên được sử dụng lại. Nếu logic trò chơi của bạn bị ràng buộc trong mô hình xem của bạn, sẽ rất khó để sử dụng lại các yếu tố đó mà không phải lập trình lại mô hình xem. Điều gì xảy ra nếu bạn muốn thay đổi giao diện người dùng của mình? Nếu logic trò chơi của bạn được kết hợp với logic mô hình xem của bạn, bạn sẽ cần phải kiểm tra lại để đảm bảo rằng trò chơi của mình vẫn hoạt động. Điều gì sẽ xảy ra nếu bạn muốn tạo một máy tính để bàn và một ứng dụng web? Nếu mô hình xem của bạn chứa logic trò chơi, việc cố gắng duy trì hai ứng dụng này cạnh nhau sẽ trở nên phức tạp vì logic ứng dụng chắc chắn sẽ bị ràng buộc với logic nghiệp vụ trong mô hình xem.

Thông báo thay đổi dữ liệu và xác thực dữ liệu xảy ra trong mọi lớp (chế độ xem, mô hình chế độ xem và mô hình).

Mô hình chứa các biểu diễn dữ liệu của bạn (thực thể) và logic nghiệp vụ cụ thể cho các thực thể đó. Một bộ bài là một 'thứ' hợp lý với các thuộc tính vốn có. Một bộ bài tốt không thể có các quân bài trùng lặp được đưa vào. Nó cần chỉ ra một cách để lấy (các) thẻ hàng đầu. Nó cần biết không đưa ra nhiều thẻ hơn số thẻ còn lại. Hành vi của bộ bài như vậy là một phần của mô hình bởi vì chúng vốn có đối với một bộ bài. Cũng sẽ có các mô hình đại lý, mô hình người chơi, mô hình bàn tay,… Những mô hình này có thể và sẽ tương tác.

View-model sẽ bao gồm trình bày và logic ứng dụng. Tất cả các công việc liên quan đến việc hiển thị trò chơi đều tách biệt với logic của trò chơi. Điều này có thể bao gồm hiển thị bàn tay dưới dạng hình ảnh, yêu cầu thẻ đối với mô hình đại lý, cài đặt hiển thị của người dùng, v.v.

Phần ruột của bài báo:

Về cơ bản, cách mà tôi muốn giải thích điều này là logic kinh doanh và các thực thể của bạn bao gồm mô hình. Đây là ứng dụng cụ thể của bạn đang sử dụng, nhưng có thể được chia sẻ trên nhiều ứng dụng.

View là lớp trình bày - bất kỳ thứ gì liên quan đến việc thực sự giao tiếp trực tiếp với người dùng.

ViewModel về cơ bản là "chất keo" dành riêng cho ứng dụng của bạn để liên kết cả hai với nhau.

Tôi có một sơ đồ đẹp ở đây cho thấy giao diện của chúng:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

Trong trường hợp của bạn - hãy giải quyết một số chi tiết cụ thể ...

Xác thực: Điều này thường có 2 dạng. Việc xác thực liên quan đến đầu vào của người dùng sẽ xảy ra trong ViewModel (chủ yếu) và View (nghĩa là: TextBox "Numeric" ngăn văn bản nhập được xử lý cho bạn trong chế độ xem, v.v.). Do đó, việc xác thực đầu vào từ người dùng thường là mối quan tâm của VM. Điều đó đang được nói, thường có một "lớp" xác thực thứ hai - đây là xác nhận rằng dữ liệu đang được sử dụng phù hợp với các quy tắc kinh doanh. Điều này thường là một phần của chính mô hình - khi bạn đẩy dữ liệu vào Mô hình của mình, nó có thể gây ra lỗi xác thực. Sau đó, VM sẽ phải remap thông tin này trở lại View.

Các hoạt động "đằng sau hậu trường không có chế độ xem, như viết thư cho DB, gửi email, v.v.": Đây thực sự là một phần của "Hoạt động cụ thể của miền" trong sơ đồ của tôi và thực sự hoàn toàn là một phần của Mô hình. Đây là những gì bạn đang cố gắng để lộ thông qua ứng dụng. ViewModel hoạt động như một cầu nối để hiển thị thông tin này, nhưng các hoạt động là thuần túy-Model.

Các hoạt động cho ViewModel: ViewModel không chỉ cần INPC - nó còn cần bất kỳ hoạt động nào dành riêng cho ứng dụng của bạn (không phải logic kinh doanh của bạn), chẳng hạn như lưu các tùy chọn và trạng thái người dùng, v.v. Điều này sẽ thay đổi ứng dụng. theo ứng dụng., ngay cả khi giao diện cùng một "mô hình".

Một cách hay để nghĩ về nó - Giả sử bạn muốn tạo 2 phiên bản cho hệ thống đặt hàng của mình. Đầu tiên là trong WPF, và thứ hai là giao diện web.

Logic được chia sẻ tự xử lý các đơn đặt hàng (gửi email, nhập vào DB, v.v.) là Mô hình. Ứng dụng của bạn đang hiển thị các hoạt động và dữ liệu này cho người dùng, nhưng thực hiện theo 2 cách.

Trong ứng dụng WPF, giao diện người dùng (những gì người xem tương tác với) là "chế độ xem" - trong ứng dụng web, về cơ bản đây là đoạn mã (ít nhất là cuối cùng) được chuyển thành javascript + html + css trên máy khách.

ViewModel là phần còn lại của "keo" cần thiết để điều chỉnh mô hình của bạn (các hoạt động này liên quan đến việc đặt hàng) để làm cho nó hoạt động với công nghệ / lớp chế độ xem cụ thể mà bạn đang sử dụng.


Có thể một ví dụ đơn giản là một máy nghe nhạc. Các mô hình của bạn sẽ chứa các thư viện và tệp âm thanh hoạt động và codec và logic trình phát và mã xử lý tín hiệu kỹ thuật số. Các mô hình chế độ xem sẽ chứa các điều khiển và hình ảnh hóa và trình duyệt thư viện của bạn. Có rất nhiều logic giao diện người dùng cần thiết để hiển thị tất cả thông tin đó và sẽ rất tuyệt nếu cho phép một lập trình viên tập trung vào việc phát nhạc trong khi cho phép lập trình viên khác tập trung vào việc làm cho giao diện người dùng trực quan và thú vị. View-model và model sẽ cho phép hai lập trình viên đó đồng ý về một tập hợp các giao diện và làm việc riêng biệt.
VoteCoffee

Một ví dụ điển hình khác là một trang web. Logic phía máy chủ thường tương đương với một mô hình. Logic phía máy khách thường tương đương với một mô hình khung nhìn. Tôi sẽ dễ dàng tưởng tượng logic trò chơi sẽ thuộc về máy chủ và không được giao cho máy khách.
VoteCoffee

2

Thông báo dựa trên INotifyPropertyChangedINotifyCollectionChanged là chính xác những gì bạn cần. Để đơn giản hóa cuộc sống của bạn với việc đăng ký thay đổi thuộc tính, xác thực thời gian biên dịch của tên thuộc tính, tránh rò rỉ bộ nhớ, tôi khuyên bạn nên sử dụng PropertyObserver từ Quỹ MVVM của Josh Smith . Vì dự án này là mã nguồn mở, bạn chỉ có thể thêm lớp đó vào dự án của mình từ các nguồn.

Để hiểu, cách sử dụng PropertyObserver hãy đọc bài viết này .

Ngoài ra, hãy tìm hiểu sâu hơn về Tiện ích mở rộng phản ứng (Rx) . Bạn có thể hiển thị IObserver <T> từ mô hình của mình và đăng ký nó trong mô hình xem.


Cảm ơn bạn rất nhiều vì đã tham khảo bài viết xuất sắc của Josh Smiths và bao gồm các Sự kiện Yếu!
JJS

1

Các chàng trai đã thực hiện một công việc đáng kinh ngạc khi trả lời câu hỏi này nhưng trong những tình huống như thế này, tôi thực sự cảm thấy rằng mô hình MVVM là một vấn đề nên tôi sẽ sử dụng Bộ điều khiển giám sát hoặc cách tiếp cận Chế độ xem thụ động và loại bỏ hệ thống ràng buộc ít nhất đối với các đối tượng mô hình đang tự tạo ra các thay đổi.


1

Tôi đã ủng hộ Mô hình hướng -> Mô hình xem -> Xem luồng thay đổi từ lâu, như bạn có thể thấy trong phần Luồng thay đổi của bài viết MVVM của tôi từ năm 2008. Điều này yêu cầu triển khai INotifyPropertyChangedtrên mô hình. Theo như tôi có thể nói, nó đã trở thành thông lệ.

Vì bạn đã đề cập đến Josh Smith, hãy xem qua lớp PropertyChanged của anh ấy . Đó là một lớp người trợ giúp để đăng ký INotifyPropertyChanged.PropertyChangedsự kiện của mô hình .

Bạn thực sự có thể thực hiện cách tiếp cận này xa hơn nữa, như tôi đã làm gần đây bằng cách tạo lớp PropertiesUpdater của mình . Các thuộc tính trên mô hình khung nhìn được tính dưới dạng biểu thức phức tạp bao gồm một hoặc nhiều thuộc tính trên mô hình.


1

Không có gì sai khi triển khai INotifyPropertyChanged bên trong Model và lắng nghe nó bên trong ViewModel. Trên thực tế, bạn thậm chí có thể chấm vào thuộc tính của mô hình ngay trong XAML: {Binding Model.ModelProperty}

Đối với thuộc tính chỉ đọc phụ thuộc / được tính toán, cho đến nay tôi chưa thấy điều gì tốt hơn và đơn giản hơn thế này: https://github.com/StephenCleary/CalculatedProperties . Nó rất đơn giản nhưng cực kỳ hữu ích, nó thực sự là "công thức Excel cho MVVM" - chỉ hoạt động theo cách giống như Excel truyền các thay đổi cho các ô công thức mà không cần thêm nỗ lực từ phía bạn.


0

Bạn có thể nêu ra các sự kiện từ mô hình mà mô hình xem cần đăng ký.

Ví dụ: gần đây tôi đã làm việc trong một dự án mà tôi phải tạo một chế độ xem dạng cây (theo lẽ tự nhiên, mô hình có bản chất phân cấp đối với nó). Trong mô hình, tôi có một bộ sưu tập có thể quan sát được gọi là ChildElements.

Trong khung nhìn, tôi đã lưu trữ một tham chiếu đến đối tượng trong mô hình và đăng ký CollectionChangedsự kiện của bộ sưu tập có thể quan sát được, như sau: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Sau đó, mô hình chế độ xem của bạn sẽ tự động được thông báo khi có thay đổi trong mô hình. Bạn có thể làm theo cùng một khái niệm bằng cách sử dụng PropertyChanged, nhưng bạn sẽ cần phải nâng cao rõ ràng các sự kiện thay đổi thuộc tính từ mô hình của mình để điều đó hoạt động.


Nếu xử lý dữ liệu phân cấp, bạn sẽ muốn xem Demo 2 của bài viết MVVM của tôi .
HappyNomad

0

Đối với tôi, đây dường như là một câu hỏi thực sự quan trọng - ngay cả khi không có áp lực để thực hiện nó. Tôi đang làm việc trên một dự án thử nghiệm, liên quan đến TreeView. Có các mục menu và các mục đó được ánh xạ tới các lệnh, ví dụ như Xóa. Hiện tại, tôi đang cập nhật cả mô hình và mô hình xem từ bên trong mô hình xem.

Ví dụ,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

Điều này tuy đơn giản, nhưng dường như có một khuyết điểm rất cơ bản. Một bài kiểm tra đơn vị điển hình sẽ thực hiện lệnh, sau đó kiểm tra kết quả trong mô hình khung nhìn. Nhưng điều này không kiểm tra rằng bản cập nhật mô hình là chính xác, vì cả hai được cập nhật đồng thời.

Vì vậy, có lẽ tốt hơn là sử dụng các kỹ thuật như PropertyObserver để cho phép cập nhật mô hình kích hoạt cập nhật mô hình chế độ xem. Thử nghiệm đơn vị tương tự bây giờ sẽ chỉ hoạt động nếu cả hai hành động đều thành công.

Đây không phải là một câu trả lời tiềm năng, tôi nhận ra, nhưng nó có vẻ đáng để đặt ra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.