WPF'de MVVM - Modeldeki değişiklikler için ViewModel nasıl uyarılır… yoksa yapmalı mıyım?


112

Başta bu ve bu olmak üzere bazı MVVM makalelerinden geçiyorum .

Benim özel sorum şudur: Model değişikliklerini Modelden ViewModel'e nasıl iletebilirim?

Josh'un makalesinde, bunu yaptığını görmüyorum. ViewModel her zaman Modelden özellikler ister. Rachel'ın örneğinde, model uygulamasına sahip ve modeldeki INotifyPropertyChangedolayları yükseltiyor, ancak bunlar görünümün kendisi tarafından tüketim içindir (bunu neden yaptığına dair daha fazla ayrıntı için makalesine / koduna bakın).

Modelin ViewModel'i model özelliklerinde yapılan değişiklikler konusunda uyardığı örnekleri hiçbir yerde görmüyorum. Bu beni, belki de bir nedenden dolayı yapılmadığı konusunda endişelendiriyor. Modeldeki değişiklikler için ViewModel'i uyarmak için bir model var mı? (1) Muhtemelen her model için 1'den fazla ViewModel olduğundan ve (2) sadece bir ViewModel olsa bile, model üzerindeki bazı eylemler diğer özelliklerin değişmesine neden olabilir.

"Bunu neden yapmak istersiniz?" Şeklinde cevaplar / yorumlar olabileceğinden şüpheleniyorum. yorumlar, işte programımın bir açıklaması. MVVM'de yeniyim, bu yüzden belki de tüm tasarımım hatalı. Kısaca anlatacağım.

"Müşteri" veya "Ürün" sınıflarından daha ilginç (en azından benim için!) Bir şey programlıyorum. BlackJack'i programlıyorum.

Arkasında herhangi bir kod bulunmayan ve sadece ViewModel'deki özelliklere ve komutlara bağlanmaya dayanan bir View'um var (Josh Smith'in makalesine bakın).

İyi ya da kötü, ben Modeli gibi sadece sınıfları yer almasına da tavır aldı PlayingCard, Deckfakat aynı zamanda BlackJackGamebütün oyunun durumunu tutan sınıfını ve oyuncu gitti büstü sahip olduğunda, satıcı kart çekmek zorundadır bilir ve Oyuncu ve krupiyenin şu anki skoru (21, 21'den az, bust, vb.).

Gönderen BlackJackGameben "drawcard" gibi yöntemleri göstermek ve onu aklıma bir kart gibi özelliklere çizildiğinde CardScoreve IsBustgüncellenmesi gerekir ve bu yeni değerler ViewModel tebliğ. Belki de yanlış düşünme budur?

Kişi, ViewModel'in DrawCard()yöntemi çağırdığı tavrı benimseyebilir, böylece güncellenmiş bir puan istemeyi bilmesi ve iflas edip etmediğini öğrenmesi gerekir. Görüşler?

ViewModel'imde, bir oyun kartının gerçek bir görüntüsünü (renk, rütbeye göre) alma ve görünüm için kullanılabilir hale getirme mantığına sahibim. Model bununla ilgilenmemelidir (belki başka ViewModel, kart görüntülerini oynamak yerine sadece sayıları kullanır). Elbette, belki bazıları bana Modelin bir BlackJack oyunu konseptine sahip olmaması gerektiğini ve bunun ViewModel'de ele alınması gerektiğini söyler?


3
Tanımladığınız etkileşim standart bir olay mekanizması gibi geliyor, ihtiyacınız olan tek şey. Model çağrılan bir olayı açığa çıkarabilir OnBustve VM buna abone olabilir. Sanırım bir IEA yaklaşımı da kullanabilirsiniz.
code4life

Dürüst olacağım, eğer gerçek bir blackjack 'uygulamasını' nerede yaparsam, verilerim birkaç servis / proxy katmanının ve A + B = C'ye benzer bir bilgiçlik düzeyi birim testinin arkasına gizlenir. / değişiklikleri bildiren hizmet.
Meirion Hughes

1
Herkese teşekkürler! Maalesef sadece bir cevap seçebiliyorum. Ekstra mimari tavsiye ve orijinal soruyu temizlemekten dolayı Rachel'ı seçiyorum. Ama pek çok harika yanıt vardı ve onları takdir ediyorum. -Dave
Dave


2
FWIW: Alan başına hem VM hem de M kavramını sürdürmenin karmaşıklığıyla birkaç yıl uğraştıktan sonra, şimdi her ikisinin de DRY'de başarısız olduğuna inanıyorum; tek bir nesne üzerinde iki ARAYÜZ - bir "Etki Alanı Arayüzü" ve bir "ViewModel Arayüzü" olmak üzere gerekli endişelerin ayrılması daha kolay yapılabilir. Bu nesne, kafa karışıklığı veya senkronizasyon eksikliği olmadan hem iş mantığına hem de Görünüm mantığına geçirilebilir. Bu nesne bir "kimlik nesnesidir" - varlığı benzersiz bir şekilde temsil eder. Alan kodu ile görünüm kodu arasındaki ayrımı korumak, bir sınıf içinde bunu yapmak için daha iyi araçlara ihtiyaç duyar.
ToolmakerSteve

Yanıtlar:


61

Modellerinizin ViewModels değişikliklerini uyarmasını istiyorsanız, INotifyPropertyChanged uygulamasını uygulamalı ve ViewModels PropertyChange bildirimlerini almak için abone olmalıdır.

Kodunuz aşağıdaki gibi görünebilir:

// 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");
}

Ancak genellikle bu, yalnızca birden fazla nesne Model verilerinde değişiklik yapacaksa gereklidir, bu genellikle böyle değildir.

PropertyChanged olayını ona iliştirmek için Model mülkünüze aslında bir referansınız olmadığı bir durumunuz varsa, Prism's EventAggregatorveya MVVM Light's gibi bir Mesajlaşma sistemi kullanabilirsiniz Messenger.

Blogumda mesajlaşma sistemlerine kısa bir genel bakış var , ancak bunu özetlemek gerekirse, herhangi bir nesne bir mesaj yayınlayabilir ve herhangi bir nesne belirli mesajları dinlemek için abone olabilir. Böylece PlayerScoreHasChangedMessagebir nesneden bir yayın yayınlayabilirsiniz ve başka bir nesne bu tür mesajları dinlemek için abone olabilir ve PlayerScorebirini duyduğunda özelliğini güncelleyebilir .

Ama tarif ettiğiniz sistem için bunun gerekli olduğunu düşünmüyorum.

İdeal bir MVVM dünyasında, uygulamanız ViewModel'lerinizden oluşur ve Modelleriniz, uygulamanızı oluşturmak için kullanılan bloklardır. Tipik olarak yalnızca veri içerirler, bu nedenle DrawCard()(bir ViewModel'de olacak) gibi yöntemlere sahip olmazlardı.

Yani muhtemelen aşağıdakiler gibi düz Model veri nesnelerine sahip olursunuz:

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

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

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

ve aşağıdaki gibi bir ViewModel nesneniz olur

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);
    }
}

(Yukarıdaki nesnelerin tümü uygulanmalıdır INotifyPropertyChanged, ancak basit olması için onu dışarıda bıraktım)


3
Daha genel olarak, tüm iş mantığı / kuralları modele dahil mi? 21'e kadar bir kart alabileceğinizi (ancak dağıtıcı 17'de kalır), kartları bölebileceğinizi vb. Söyleyen tüm mantık nereye gider? Hepsinin model sınıfına ait olduğunu varsaydım ve bu nedenle ihtiyacım olduğunu hissettim modeldeki bir BlacJackGame denetleyici sınıfı. Hala bunu anlamaya çalışıyorum ve örnekleri / referansları takdir ediyorum. Bir örnek için blackjack fikri, iş mantığı / kurallarının kesinlikle bir MVC modelinin model sınıfında olduğu iOS programlamadaki bir iTunes sınıfından kaldırıldı.
Dave

3
@Dave Evet, DrawCard()yöntem, diğer oyun mantığınızla birlikte ViewModel'de olacaktır. İdeal bir MVVM uygulamasında, uygulamanızı tamamen UI olmadan çalıştırabilmeniz için, sadece ViewModel'ler oluşturup yöntemlerini bir test betiği veya bir komut istemi penceresi aracılığıyla çalıştırabilirsiniz. Modeller tipik olarak yalnızca ham verileri ve temel veri doğrulamasını içeren veri modelleridir.
Rachel

6
Tüm yardımlar için teşekkürler Rachel. Bunu biraz daha araştırmam veya başka bir soru yazmam gerekecek; Hala oyun mantığının yeri konusunda kafam karışık. Siz (ve diğerleri) bunu ViewModel'e koymayı savunuyorsunuz, diğerleri benim durumumda oyun kurallarının ve oyunun durumunun modele ait olduğunu varsaydığım "iş mantığı" diyor (örneğin bkz: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) ve stackoverflow.com/questions/10964003/… ). Bu basit oyunda muhtemelen çok önemli olmadığını biliyorum. Ama bilmek güzel olurdu. Thxs!
Dave

1
@Dave "İş mantığı" terimini yanlış kullanıyor ve uygulama mantığıyla karıştırıyor olabilirim. Bağladığınız MSDN makalesini alıntılamak için "Yeniden kullanım fırsatlarını en üst düzeye çıkarmak için modeller, herhangi bir kullanım senaryosuna özel veya kullanıcıya göre özel davranış veya uygulama mantığı içermemelidir" ve "Tipik olarak, görünüm modeli temsil edilebilecek komutları veya eylemleri tanımlayacaktır. kullanıcı arayüzünde ve kullanıcının çağırabileceği " . Yani a gibi şeyler DrawCardCommand()ViewModel'de olurdu, ancak sanırım komutun isterseniz çağırdığı BlackjackGameModelbir DrawCard()yöntemi içeren bir nesneye sahip olabilirsiniz
Rachel

2
Bellek sızıntılarından kaçının. Bir WeakEvent kalıbı kullanın. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

Kısa cevap: ayrıntılara bağlıdır.

Örneğinizde modeller "kendi başlarına" güncelleniyor ve bu değişikliklerin elbette bir şekilde görüşlere yayılması gerekiyor. Görünümler yalnızca doğrudan görünüm modellerine erişebildiğinden, modelin bu değişiklikleri ilgili görünüm modeline iletmesi gerektiği anlamına gelir. Bunu yapmak için yerleşik mekanizma elbette şudur INotifyPropertyChanged, bu da aşağıdaki gibi bir iş akışı alacağınız anlamına gelir:

  1. Viewmodel oluşturulur ve modeli sarar
  2. Viewmodel, modelin PropertyChangedetkinliğine abone olur
  3. Viewmodel, görünümler olarak ayarlanır DataContext, özellikler bağlıdır vb.
  4. Viewmodel'de tetikleyici eylemini görüntüleyin
  5. Viewmodel, model üzerinde yöntemi çağırır
  6. Model kendini günceller
  7. Viewmodel, modeli işler PropertyChangedve PropertyChangedyanıt olarak kendi modelini yükseltir
  8. Görünüm, geri bildirim döngüsünü kapatarak bağlamalarındaki değişiklikleri yansıtır

Öte yandan, modelleriniz çok az iş mantığı içeriyorsa (veya hiç yoksa) veya başka bir nedenle (işlem yeteneği kazanmak gibi) her bir görünüm modelinin kendi sarmalanmış modeline "sahip olmasına" izin verdiyseniz, modelde yapılan tüm değişiklikler geçecektir. görünüm modeli, böylece böyle bir düzenleme gerekli olmayacaktır.

Başka bir MVVM söz konusu böyle bir tasarım tarif burada .


Merhaba, yaptığınız liste harika. Bununla birlikte, 7. ve 8. ile ilgili bir sorunum var. Özellikle: INotifyPropertyChanged uygulamayan bir ViewModel'im var. Çocukların bir listesini içeren bir alt öğe listesi içerir (WPF Treeview denetimi için ViewModel olarak kullanılır). UserControl DataContext ViewModel'in alt öğelerden herhangi birinde (TreeviewItems) özellik değişikliklerini "dinlemesini" nasıl sağlayabilirim? INotifyPropertyChanged uygulayan tüm alt öğelere tam olarak nasıl abone olabilirim? Yoksa ayrı bir soru mu sormalıyım?
Igor

4

Senin seçimlerin:

  • INotifyPropertyChanged uygula
  • Etkinlikler
  • Proxy manipülatörlü POCO

Gördüğüm kadarıyla INotifyPropertyChanged.Net'in temel bir parçası. yani içinde System.dll. Bunu "Modelinize" uygulamak, bir olay yapısını uygulamaya benzer.

Saf POCO istiyorsanız, nesnelerinizi proxy'ler / hizmetler aracılığıyla etkili bir şekilde değiştirmeniz gerekir ve ardından ViewModel'iniz, proxy'yi dinleyerek değişikliklerden haberdar edilir.

Şahsen ben INotifyPropertyChanged'i gevşek bir şekilde uyguluyorum ve sonra kirli işi benim için yapmak için FODY kullanıyorum . POCO'ya bakıyor ve hissediyor.

Bir örnek (FODY'den IL'ye PropertyChanged yükselticileri dokumayı kullanarak):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

sonra ViewModel'inizin herhangi bir değişiklik için PropertyChanged'ı dinlemesini sağlayabilirsiniz; veya mülke özgü değişiklikler.

INotifyPropertyChanged rotasının güzelliği, onu Extended ObservableCollection ile zincirlemenizdir . Böylece, yakın poco nesnelerinizi bir koleksiyona atarsınız ve koleksiyonu dinlersiniz ... herhangi bir değişiklik olursa, herhangi bir yerde, onu öğrenirsiniz.

Dürüst olacağım, bu "Neden derleyici tarafından otomatik olarak INotifyPropertyChanged yapılmadı" tartışmasına katılabilir, bu tartışmada şu konulara ayrılır: c # içindeki her nesnenin herhangi bir parçası değiştirildiğinde bildirimde bulunma olanağı olmalıdır; yani INotifyPropertyChanged'i varsayılan olarak uygulayın. Ama öyle değil ve en az çaba gerektiren en iyi yol IL Weaving'i (özellikle FODY ) kullanmaktır.


4

Oldukça eski bir iş parçacığı, ancak çok fazla araştırmadan sonra kendi çözümümü buldum: Bir Özellik Değiştirilmiş Proxy

Bu sınıfla, başka birinin NotifyPropertyChanged programına kolayca kaydolabilir ve kayıtlı mülk için çalıştırılırsa uygun önlemleri alabilirsiniz.

Aşağıda, kendi başına değişebilen bir model özelliği olan "Durum" a sahip olduğunuzda bunun nasıl görünebileceğine dair bir örnek verilmiştir ve ardından ViewModel'e kendi PropertyChanged özelliğini kendi PropertyChanged özelliğini çalıştırması için otomatik olarak bildirmesi gerekir, böylece görünüm de bildirilir: )

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));
    }
}

ve işte sınıfın kendisi:

/// <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
Bellek sızıntılarından kaçının. Bir WeakEvent kalıbı kullanın. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS - OTOH, Zayıf Olay Modelinin Tehlikeli olduğunu düşünün . Şahsen, kaydını silmeyi ( -= my_event_handler) unutursam bellek sızıntısı riskini almayı tercih ederim , çünkü izini sürmek nadir görülen + öngörülemeyen bir zombi probleminden daha kolaydır.
ToolmakerSteve

@ToolmakerSteve dengeli bir argüman eklediğiniz için teşekkürler. Geliştiricilerin, kendi durumlarında kendileri için en iyi olanı yapmasını öneririm. İnternetteki kaynak kodunu körü körüne benimsemeyin. EventAggregator / EventBus gibi yaygın olarak kullanılan çapraz bileşenli mesajlaşma gibi başka modeller de var (bunlar da kendi tehlikeleriyle işleniyor)
JJS

2

Bu makaleyi yararlı buldum: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wPF

Özetim:

MVVM organizasyonunun arkasındaki fikir, görünümlerin ve modellerin daha kolay yeniden kullanımına izin vermek ve ayrıca ayrıştırılmış testlere izin vermektir. Görünüm modeliniz, görünüm varlıklarını temsil eden bir modeldir, modeliniz işletme varlıklarını temsil eder.

Ya daha sonra bir poker oyunu yapmak istersen? Kullanıcı arayüzünün çoğu yeniden kullanılabilir olmalıdır. Oyun mantığınız görünüm modelinize bağlıysa, bu öğeleri görünüm modelini yeniden programlamak zorunda kalmadan yeniden kullanmak çok zor olacaktır. Kullanıcı arayüzünüzü değiştirmek isterseniz ne olur? Oyun mantığınız görünüm modeli mantığınıza bağlıysa, oyununuzun hala çalışıp çalışmadığını tekrar kontrol etmeniz gerekir. Ya bir masaüstü ve bir web uygulaması oluşturmak istiyorsanız? Görünüm modeliniz oyun mantığını içeriyorsa, uygulama mantığı kaçınılmaz olarak görünüm modelindeki iş mantığına bağlı olacağından, bu iki uygulamayı yan yana sürdürmeye çalışmak karmaşık hale gelir.

Veri değişikliği bildirimleri ve veri doğrulama her katmanda (görünüm, görünüm modeli ve model) gerçekleşir.

Model, veri temsillerinizi (varlıkları) ve bu varlıklara özgü iş mantığını içerir. Bir kart destesi, doğasında bulunan özelliklere sahip mantıksal bir 'şey'dir. İyi bir desteye çift kartlar yerleştirilemez. En iyi kart (lar) ı almanın bir yolunu göstermesi gerekiyor. Kalandan daha fazla kart vermemesi gerektiğini bilmesi gerekiyor. Bu tür deste davranışları, bir deste kartın doğasında olduğundan modelin bir parçasıdır. Ayrıca bayi modelleri, oyuncu modelleri, el modelleri vb. Olacaktır. Bu modeller etkileşim kurabilir ve etkileşimde bulunacaktır.

Görünüm modeli, sunum ve uygulama mantığından oluşacaktır. Oyunu sergilemeyle ilgili tüm çalışmalar oyunun mantığından ayrıdır. Bu, ellerin resim olarak gösterilmesini, bayi modeline kart taleplerini, kullanıcı görüntüleme ayarlarını vb. İçerebilir.

Makalenin cesareti:

Temel olarak, bunu açıklamayı sevdiğim yol, iş mantığınızın ve varlıklarınızın modeli oluşturmasıdır. Bu, özel uygulamanızın kullandığı şeydir, ancak birçok uygulama arasında paylaşılabilir.

Görünüm, sunum katmanıdır - aslında doğrudan kullanıcıyla arayüz oluşturmayla ilgili her şey.

ViewModel, temelde ikisini birbirine bağlayan uygulamanıza özgü "yapıştırıcıdır".

Burada nasıl arayüz oluşturduklarını gösteren güzel bir diyagramım var:

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

Sizin durumunuzda - bazı özellikleri ele alalım ...

Doğrulama: Bu genellikle 2 şekilde gelir. Kullanıcı girdisiyle ilgili doğrulama, ViewModel'de (öncelikle) ve Görünümde (yani: metnin girilmesini önleyen "Sayısal" Metin Kutusu, görünümde sizin için işlenir, vb.) Gerçekleşir. Bu nedenle, kullanıcıdan gelen girdinin doğrulanması tipik olarak bir VM sorunudur. Bununla birlikte, genellikle ikinci bir doğrulama "katmanı" vardır - bu, kullanılan verilerin iş kurallarıyla eşleştiğinin doğrulanmasıdır. Bu genellikle modelin bir parçasıdır - verileri Modelinize aktardığınızda, doğrulama hatalarına neden olabilir. VM daha sonra bu bilgiyi Görünüme geri göndermelidir.

DB'ye yazma, e-posta gönderme vb. Gibi "perde arkasındaki sahne arkası işlemler": Bu, diyagramımdaki "Etki Alanına Özgü İşlemler" in gerçekten bir parçasıdır ve gerçekten tamamen Modelin bir parçasıdır. Uygulama aracılığıyla ifşa etmeye çalıştığınız şey budur. ViewModel, bu bilgileri açığa çıkarmak için bir köprü görevi görür, ancak işlemler saf Modeldir.

ViewModel için İşlemler: ViewModel, INPC'den daha fazlasına ihtiyaç duyar - ayrıca, kaydetme tercihleri ​​ve kullanıcı durumu vb. Gibi uygulamanıza özgü (iş mantığınıza değil) herhangi bir işleme de ihtiyaç duyar. Bu, uygulamayı değiştirecektir. uygulamaya göre, aynı "model" ile arayüz oluştururken bile.

Bunu düşünmenin iyi bir yolu - Sipariş sisteminizin 2 versiyonunu yapmak istediğinizi varsayalım. Birincisi WPF'de, ikincisi ise bir web arayüzü.

Siparişlerin kendisiyle ilgilenen paylaşılan mantık (e-posta gönderme, DB'ye girme vb.) Modeldir. Uygulamanız bu işlemleri ve verileri kullanıcıya ifşa ediyor, ancak bunu 2 şekilde yapıyor.

WPF uygulamasında, kullanıcı arabirimi (görüntüleyicinin etkileşime girdiği) "görünüm" dür - web uygulamasında, bu temelde (en azından sonunda) istemcide javascript + html + css'ye dönüştürülen koddur.

ViewModel, modelinizin kullandığınız belirli görünüm teknolojisi / katmanıyla çalışmasını sağlamak için modelinizi (siparişle ilgili bu işlemler) uyarlamak için gereken "tutkal" ın geri kalan kısmıdır.


Belki basit bir örnek bir müzik çalar. Modelleriniz kitaplıkları ve aktif ses dosyasını ve kodekleri ve oynatıcı mantığını ve dijital sinyal işleme kodunu içerecektir. Görünüm modelleri, kontrollerinizi, görselleştirmelerinizi ve kitaplık tarayıcınızı içerir. Tüm bu bilgileri görüntülemek için çok fazla kullanıcı arayüzü mantığı gerekiyor ve bir programcının müziği çalmaya odaklanmasına izin verirken, başka bir programcının kullanıcı arayüzünü sezgisel ve eğlenceli hale getirmeye odaklanmasına izin vermek güzel olurdu. Görünüm modeli ve modeli, bu iki programcının bir dizi arayüz üzerinde anlaşmasına ve ayrı ayrı çalışmasına izin vermelidir.
VoteCoffee

Bir başka güzel örnek bir web sayfasıdır. Sunucu tarafı mantığı genellikle bir modele eşdeğerdir. İstemci tarafı mantığı genellikle bir görünüm modeline eşdeğerdir. Oyun mantığının sunucuya ait olacağını ve istemciye emanet edilmeyeceğini kolayca hayal ederdim.
VoteCoffee

2

INotifyPropertyChanged ve INotifyCollectionChanged tabanlı bildirim tam olarak ihtiyacınız olan şeydir. Bellek sızıntılarını önleme, mülkiyet değişiklikleri, mülkiyet adının derleme zamanı doğrulama aboneliğine hayatınızı kolaylaştırmak için, sana kullanmayı tavsiye ediyorum PropertyObserver gelen Josh Smith'in MVVM Vakfı . Bu proje açık kaynak olduğundan, kaynaklardan projenize sadece o sınıfı ekleyebilirsiniz.

PropertyObserver'ın nasıl kullanılacağını anlamak için bu makaleyi okuyun .

Ayrıca, Reaktif Uzantılara (Rx) daha derin bir göz atın . Modelinizden IObserver <T> açığa çıkarabilir ve görünüm modelinde ona abone olabilirsiniz.


Josh Smiths'in harika makalesine atıfta bulunduğunuz ve Weak Events'i yazdığınız için çok teşekkür ederiz!
JJS

1

Adamlar bunu yanıtlamak için harika bir iş çıkardılar ama böyle durumlarda MVVM modelinin gerçekten bir acı olduğunu hissediyorum, bu yüzden gidip bir Denetleyici Denetleyici veya Pasif Görüş yaklaşımı kullanacak ve en azından model nesneler için bağlama sistemini bırakacaktım. kendi başlarına değişiklikler üretir.


1

Yönlü Modeli -> Modeli Görüntüle -> Değişikliklerin akışını uzun süredir savunuyorum, 2008'deki MVVM makalemin Değişiklik Akışı bölümünde . Bu INotifyPropertyChanged, model üzerinde uygulamayı gerektirir . Anladığım kadarıyla, o zamandan beri yaygın bir uygulama haline geldi.

Josh Smith'ten bahsettiğiniz için PropertyChanged sınıfına bir göz atın . Modelin INotifyPropertyChanged.PropertyChangedetkinliğine abone olmak için yardımcı bir sınıftır .

Yakın zamanda PropertiesUpdater sınıfımı oluşturarak yaptığım gibi, bu yaklaşımı daha da ileriye taşıyabilirsiniz . Görünüm modelindeki özellikler, model üzerinde bir veya daha fazla özellik içeren karmaşık ifadeler olarak hesaplanır.


1

INotifyPropertyChanged uygulamasında yanlış bir şey yokModel içinde ve onu ViewModel içinde dinlemek . Aslında, XAML'de modelin özelliğine bile girebilirsiniz: {Binding Model.ModelProperty}

Bağımlı / hesaplanmış salt okunur özelliklere gelince, bundan daha iyi ve daha basit bir şey görmedim: https://github.com/StephenCleary/CalculatedProperties . Çok basit ama inanılmaz derecede kullanışlı, gerçekten "MVVM için Excel formülleri" - Excel'in değişiklikleri sizin tarafınızdan fazladan çaba göstermeden formül hücrelerine yaymasıyla aynı şekilde çalışır.


0

Görünüm modelinin abone olması gereken modelden etkinlikler oluşturabilirsiniz.

Örneğin, yakın zamanda bir ağaç görünümü oluşturmam gereken bir proje üzerinde çalıştım (doğal olarak, modelin hiyerarşik bir doğası vardı). Modelde gözlemlenebilir bir koleksiyonum vardı.ChildElements .

Görünüm modelinde, modeldeki nesneye bir referans kaydetmiştim CollectionChangedve gözlemlenebilir koleksiyon olayına abone olmuştum , örneğin: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Ardından, modelde bir değişiklik olduğunda görünüm modeliniz otomatik olarak bilgilendirilir. Aynı konsepti kullanarak da takip edebilirsiniz PropertyChanged, ancak bunun çalışması için modelinizden açıkça özellik değişikliği olayları oluşturmanız gerekecektir.


Hiyerarşik veri ile ilgili ise, bakmak isteyeceksiniz Demo 2 arasında benim MVVM maddesinde .
HappyNomad

0

Bu bana gerçekten önemli bir soru gibi görünüyor - bunu yapmak için herhangi bir baskı olmasa bile. TreeView içeren bir test projesi üzerinde çalışıyorum. Örneğin Sil gibi komutlarla eşleşen menü öğeleri vardır. Şu anda hem modeli hem de görünüm modelini görünüm modeli içinden güncelliyorum.

Örneğin,

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
}

Bu basit, ancak çok temel bir kusuru var gibi görünüyor. Tipik bir birim testi komutu yürütür ve ardından sonucu görünüm modelinde kontrol eder. Ancak bu, ikisi aynı anda güncellendiği için model güncellemesinin doğru olup olmadığını test etmez.

Bu nedenle, model güncellemesinin bir görünüm modeli güncellemesini tetiklemesine izin vermek için PropertyObserver gibi teknikler kullanmak belki daha iyidir. Aynı birim testi artık yalnızca her iki işlem de başarılı olursa çalışacak.

Bunun potansiyel bir cevap olmadığını anlıyorum, ama ortaya koymaya değer görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.