MVVM ใน WPF - จะแจ้งเตือน ViewModel ของการเปลี่ยนแปลงใน Model อย่างไร…หรือฉันควร?


112

ฉันกำลังจะผ่านบทความ MVVM บางส่วนใหญ่นี้และนี้

คำถามเฉพาะของฉันคือ: ฉันจะสื่อสารการเปลี่ยนแปลงโมเดลจากโมเดลเป็น ViewModel ได้อย่างไร

ในบทความของ Josh ฉันไม่เห็นว่าเขาทำเช่นนี้ ViewModel จะถาม Model สำหรับคุณสมบัติเสมอ ในตัวอย่างของ Rachel เธอมีการนำแบบจำลองมาใช้INotifyPropertyChangedและหยิบยกเหตุการณ์ขึ้นจากแบบจำลอง แต่มีไว้เพื่อการบริโภคโดยการดูเอง (ดูบทความ / รหัสของเธอสำหรับรายละเอียดเพิ่มเติมว่าเหตุใดเธอจึงทำเช่นนี้)

ฉันไม่เห็นตัวอย่างที่โมเดลแจ้งให้ ViewModel ทราบถึงการเปลี่ยนแปลงคุณสมบัติของโมเดล สิ่งนี้ทำให้ฉันกังวลว่าอาจจะไม่ได้ทำด้วยเหตุผลบางประการ มีรูปแบบสำหรับการแจ้งเตือน ViewModel ของการเปลี่ยนแปลงใน Model หรือไม่? ดูเหมือนว่าจะมีความจำเป็นเนื่องจาก (1) มี ViewModel มากกว่า 1 แบบสำหรับแต่ละรุ่นและ (2) แม้ว่าจะมี ViewModel เพียงตัวเดียวการกระทำบางอย่างในโมเดลอาจส่งผลให้คุณสมบัติอื่น ๆ เปลี่ยนไป

ฉันสงสัยว่าอาจมีคำตอบ / ความคิดเห็นในรูปแบบ "ทำไมคุณถึงต้องการทำเช่นนั้น" ความคิดเห็นดังนั้นนี่คือคำอธิบายโปรแกรมของฉัน ฉันยังใหม่กับ MVVM ดังนั้นบางทีการออกแบบทั้งหมดของฉันอาจผิดพลาด ฉันจะอธิบายสั้น ๆ

ฉันกำลังเขียนโปรแกรมสิ่งที่น่าสนใจ (อย่างน้อยก็สำหรับฉัน!) มากกว่าคลาส "ลูกค้า" หรือ "ผลิตภัณฑ์" ฉันกำลังเขียนโปรแกรม BlackJack

ฉันมีมุมมองที่ไม่มีรหัสใด ๆ อยู่ข้างหลังและอาศัยการเชื่อมโยงกับคุณสมบัติและคำสั่งใน ViewModel (ดูบทความของ Josh Smith)

สำหรับดีขึ้นหรือแย่ลงผมเอาทัศนคติที่ว่ารุ่นควรมีชั้นเรียนไม่ได้เป็นเพียงเช่นPlayingCard, Deckแต่ยังBlackJackGameชั้นที่ช่วยให้สถานะของทั้งเกมและรู้ว่าเมื่อผู้เล่นที่มีหน้าอกหายไปตัวแทนจำหน่ายที่มีการวาดการ์ดและ คะแนนปัจจุบันของผู้เล่นและเจ้ามือคืออะไร (น้อยกว่า 21, 21, หน้าอก ฯลฯ )

จากที่BlackJackGameฉันเปิดเผยวิธีการเช่น "DrawCard" และเกิดขึ้นกับฉันว่าเมื่อการ์ดถูกวาดคุณสมบัติเช่นCardScoreและIsBustควรได้รับการอัปเดตและค่าใหม่เหล่านี้จะสื่อสารกับ ViewModel บางทีนั่นอาจเป็นความคิดที่ผิดพลาด

อาจใช้ทัศนคติที่ ViewModel เรียกว่าDrawCard()เมธอดดังนั้นเขาจึงควรรู้เพื่อขอคะแนนที่อัปเดตและดูว่าเขามีหน้าอกหรือไม่ ความคิดเห็น?

ใน ViewModel ของฉันฉันมีตรรกะในการจับภาพจริงของการ์ดเล่น (ตามชุดสูทอันดับ) และทำให้พร้อมใช้งานสำหรับมุมมอง โมเดลไม่ควรเกี่ยวข้องกับสิ่งนี้ (บางที ViewModel อื่น ๆ จะใช้ตัวเลขแทนการเล่นภาพการ์ด) แน่นอนว่าอาจมีบางคนบอกฉันว่า Model ไม่ควรมีแนวคิดของเกม BlackJack และควรจัดการใน ViewModel?


3
การโต้ตอบที่คุณกำลังอธิบายดูเหมือนกลไกเหตุการณ์มาตรฐานคือทั้งหมดที่คุณต้องการ โมเดลสามารถเปิดเผยเหตุการณ์ที่เรียกOnBustและ VM สามารถสมัครใช้งานได้ ฉันเดาว่าคุณสามารถใช้แนวทาง IEA ได้เช่นกัน
code4life

ฉันจะพูดตามตรงว่าถ้าฉันจะสร้าง 'แอป' แบล็คแจ็คจริงได้ที่ไหนข้อมูลของฉันจะซ่อนอยู่หลังบริการ / พร็อกซีสองสามชั้นและระดับการทดสอบหน่วยที่อวดดีคล้ายกับ A + B = C มันจะเป็นพร็อกซี / บริการที่แจ้งการเปลี่ยนแปลง
Meirion Hughes

1
ขอบคุณทุกคน! ขออภัยฉันเลือกได้เพียงคำตอบเดียว ฉันเลือกของ Rachel เนื่องจากคำแนะนำเพิ่มเติมเกี่ยวกับสถาปัตยกรรมและการทำความสะอาดคำถามเดิม แต่มีคำตอบที่ดีมากมายและฉันก็ขอบคุณพวกเขา -Dave
Dave


2
FWIW: หลังจากดิ้นรนมาหลายปีด้วยความซับซ้อนในการดูแลทั้ง VM และ M ต่อแนวคิดของโดเมนตอนนี้ฉันเชื่อว่าการที่ DRY ทั้งคู่ล้มเหลว การแยกข้อกังวลที่จำเป็นสามารถทำได้ง่ายขึ้นโดยการมี INTERFACES สองรายการในอ็อบเจ็กต์เดียวนั่นคือ "Domain Interface" และ "ViewModel Interface" ออบเจ็กต์นี้สามารถส่งผ่านไปยังทั้งตรรกะทางธุรกิจและตรรกะของมุมมองโดยไม่สับสนหรือขาดการซิงโครไนซ์ ออบเจ็กต์นั้นเป็น "อ็อบเจ็กต์เอกลักษณ์" - เป็นตัวแทนของเอนทิตี การรักษาการแยกรหัสโดเมนเทียบกับรหัสมุมมองนั้นต้องการเครื่องมือที่ดีกว่าในการทำเช่นนั้นภายในชั้นเรียน
ToolmakerSteve

คำตอบ:


61

หากคุณต้องการให้โมเดลของคุณแจ้งเตือน ViewModels เกี่ยวกับการเปลี่ยนแปลงควรใช้INotifyPropertyChangedและ ViewModels ควรสมัครรับการแจ้งเตือน PropertyChange

รหัสของคุณอาจมีลักษณะดังนี้:

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

แต่โดยทั่วไปแล้วสิ่งนี้จำเป็นก็ต่อเมื่อมีมากกว่าหนึ่งวัตถุที่จะทำการเปลี่ยนแปลงข้อมูลของ Model ซึ่งโดยปกติแล้วจะไม่เป็นเช่นนั้น

หากคุณเคยมีกรณีที่คุณไม่จริงมีการอ้างอิงถึงรุ่นทรัพย์สินของคุณจะแนบเหตุการณ์ PropertyChanged กับมันแล้วคุณสามารถใช้ระบบการส่งข้อความเช่นปริซึมEventAggregatorหรือ MVVM Messengerแสง

ฉันมีภาพรวมคร่าวๆของระบบการส่งข้อความในบล็อกของฉันอย่างไรก็ตามเมื่อสรุปแล้ววัตถุใด ๆ ก็สามารถถ่ายทอดข้อความได้และวัตถุใด ๆ ก็สามารถสมัครรับฟังข้อความเฉพาะได้ ดังนั้นคุณอาจออกอากาศPlayerScoreHasChangedMessageจากออบเจ็กต์หนึ่งและอีกอ็อบเจกต์สามารถสมัครรับข้อความประเภทเหล่านั้นและอัปเดตPlayerScoreคุณสมบัติเมื่อได้ยิน

แต่ฉันไม่คิดว่าสิ่งนี้จำเป็นสำหรับระบบที่คุณได้อธิบายไว้

ในโลก MVVM ในอุดมคติแอปพลิเคชันของคุณประกอบด้วย ViewModels ของคุณและโมเดลของคุณเป็นเพียงบล็อกที่ใช้ในการสร้างแอปพลิเคชันของคุณ โดยทั่วไปจะมีเฉพาะข้อมูลดังนั้นจะไม่มีเมธอดเช่นDrawCard()(ซึ่งจะอยู่ใน ViewModel)

ดังนั้นคุณอาจมีวัตถุข้อมูล Model ธรรมดาเช่นนี้:

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

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

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

และคุณมีวัตถุ ViewModel เช่น

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

(วัตถุข้างต้นควรใช้ทั้งหมดINotifyPropertyChangedแต่ฉันทิ้งมันไว้เพื่อความเรียบง่าย)


3
โดยทั่วไปแล้วตรรกะ / กฎทางธุรกิจทั้งหมดอยู่ในโมเดลหรือไม่? ตรรกะทั้งหมดไปที่ใดที่บอกว่าคุณสามารถรับไพ่ได้ถึง 21 (แต่เจ้ามือยังคงอยู่ที่ 17) คุณสามารถแยกไพ่ได้ ฯลฯ ฉันคิดว่ามันทั้งหมดอยู่ในคลาสโมเดลและด้วยเหตุนั้นฉันรู้สึกว่าฉันต้องการ คลาสคอนโทรลเลอร์ BlacJackGame ในโมเดล ฉันยังคงพยายามทำความเข้าใจและขอขอบคุณตัวอย่าง / ข้อมูลอ้างอิง แนวคิดของแบล็คแจ็คเป็นตัวอย่างที่ยกมาจากคลาส iTunes ในการเขียนโปรแกรม iOS ซึ่งตรรกะ / กฎทางธุรกิจนั้นแน่นอนที่สุดในคลาสโมเดลของรูปแบบ MVC
Dave

3
@Dave ใช่DrawCard()วิธีการจะอยู่ใน ViewModel พร้อมกับตรรกะเกมอื่น ๆ ของคุณ ในแอปพลิเคชัน MVVM ในอุดมคติคุณควรจะสามารถเรียกใช้แอปพลิเคชันของคุณได้โดยไม่ต้องใช้ UI ทั้งหมดเพียงแค่สร้าง ViewModels และเรียกใช้วิธีการเช่นผ่านสคริปต์ทดสอบหรือหน้าต่างพรอมต์คำสั่ง โดยทั่วไปโมเดลเป็นเพียงโมเดลข้อมูลที่มีข้อมูลดิบและการตรวจสอบข้อมูลพื้นฐาน
Rachel

6
ขอบคุณ Rachel สำหรับความช่วยเหลือทั้งหมด ฉันจะต้องค้นคว้าเพิ่มเติมหรือเขียนคำถามอื่น ฉันยังสับสนกับตำแหน่งของตรรกะของเกม คุณ (และคนอื่น ๆ ) สนับสนุนให้ใส่ไว้ใน ViewModel คนอื่น ๆ พูดว่า "ตรรกะทางธุรกิจ" ซึ่งในกรณีของฉันฉันถือว่าเป็นกฎของเกมและสถานะของเกมที่อยู่ในโมเดล (ดูตัวอย่างเช่น: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) และstackoverflow.com/questions/10964003/… ). ฉันตระหนักดีว่าในเกมง่ายๆนี้มันอาจจะไม่สำคัญมากนัก แต่จะดีที่ได้รู้ ขอบคุณ!
Dave

1
@Dave ฉันอาจใช้คำว่า "ตรรกะทางธุรกิจ" ไม่ถูกต้องและผสมกับตรรกะของแอปพลิเคชัน หากต้องการอ้างอิงบทความ MSDN ที่คุณเชื่อมโยง"เพื่อเพิ่มโอกาสในการใช้ซ้ำให้มากที่สุดโมเดลไม่ควรมีลักษณะการทำงานหรือตรรกะของแอปพลิเคชันเฉพาะกรณีใช้งานหรือผู้ใช้โดยเฉพาะ"และ"โดยทั่วไปแล้วโมเดลมุมมองจะกำหนดคำสั่งหรือการดำเนินการที่สามารถแสดงได้ ใน UI และให้ผู้ใช้สามารถเรียกใช้" ดังนั้นสิ่งต่างๆเช่น a DrawCardCommand()จะอยู่ใน ViewModel แต่ฉันเดาว่าคุณอาจมีBlackjackGameModelวัตถุที่มีDrawCard()วิธีการที่คำสั่งเรียกว่าถ้าคุณต้องการ
Rachel

2
หลีกเลี่ยงการรั่วไหลของหน่วยความจำ ใช้รูปแบบ WeakEvent joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

คำตอบสั้น ๆ : ขึ้นอยู่กับลักษณะเฉพาะ

ในตัวอย่างของคุณโมเดลกำลังได้รับการอัปเดต "ด้วยตัวมันเอง" และแน่นอนว่าการเปลี่ยนแปลงเหล่านี้จำเป็นต้องเผยแพร่ไปยังมุมมอง เนื่องจากข้อมูลพร็อพเพอร์ตี้สามารถเข้าถึงได้โดยตรงเท่านั้นมุมมองโมเดลจึงหมายความว่าโมเดลต้องสื่อสารการเปลี่ยนแปลงเหล่านี้กับโมเดลวิวที่เกี่ยวข้อง แน่นอนว่ากลไกที่กำหนดไว้สำหรับการทำเช่นนั้นINotifyPropertyChangedซึ่งหมายความว่าคุณจะได้รับขั้นตอนการทำงานดังนี้:

  1. Viewmodel ถูกสร้างขึ้นและปิดโมเดล
  2. Viewmodel สมัครเข้าPropertyChangedร่วมกิจกรรมของนางแบบ
  3. Viewmodel ถูกตั้งค่าเป็นมุมมองDataContextคุณสมบัติถูกผูกไว้ ฯลฯ
  4. ดูการดำเนินการทริกเกอร์บน viewmodel
  5. Viewmodel เรียกวิธีการในแบบจำลอง
  6. โมเดลปรับปรุงตัวเอง
  7. Viewmodel จัดการกับโมเดลPropertyChangedและยกระดับของตัวเองPropertyChangedเพื่อตอบสนอง
  8. มุมมองแสดงถึงการเปลี่ยนแปลงในการเชื่อมโยงปิดลูปข้อเสนอแนะ

ในทางกลับกันหากโมเดลของคุณมีตรรกะทางธุรกิจเพียงเล็กน้อย (หรือไม่มีเลย) หรือด้วยเหตุผลอื่น ๆ (เช่นการเพิ่มความสามารถในการทำธุรกรรม) คุณตัดสินใจที่จะปล่อยให้โมเดลมุมมองแต่ละตัว "เป็นเจ้าของ" โมเดลที่ถูกห่อไว้การปรับเปลี่ยนโมเดลทั้งหมดจะผ่านไป viewmodel ดังนั้นการจัดเรียงดังกล่าวจึงไม่จำเป็น

ผมอธิบายการออกแบบดังกล่าวในคำถาม MVVM อีกที่นี่


สวัสดีรายการที่คุณทำนั้นยอดเยี่ยมมาก อย่างไรก็ตามฉันมีปัญหากับ 7. และ 8 โดยเฉพาะ: ฉันมี ViewModel ที่ไม่ได้ใช้ INotifyPropertyChanged ประกอบด้วยรายชื่อชายด์ซึ่งมีรายชื่อชายด์ (ใช้เป็น ViewModel สำหรับคอนโทรล WPF Treeview) ฉันจะทำให้ UserControl DataContext ViewModel "รับฟัง" การเปลี่ยนแปลงคุณสมบัติในลูก ๆ (TreeviewItems) ได้อย่างไร ฉันจะสมัครสมาชิกองค์ประกอบลูกทั้งหมดได้อย่างไรใครเป็นผู้ใช้ INotifyPropertyChanged หรือควรแยกคำถาม?
อิกอร์

4

ทางเลือกของคุณ:

  • ใช้งาน INotifyPropertyChanged
  • เหตุการณ์
  • POCO พร้อมตัวจัดการพร็อกซี

อย่างที่ฉันเห็นมันINotifyPropertyChangedเป็นส่วนพื้นฐานของ. Net คือมันอยู่ในSystem.dll. การนำไปใช้ใน "โมเดล" ของคุณนั้นคล้ายกับการนำโครงสร้างเหตุการณ์ไปใช้

หากคุณต้องการ POCO ที่บริสุทธิ์คุณจะต้องจัดการวัตถุของคุณผ่านพร็อกซี / บริการอย่างมีประสิทธิภาพจากนั้น ViewModel ของคุณจะได้รับแจ้งการเปลี่ยนแปลงโดยการฟังพร็อกซี

โดยส่วนตัวแล้วฉันเพิ่งใช้ INotifyPropertyChanged แบบหลวม ๆ จากนั้นใช้FODYเพื่อทำงานสกปรกให้ฉัน มันดูและรู้สึก POCO

ตัวอย่าง (การใช้ FODY ถึง IL สานผู้สร้างทรัพย์สินเปลี่ยนแปลง):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

จากนั้นคุณสามารถให้ ViewModel ของคุณฟัง PropertyChanged สำหรับการเปลี่ยนแปลงใด ๆ หรือการเปลี่ยนแปลงคุณสมบัติเฉพาะ

ความงามของเส้นทาง INotifyPropertyChanged เป็นคุณโซ่ขึ้นกับObservableCollection ขยาย ดังนั้นคุณจึงทิ้งวัตถุ poco ที่อยู่ใกล้ของคุณลงในคอลเลกชันและฟังคอลเลคชัน ... หากมีอะไรเปลี่ยนแปลงคุณจะได้เรียนรู้เกี่ยวกับมัน

ฉันจะพูดตามตรงว่าสิ่งนี้สามารถเข้าร่วมการสนทนา "ทำไม INotifyPropertyChanged ไม่ได้รับการจัดการโดยอัตโนมัติโดยคอมไพเลอร์" ซึ่งเกี่ยวข้องกับ: ทุกออบเจ็กต์ใน c # ควรมีสิ่งอำนวยความสะดวกในการแจ้งเตือนหากมีการเปลี่ยนแปลงส่วนใดส่วนหนึ่ง เช่นใช้ INotifyPropertyChanged โดยค่าเริ่มต้น แต่วิธีที่ดีที่สุดที่ต้องใช้ความพยายามน้อยที่สุดคือการใช้ IL Weaving (โดยเฉพาะFODY )


4

กระทู้เก่าพอสมควร แต่หลังจากค้นหามาหลายครั้งฉันก็ได้หาวิธีแก้ปัญหาของตัวเอง: PropertyChangedProxy

ด้วยคลาสนี้คุณสามารถลงทะเบียนกับ NotifyPropertyChanged ของบุคคลอื่นได้อย่างง่ายดายและดำเนินการที่เหมาะสมหากถูกไล่ออกสำหรับคุณสมบัติที่ลงทะเบียน

ต่อไปนี้เป็นตัวอย่างของคุณสมบัติของโมเดลเมื่อคุณมีคุณสมบัติของโมเดล "สถานะ" ซึ่งสามารถเปลี่ยนแปลงได้ด้วยตัวเองจากนั้นควรแจ้งให้ ViewModel เริ่มการทำงานของ PropertyChanged ของตัวเองโดยอัตโนมัติบนคุณสมบัติ "สถานะ" เพื่อให้มุมมองได้รับการแจ้งเตือนด้วย: )

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

และนี่คือชั้นเรียนเอง:

/// <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
หลีกเลี่ยงการรั่วไหลของหน่วยความจำ ใช้รูปแบบ WeakEvent joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS - OTOH พิจารณารูปแบบการจัดกิจกรรมที่อ่อนแอเป็นอันตราย โดยส่วนตัวแล้วฉันควรเสี่ยงต่อการรั่วไหลของหน่วยความจำหากลืมยกเลิกการลงทะเบียน ( -= my_event_handler) เพราะมันง่ายต่อการติดตามมากกว่าปัญหาซอมบี้ที่หายากและคาดเดาไม่ได้ซึ่งอาจหรืออาจไม่เคยเกิดขึ้น
ToolmakerSteve

@ToolmakerSteve ขอบคุณสำหรับการเพิ่มอาร์กิวเมนต์ที่สมดุล ฉันขอแนะนำให้นักพัฒนาทำสิ่งที่ดีที่สุดสำหรับพวกเขาในสถานการณ์ของพวกเขาเอง อย่าใช้ซอร์สโค้ดจากอินเทอร์เน็ตแบบสุ่มสี่สุ่มห้า มีรูปแบบอื่น ๆ เช่น EventAggregator / EventBus ที่ใช้กันทั่วไปในการส่งข้อความข้ามองค์ประกอบ (ซึ่งเกิดขึ้นด้วยความเสี่ยงของตัวเอง)
JJS

2

ฉันพบว่าบทความนี้มีประโยชน์: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

สรุปของฉัน:

แนวคิดเบื้องหลังองค์กร MVVM คือการอนุญาตให้นำมุมมองและแบบจำลองกลับมาใช้ใหม่ได้ง่ายขึ้นและยังอนุญาตให้มีการทดสอบแบบแยกส่วน view-model ของคุณเป็นโมเดลที่แสดงถึงเอนทิตีมุมมองโมเดลของคุณแสดงถึงเอนทิตีทางธุรกิจ

จะเป็นอย่างไรหากคุณต้องการสร้างเกมโป๊กเกอร์ในภายหลัง? UI ส่วนใหญ่ควรใช้ซ้ำได้ หากตรรกะของเกมของคุณถูกผูกไว้ในโมเดลมุมมองของคุณมันจะยากมากที่จะนำองค์ประกอบเหล่านั้นกลับมาใช้ใหม่โดยไม่ต้องตั้งโปรแกรมมุมมองโมเดลใหม่ ถ้าคุณต้องการเปลี่ยนอินเทอร์เฟซผู้ใช้ของคุณล่ะ? หากตรรกะของเกมของคุณเชื่อมโยงกับตรรกะของโมเดลมุมมองคุณจะต้องตรวจสอบอีกครั้งว่าเกมของคุณยังใช้งานได้ จะเป็นอย่างไรหากคุณต้องการสร้างเดสก์ท็อปและเว็บแอป หากโมเดลมุมมองของคุณมีตรรกะของเกมการพยายามรักษาทั้งสองแอปพลิเคชันไว้เคียงข้างกันจะกลายเป็นเรื่องซับซ้อนเนื่องจากตรรกะของแอปพลิเคชันจะเชื่อมโยงกับตรรกะทางธุรกิจในมุมมองโมเดลอย่างหลีกเลี่ยงไม่ได้

การแจ้งเตือนการเปลี่ยนแปลงข้อมูลและการตรวจสอบข้อมูลจะเกิดขึ้นในทุกเลเยอร์ (มุมมองมุมมองโมเดลและโมเดล)

แบบจำลองประกอบด้วยการแสดงข้อมูลของคุณ (เอนทิตี) และตรรกะทางธุรกิจเฉพาะสำหรับเอนทิตีเหล่านั้น สำรับไพ่เป็น 'สิ่งของ' เชิงตรรกะที่มีคุณสมบัติโดยธรรมชาติ เด็คที่ดีไม่สามารถใส่การ์ดซ้ำลงไปได้ ต้องเปิดเผยวิธีรับการ์ดบนสุด ต้องรู้ว่าอย่าแจกการ์ดมากกว่าที่เหลืออยู่ พฤติกรรมของเด็คดังกล่าวเป็นส่วนหนึ่งของแบบจำลองเนื่องจากมีอยู่ในสำรับไพ่ นอกจากนี้ยังมีโมเดลตัวแทนจำหน่ายโมเดลผู้เล่นโมเดลมือ ฯลฯ โมเดลเหล่านี้สามารถและจะโต้ตอบ

โมเดลมุมมองจะประกอบด้วยการนำเสนอและตรรกะของแอปพลิเคชัน งานทั้งหมดที่เกี่ยวข้องกับการแสดงเกมนั้นแยกจากตรรกะของเกม ซึ่งอาจรวมถึงการแสดงมือเป็นรูปภาพการขอไพ่ไปยังรุ่นตัวแทนจำหน่ายการตั้งค่าการแสดงผลของผู้ใช้เป็นต้น

ความกล้าของบทความ:

โดยพื้นฐานแล้ววิธีที่ฉันต้องการอธิบายสิ่งนี้คือตรรกะทางธุรกิจและเอนทิตีของคุณประกอบด้วยแบบจำลอง นี่คือสิ่งที่แอปพลิเคชันเฉพาะของคุณใช้ แต่สามารถแชร์กับแอปพลิเคชันต่างๆได้

View เป็นเลเยอร์การนำเสนอ - สิ่งที่เกี่ยวข้องกับการเชื่อมต่อโดยตรงกับผู้ใช้

ViewModel นั้นเป็น "กาว" เฉพาะสำหรับแอปพลิเคชันของคุณที่เชื่อมโยงทั้งสองเข้าด้วยกัน

ฉันมีแผนภาพที่ดีที่นี่ซึ่งแสดงให้เห็นว่าพวกเขาเชื่อมต่ออย่างไร:

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

ในกรณีของคุณ - ให้จัดการเฉพาะบางส่วน ...

การตรวจสอบความถูกต้อง: โดยทั่วไปมี 2 รูปแบบ การตรวจสอบความถูกต้องที่เกี่ยวข้องกับการป้อนข้อมูลของผู้ใช้จะเกิดขึ้นใน ViewModel (โดยหลัก) และ View (เช่นกล่องข้อความ "ตัวเลข" ที่ป้องกันไม่ให้ป้อนข้อความจะถูกจัดการให้คุณในมุมมองเป็นต้น) ด้วยเหตุนี้การตรวจสอบความถูกต้องของอินพุตจากผู้ใช้จึงเป็นปัญหาของ VM ดังที่กล่าวมามักจะมีการตรวจสอบ "เลเยอร์" ที่สองซึ่งเป็นการตรวจสอบความถูกต้องว่าข้อมูลที่ใช้นั้นตรงกับกฎทางธุรกิจ ซึ่งมักจะเป็นส่วนหนึ่งของโมเดลเมื่อคุณพุชข้อมูลไปยังโมเดลของคุณอาจทำให้เกิดข้อผิดพลาดในการตรวจสอบความถูกต้อง จากนั้น VM จะต้องทำการแมปข้อมูลนี้ใหม่กลับไปที่ View

การดำเนินงาน "เบื้องหลังโดยไม่มีมุมมองเช่นการเขียนไปยังฐานข้อมูลการส่งอีเมล ฯลฯ ": นี่เป็นส่วนหนึ่งของ "การดำเนินการเฉพาะโดเมน" ในแผนภาพของฉันและเป็นส่วนหนึ่งของโมเดลอย่างแท้จริง นี่คือสิ่งที่คุณพยายามเปิดเผยผ่านแอปพลิเคชัน ViewModel ทำหน้าที่เป็นสะพานเชื่อมเพื่อแสดงข้อมูลนี้ แต่การดำเนินการเป็นแบบจำลองที่บริสุทธิ์

การดำเนินการสำหรับ ViewModel: ViewModel ต้องการมากกว่า INPC แต่ยังต้องการการดำเนินการใด ๆ ที่เฉพาะเจาะจงสำหรับแอปพลิเคชันของคุณ (ไม่ใช่ตรรกะทางธุรกิจของคุณ) เช่นการตั้งค่าการบันทึกและสถานะผู้ใช้เป็นต้นซึ่งจะแตกต่างกันไปในแต่ละแอป โดยแอปแม้ว่าจะเชื่อมต่อกับ "โมเดล" เดียวกันก็ตาม

วิธีคิดที่ดี - สมมติว่าคุณต้องการสร้างระบบการสั่งซื้อ 2 เวอร์ชัน อันแรกอยู่ใน WPF และอันที่สองคือเว็บอินเตอร์เฟส

ตรรกะที่ใช้ร่วมกันที่เกี่ยวข้องกับคำสั่งซื้อ (การส่งอีเมลการเข้าสู่ฐานข้อมูล ฯลฯ ) คือ Model แอปพลิเคชันของคุณกำลังเปิดเผยการดำเนินการและข้อมูลเหล่านี้แก่ผู้ใช้ แต่ทำได้ 2 วิธี

ในแอปพลิเคชัน WPF อินเทอร์เฟซผู้ใช้ (สิ่งที่ผู้ดูโต้ตอบ) คือ "มุมมอง" - ในเว็บแอปพลิเคชันโดยพื้นฐานแล้วจะเป็นรหัสที่ (อย่างน้อยที่สุด) จะเปลี่ยนเป็น javascript + html + css บนไคลเอนต์

ViewModel เป็นส่วนที่เหลือของ "กาว" ที่จำเป็นในการปรับเปลี่ยนโมเดลของคุณ (การดำเนินการเหล่านี้เกี่ยวข้องกับการสั่งซื้อ) เพื่อให้สามารถทำงานร่วมกับเทคโนโลยี / เลเยอร์มุมมองเฉพาะที่คุณใช้อยู่


บางทีตัวอย่างง่ายๆก็คือเครื่องเล่นเพลง โมเดลของคุณจะมีไลบรารีและไฟล์เสียงที่ใช้งานอยู่และตัวแปลงสัญญาณและตรรกะของเครื่องเล่นและรหัสประมวลผลสัญญาณดิจิทัล โมเดลมุมมองจะมีการควบคุมและการแสดงภาพและเบราว์เซอร์ไลบรารีของคุณ มีตรรกะ UI จำนวนมากที่จำเป็นในการแสดงข้อมูลทั้งหมดนั้นและเป็นการดีที่จะให้โปรแกรมเมอร์คนหนึ่งมุ่งเน้นไปที่การเล่นเพลงในขณะที่ให้โปรแกรมเมอร์คนอื่นมุ่งเน้นไปที่การทำให้ UI ใช้งานง่ายและสนุกสนาน View-model และ model ควรอนุญาตให้โปรแกรมเมอร์สองคนนั้นเห็นด้วยกับชุดอินเทอร์เฟซและทำงานแยกกัน
VoteCoffee

อีกตัวอย่างที่ดีคือหน้าเว็บ ตรรกะฝั่งเซิร์ฟเวอร์โดยทั่วไปเทียบเท่ากับโมเดล โดยทั่วไปตรรกะฝั่งไคลเอ็นต์จะเทียบเท่ากับ view-model ฉันนึกภาพง่ายๆว่าตรรกะของเกมจะอยู่บนเซิร์ฟเวอร์และไม่ได้รับความไว้วางใจจากไคลเอนต์
VoteCoffee

2

การแจ้งเตือนตามINotifyPropertyChangedและINotifyCollectionChangedคือสิ่งที่คุณต้องการ ที่จะทำให้ชีวิตของคุณด้วยการสมัครสมาชิกการเปลี่ยนแปลงสถานที่ให้บริการการตรวจสอบรวบรวมเวลาของชื่อคุณสมบัติหลีกเลี่ยงการรั่วไหลของหน่วยความจำที่ผมจะแนะนำให้คุณใช้PropertyObserverจากจอชสมิ ธ มูลนิธิ เนื่องจากโปรเจ็กต์นี้เป็นโอเพนซอร์สคุณสามารถเพิ่มคลาสนั้นลงในโปรเจ็กต์ของคุณจากแหล่งที่มาได้

เพื่อให้เข้าใจถึงวิธีการใช้ PropertyObserver อ่านบทความนี้

นอกจากนี้มีลักษณะลึกที่ปฏิกิริยา Extensions (Rx) คุณสามารถเปิดเผยIObserver <T>จากโมเดลของคุณและสมัครรับข้อมูลจากโมเดลของคุณในมุมมอง


ขอบคุณมากสำหรับการอ้างอิงบทความที่ยอดเยี่ยมของ Josh Smiths และครอบคลุม Weak Events!
JJS

1

พวกเขาทำงานได้อย่างยอดเยี่ยมในการตอบคำถามนี้ แต่ในสถานการณ์เช่นนี้ฉันรู้สึกจริงๆว่ารูปแบบ MVVM เป็นความเจ็บปวดดังนั้นฉันจะไปใช้ Supervising Controller หรือวิธี Passive View และปล่อยระบบการผูกมัดอย่างน้อยสำหรับวัตถุโมเดลที่ สร้างการเปลี่ยนแปลงด้วยตัวเอง


1

ฉันสนับสนุนรูปแบบทิศทาง -> ดูโมเดล -> ดูขั้นตอนการเปลี่ยนแปลงมานานแล้วดังที่คุณเห็นในส่วนการไหลของการเปลี่ยนแปลงของบทความ MVVMของฉันตั้งแต่ปี 2008 สิ่งนี้จำเป็นต้องมีการใช้งานINotifyPropertyChangedกับโมเดล เท่าที่ฉันสามารถบอกได้มันกลายเป็นเรื่องธรรมดาไปแล้ว

เพราะคุณกล่าวถึงจอชสมิ ธ จะดูที่ชั้น PropertyChanged ของเขา เป็นคลาสผู้ช่วยในการสมัครเข้าร่วมINotifyPropertyChanged.PropertyChangedกิจกรรมของโมเดล

จริงๆคุณสามารถใช้วิธีการนี้อีกมากที่ผมมี recenty โดยการสร้างชั้น PropertiesUpdater ของฉัน คุณสมบัติบนโมเดลมุมมองถูกคำนวณเป็นนิพจน์ที่ซับซ้อนซึ่งรวมคุณสมบัติอย่างน้อยหนึ่งรายการบนโมเดล


1

ไม่มีอะไรผิดในการใช้INotifyPropertyChangedภายใน Model และฟังจาก ViewModel ในความเป็นจริงคุณสามารถกำหนดคุณสมบัติของโมเดลได้ใน XAML: {Binding Model.ModelProperty}

สำหรับการคำนวณขึ้นอยู่กับคุณสมบัติ / อ่านอย่างเดียวโดยไกลฉันไม่ได้เห็นอะไรที่ดีกว่าและง่ายกว่านี้: https://github.com/StephenCleary/CalculatedProperties มันง่ายมาก แต่มีประโยชน์อย่างไม่น่าเชื่อมันเป็น "สูตร Excel สำหรับ MVVM" ซึ่งใช้งานได้เช่นเดียวกับ Excel ที่เผยแพร่การเปลี่ยนแปลงไปยังเซลล์สูตรโดยไม่ต้องใช้ความพยายามใด ๆ


0

คุณสามารถเพิ่มเหตุการณ์จากแบบจำลองซึ่ง viewmodel จะต้องสมัครสมาชิก

ตัวอย่างเช่นเมื่อเร็ว ๆ นี้ฉันได้ทำงานในโครงการที่ฉันต้องสร้างมุมมองแบบต้นไม้ (โดยธรรมชาติแล้วโมเดลมีลักษณะเป็นลำดับชั้น) ในรูปแบบที่ฉันมี ObservableCollection ChildElementsที่เรียกว่า

ใน viewmodel ฉันได้เก็บการอ้างอิงถึงวัตถุในโมเดลและสมัครรับCollectionChangedเหตุการณ์ของคอลเลกชันที่สังเกตได้ดังนี้: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

จากนั้น viewmodel ของคุณจะได้รับการแจ้งเตือนโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงเกิดขึ้นในโมเดล คุณสามารถทำตามแนวคิดเดียวกันโดยใช้PropertyChangedแต่คุณจะต้องเพิ่มเหตุการณ์การเปลี่ยนแปลงคุณสมบัติจากแบบจำลองของคุณอย่างชัดเจนเพื่อให้สามารถใช้งานได้


หากการจัดการกับข้อมูลแบบลำดับชั้นที่คุณจะต้องการที่จะดูที่การสาธิต 2ของบทความ MVVM ของฉัน
HappyNomad

0

ดูเหมือนว่าฉันจะเป็นคำถามที่สำคัญมาก - แม้ว่าจะไม่มีแรงกดดันให้ทำก็ตาม ฉันกำลังทำโครงการทดสอบซึ่งเกี่ยวข้องกับ TreeView มีรายการเมนูและสิ่งที่แมปกับคำสั่งเช่น Delete ขณะนี้ฉันกำลังอัปเดตทั้งโมเดลและโมเดลมุมมองจากภายในโมเดลมุมมอง

ตัวอย่างเช่น,

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
}

นี่เป็นเรื่องง่าย แต่ดูเหมือนจะมีข้อบกพร่องพื้นฐานมาก การทดสอบหน่วยโดยทั่วไปจะดำเนินการคำสั่งจากนั้นตรวจสอบผลลัพธ์ในโมเดลมุมมอง แต่นี่ไม่ได้ทดสอบว่าการอัปเดตโมเดลนั้นถูกต้องเนื่องจากทั้งสองได้รับการอัปเดตพร้อมกัน

ดังนั้นอาจจะดีกว่าถ้าใช้เทคนิคเช่น PropertyObserver เพื่อให้การอัปเดตโมเดลทริกเกอร์การอัปเดตโมเดลมุมมอง ตอนนี้การทดสอบหน่วยเดียวกันจะใช้ได้ผลก็ต่อเมื่อการกระทำทั้งสองสำเร็จ

นี่ไม่ใช่คำตอบที่เป็นไปได้ฉันรู้ แต่มันก็คุ้มค่าที่จะออกไปที่นั่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.