น่าเสียดายที่ไม่มีแอพตัวอย่าง MVVM ที่ยอดเยี่ยมที่ทำทุกอย่างและมีวิธีการต่าง ๆ มากมายในการทำสิ่งต่าง ๆ ก่อนอื่นคุณอาจต้องการทำความคุ้นเคยกับกรอบแอพหนึ่ง (Prism เป็นตัวเลือกที่เหมาะสม) เพราะพวกเขามีเครื่องมือที่สะดวกเช่นการฉีดพึ่งพาการสั่งการรวมเหตุการณ์และอื่น ๆ เพื่อลองรูปแบบที่แตกต่างกันที่เหมาะกับคุณ .
รุ่นปริซึม:
http://www.codeplex.com/CompositeWPF
มันมีแอพตัวอย่างที่ดีพอสมควร (ผู้ค้าหุ้น) พร้อมกับตัวอย่างที่มีขนาดเล็กจำนวนมากและวิธีการ อย่างน้อยที่สุดเป็นการสาธิตที่ดีของรูปแบบย่อยทั่วไปหลายอย่างที่ใช้เพื่อให้ MVVM ใช้งานได้จริง พวกเขามีตัวอย่างทั้ง CRUD และกล่องโต้ตอบฉันเชื่อ
ปริซึมไม่จำเป็นสำหรับทุกโครงการ แต่เป็นการดีที่จะทำความคุ้นเคย
CRUD:
ส่วนนี้ค่อนข้างง่าย WPF การเชื่อมสองทางทำให้ง่ายต่อการแก้ไขข้อมูลส่วนใหญ่ เคล็ดลับที่แท้จริงคือการจัดทำแบบจำลองที่ทำให้ติดตั้ง UI ได้ง่าย อย่างน้อยที่สุดคุณต้องการให้แน่ใจว่า ViewModel (หรือวัตถุทางธุรกิจ) ของคุณใช้INotifyPropertyChanged
เพื่อรองรับการเชื่อมโยงและคุณสามารถเชื่อมโยงคุณสมบัติกับตัวควบคุม UI ได้โดยตรง แต่คุณอาจต้องการนำไปใช้IDataErrorInfo
สำหรับการตรวจสอบความถูกต้อง โดยทั่วไปหากคุณใช้ ORM บางประเภทในการตั้งค่า CRUD จะเป็นการถ่ายภาพ
บทความนี้แสดงให้เห็นถึงการดำเนินงานที่เรียบง่าย crud:
http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx
มันถูกสร้างขึ้นบน LinqToSql แต่นั่นไม่เกี่ยวข้องกับตัวอย่าง - ทั้งหมดที่สำคัญคือวัตถุทางธุรกิจของคุณใช้INotifyPropertyChanged
(ซึ่งคลาสที่สร้างโดย LinqToSql ทำ) MVVM ไม่ได้เป็นตัวอย่าง แต่ฉันไม่คิดว่ามันจะสำคัญในกรณีนี้
บทความนี้แสดงให้เห็นถึงการตรวจสอบข้อมูล
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx
อีกครั้งโซลูชัน ORM ส่วนใหญ่จะสร้างคลาสที่ใช้งานอยู่แล้วIDataErrorInfo
และโดยปกติจะมีกลไกเพื่อให้ง่ายต่อการเพิ่มกฎการตรวจสอบความถูกต้องที่กำหนดเอง
เวลาส่วนใหญ่คุณสามารถนำอ็อบเจกต์ (โมเดล) ที่สร้างโดย ORM บางส่วนมารวมไว้ใน ViewModel ที่เก็บมันและคำสั่งสำหรับบันทึก / ลบ - และคุณพร้อมที่จะผูก UI ตรงกับคุณสมบัติของโมเดล
มุมมองจะมีลักษณะเช่นนี้ (ViewModel มีคุณสมบัติItem
ที่เก็บแบบจำลองเช่นคลาสที่สร้างขึ้นใน ORM):
<StackPanel>
<StackPanel DataContext=Item>
<TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<Button Command="{Binding SaveCommand}" />
<Button Command="{Binding CancelCommand}" />
</StackPanel>
Dialogs:
Dialogs และ MVVM นั้นค่อนข้างยุ่งยาก ฉันชอบที่จะใช้รสชาติของวิธีการไกล่เกลี่ยกับกล่องโต้ตอบคุณสามารถอ่านเพิ่มเติมเกี่ยวกับมันได้ในคำถาม StackOverflow:
ตัวอย่างกล่องโต้ตอบ WPF MVVM
วิธีปกติของฉันซึ่งไม่ใช่ MVVM แบบคลาสสิกสามารถสรุปได้ดังนี้:
คลาสพื้นฐานสำหรับไดอะล็อก ViewModel ที่แสดงคำสั่งสำหรับการกระทำและยกเลิกการกระทำเหตุการณ์เพื่อให้มุมมองทราบว่าไดอะล็อกพร้อมที่จะปิดและสิ่งอื่นที่คุณต้องการในไดอะล็อกของคุณทั้งหมด
มุมมองทั่วไปสำหรับไดอะล็อกของคุณซึ่งอาจเป็นหน้าต่างหรือตัวควบคุมชนิดโอเวอร์เลย์ "โมดอล" แบบกำหนดเอง หัวใจของมันคือการนำเสนอเนื้อหาที่เราถ่ายโอน viewmodel เข้าไปและจัดการการเดินสายสำหรับการปิดหน้าต่าง - ตัวอย่างเช่นการเปลี่ยนแปลงบริบทข้อมูลคุณสามารถตรวจสอบว่า ViewModel ใหม่ได้รับการสืบทอดมาจากคลาสพื้นฐานของคุณหรือไม่ สมัครสมาชิกกับเหตุการณ์ปิดที่เกี่ยวข้อง (ตัวจัดการจะกำหนดผลการโต้ตอบ) หากคุณมีฟังก์ชั่นการปิด Universal ที่เป็นทางเลือก (เช่นปุ่ม X) คุณควรตรวจสอบให้แน่ใจว่าได้รันคำสั่งปิดที่เกี่ยวข้องใน ViewModel ด้วยเช่นกัน
ที่ใดที่หนึ่งที่คุณต้องการให้เทมเพลตข้อมูลสำหรับ ViewModels ของคุณสามารถทำได้ง่ายมากโดยเฉพาะอย่างยิ่งเนื่องจากคุณอาจมีมุมมองสำหรับแต่ละกล่องโต้ตอบที่ห่อหุ้มในตัวควบคุมแยกต่างหาก เทมเพลตข้อมูลเริ่มต้นสำหรับ ViewModel จะมีลักษณะดังนี้:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
มุมมองไดอะล็อกจำเป็นต้องเข้าถึงสิ่งเหล่านี้เนื่องจากไม่เช่นนั้นจะไม่รู้วิธีแสดง ViewModel นอกเหนือจาก UI ของไดอะล็อกที่แบ่งใช้แล้วเนื้อหาจะเป็นแบบนี้:
<ContentControl Content="{Binding}" />
เทมเพลตข้อมูลโดยนัยจะแมปมุมมองกับโมเดล แต่ใครเป็นผู้เปิดใช้
นี่คือส่วนที่ไม่ให้ mvvm วิธีหนึ่งในการทำเช่นนี้คือการใช้เหตุการณ์ระดับโลก สิ่งที่ฉันคิดว่าเป็นสิ่งที่ดีกว่าที่จะทำคือใช้การตั้งค่าประเภทตัวรวบรวมเหตุการณ์ที่ให้ผ่านการฉีดขึ้นรูป - วิธีนี้เหตุการณ์เป็นแบบสากลไปยังคอนเทนเนอร์ไม่ใช่แอปทั้งหมด ปริซึมใช้กรอบความสามัคคีสำหรับซีแมนทิกส์คอนเทนเนอร์และการฉีดพึ่งพาและโดยรวมแล้วฉันชอบ Unity ไม่มาก
โดยปกติแล้วมันจะเหมาะสมสำหรับหน้าต่างรูทเพื่อสมัครสมาชิกเหตุการณ์นี้ - มันสามารถเปิดไดอะล็อกและตั้งค่าบริบทข้อมูลเป็น ViewModel ที่ถูกส่งผ่านด้วยเหตุการณ์ที่ยกขึ้น
การตั้งค่าด้วยวิธีนี้ทำให้ ViewModels ขอให้แอปพลิเคชันเปิดกล่องโต้ตอบและตอบสนองต่อการกระทำของผู้ใช้ที่นั่นโดยไม่ทราบอะไรเกี่ยวกับ UI ดังนั้นส่วนใหญ่ MVVM-ness ยังคงสมบูรณ์
อย่างไรก็ตามมีบางครั้งที่ UI ต้องเพิ่มไดอะล็อกซึ่งอาจทำให้เรื่องยุ่งยากเล็กน้อย ยกตัวอย่างเช่นหากตำแหน่งไดอะล็อกขึ้นอยู่กับตำแหน่งของปุ่มที่เปิด ในกรณีนี้คุณต้องมีข้อมูลเฉพาะ UI บางอย่างเมื่อคุณขอเปิดกล่องโต้ตอบ โดยทั่วไปฉันสร้างคลาสแยกต่างหากที่เก็บ ViewModel และข้อมูล UI ที่เกี่ยวข้อง น่าเสียดายที่ข้อต่อบางอย่างดูเหมือนหลีกเลี่ยงไม่ได้
โค้ดหลอกของตัวจัดการปุ่มที่เพิ่มไดอะล็อกที่ต้องการข้อมูลตำแหน่งองค์ประกอบ:
ButtonClickHandler(sender, args){
var vm = DataContext as ISomeDialogProvider; // check for null
var ui_vm = new ViewModelContainer();
// assign margin, width, or anything else that your custom dialog might require
...
ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
// raise the dialog show event
}
มุมมองโต้ตอบจะผูกกับข้อมูลตำแหน่งและผ่านการมี ViewModel ContentControl
ไปภายใน ViewModel เองยังไม่รู้อะไรเกี่ยวกับ UI
โดยทั่วไปฉันไม่ได้ใช้DialogResult
คุณสมบัติส่งคืนของShowDialog()
เมธอดหรือคาดว่าเธรดจะบล็อกจนกว่าไดอะล็อกจะปิด กล่องโต้ตอบโมดอลที่ไม่ได้มาตรฐานจะไม่ทำงานเช่นนั้นเสมอไปและในสภาพแวดล้อมแบบคอมโพสิตคุณมักไม่ต้องการให้ตัวจัดการเหตุการณ์บล็อคเช่นนั้น แต่อย่างใด ฉันต้องการให้ ViewModels จัดการกับสิ่งนี้ - ผู้สร้าง ViewModel สามารถสมัครสมาชิกกับกิจกรรมที่เกี่ยวข้องกำหนดวิธีการยอมรับ / ยกเลิกเป็นต้นดังนั้นจึงไม่จำเป็นต้องพึ่งพากลไก UI นี้
ดังนั้นแทนที่จะไหลนี้:
// in code behind
var result = somedialog.ShowDialog();
if (result == ...
ฉันใช้:
// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container
ฉันชอบวิธีนี้มากเพราะบทสนทนาของฉันส่วนใหญ่ไม่ใช่การควบคุมแบบหลอกแบบโมดัลและการทำแบบนี้ดูตรงไปตรงมามากกว่าการทำงานกับมัน ง่ายต่อการทดสอบหน่วยเช่นกัน