Siapa yang harus mengontrol navigasi dalam aplikasi MVVM?

33

Contoh # 1: Saya memiliki tampilan yang ditampilkan dalam aplikasi MVVM saya (mari kita gunakan Silverlight untuk keperluan diskusi) dan saya mengklik tombol yang akan membawa saya ke halaman baru.

Contoh # 2: Tampilan yang sama memiliki tombol lain yang, ketika diklik, harus membuka tampilan detail di jendela anak (dialog).

Kita tahu bahwa akan ada objek Command yang diekspos oleh ViewModel kita yang terikat pada tombol dengan metode yang merespons klik pengguna. Tapi, bagaimana? Bagaimana kita menyelesaikan aksinya? Bahkan jika kita menggunakan layanan Navigasi, apa yang akan kita katakan?

Untuk lebih spesifik, dalam model Tampilan-pertama tradisional (seperti skema navigasi berbasis URL seperti di web atau kerangka kerja navigasi bawaan SL) objek Perintah harus tahu Tampilan apa yang akan ditampilkan selanjutnya. Itu tampaknya melewati batas ketika sampai pada pemisahan kekhawatiran yang dipromosikan oleh pola.

Di sisi lain, jika tombol tidak ditransfer ke objek Command dan berperilaku seperti hyperlink, aturan navigasi dapat didefinisikan dalam markup. Tetapi apakah kita ingin agar Views mengendalikan aliran aplikasi dan bukankah navigasi hanyalah tipe lain dari logika bisnis? (Saya bisa mengatakan ya dalam beberapa kasus dan tidak dalam kasus lain.)

Bagi saya, implementasi utopis dari pola MVVM (dan saya pernah mendengar orang lain mengaku) ​​akan memiliki kabel ViewModel sedemikian rupa sehingga aplikasi dapat berjalan tanpa kepala (yaitu tidak ada Views). Ini memberikan area permukaan paling luas untuk pengujian berbasis kode dan membuat Tampilan menjadi kulit asli pada aplikasi. Dan ViewModel saya seharusnya tidak peduli jika itu ditampilkan di jendela utama, panel mengambang atau jendela anak, bukan?

Menurut apprach ini, tergantung pada beberapa mekanisme lain saat runtime untuk 'mengikat' tampilan apa yang harus ditampilkan untuk setiap ViewModel. Tetapi bagaimana jika kita ingin berbagi Tampilan dengan banyak ViewModels atau sebaliknya?

Jadi mengingat kebutuhan untuk mengelola hubungan View-ViewModel sehingga kita tahu apa yang harus ditampilkan ketika bersama dengan kebutuhan untuk menavigasi antara tampilan, termasuk menampilkan jendela anak / dialog, bagaimana kita benar-benar mencapai ini dalam pola MVVM?

SonOfPirate
sumber

Jawaban:

21

Navigasi harus selalu ditangani dalam ViewModel.

Anda berada di jalur yang benar dengan berpikir bahwa penerapan pola desain MVVM yang sempurna berarti Anda dapat menjalankan aplikasi sepenuhnya tanpa Tampilan, dan Anda tidak dapat melakukan itu jika Tampilan Anda mengontrol Navigasi Anda.

Saya biasanya memiliki ApplicationViewModel, atau ShellViewModel, yang menangani keseluruhan kondisi aplikasi saya. Ini termasuk CurrentPage(yang merupakan ViewModel) dan kode untuk penanganan ChangePageEvents. (Ini sering juga digunakan untuk objek aplikasi-lebar lainnya seperti CurrentUser, atau ErrorMessages juga)

Jadi, jika ada ViewModel, di mana saja, menyiarkan a ChangePageEvent(new SomePageViewModel), surat ShellViewModelwasiat akan mengambil pesan itu dan mengalihkannya CurrentPageke halaman apa pun yang ditentukan dalam pesan tersebut.

Saya benar-benar menulis posting blog tentang Navigasi dengan MVVM jika Anda tertarik

Rachel
sumber
2
Pendekatan yang menarik. Empat komentar: 1) Silverlight tidak mendukung properti DataType di DataTemplate sehingga memetakan DataTemplate ke ViewModel tidak dimungkinkan di SL. 2) Ini tidak membahas kemungkinan banyak-ke-banyak antara Views dan ViewModels. 3) Itu tidak menangani windows anak (atau setidaknya saya tidak melihat caranya). 4) Ini memerlukan penggabungan erat antara Aplikasi / Shell ViewModel Anda dan anak-anak (cucu, dll.) Jika saya memiliki 40 halaman di aplikasi saya, ViewModel ini akan menjadi cara rumit untuk dikelola.
SonOfPirate
Yang mengatakan, itu pasti sesuatu yang perlu dipertimbangkan.
SonOfPirate
@SonOfPirate 1) Silverlight tidak mendukung pemetaan DataTemplate implisit (namun), namun mendukung a DataTemplateSelector, yang biasanya saya gunakan untuk aplikasi Silverlight. 2) Saya telah menggunakan ini dalam banyak-ke-banyak situasi sebelumnya. Misalnya, satu ViewModel dapat memiliki beberapa Tampilan, atau satu Tampilan dapat dikaitkan dengan beberapa ViewModels. Untuk contoh yang pertama, lihat rachel53461.wordpress.com/2011/05/28/… . Untuk nanti, Anda hanya perlu menentukan beberapa peta ViewModels ke tampilan yang sama di DataTemplateSelector Anda.
Rachel
3) Itu hanya contoh dasar. Jika Anda tahu aplikasi Anda akan menjadi banyak jendela, Anda jelas akan mengubah ShellViewModel untuk menangani banyak CurrentPages4) Sekali lagi, itu hanya contoh dasar. Pada kenyataannya, PageViewModels saya semua didasarkan pada beberapa kelas dasar, jadi ShellViewModel saya hanya berfungsi dengan kelas dasar atau antarmuka, seperti IPageViewModel. Sungguh bagian terbesar dari pemetaan yang berantakan adalah DataTemplateSelector yang harus memetakan 40 Views ke 40 ViewModels.
Rachel
@SonOfPirate Saya harap itu menjawab beberapa pertanyaan Anda. Jangan ragu untuk mencari saya di Obrolan jika Anda memiliki orang lain :)
Rachel
6

Demi penutupan, saya pikir saya akan memposting arah yang akhirnya saya pilih untuk menyelesaikan masalah ini.

Keputusan pertama adalah memanfaatkan kerangka kerja Navigasi Halaman Silverlight yang disediakan di luar kotak. Keputusan ini didasarkan pada beberapa faktor termasuk pengetahuan bahwa jenis navigasi ini sedang dilakukan oleh Microsoft ke dalam aplikasi Metro Windows 8 dan konsisten dengan navigasi di aplikasi Phone 7.

Untuk membuatnya bekerja, saya selanjutnya melihat pekerjaan yang dilakukan ASP.NET MVC dengan navigasi berbasis konvensi. Kontrol Frame menggunakan URI untuk menemukan 'halaman' yang akan ditampilkan. Kesamaan menyediakan kesempatan untuk menggunakan pendekatan berbasis konvensi serupa di aplikasi Silverlight. Caranya adalah membuat semuanya bekerja bersama dengan cara MVVM.

Solusinya adalah NavigationService. Layanan ini memaparkan beberapa metode, seperti NavigateTo dan Kembali, yang dapat digunakan ViewModels untuk memulai perubahan halaman. Ketika halaman baru diminta, NavigationService mengirimkan CurrentPageChangedMessage menggunakan fitur MVVMLight Messenger.

Tampilan yang berisi kontrol Frame memiliki ViewModel sendiri yang ditetapkan sebagai DataContext yang mendengarkan pesan ini. Saat diterima, nama tampilan baru dimasukkan melalui fungsi pemetaan yang menerapkan aturan konvensi kami dan disetel ke properti CurrentPage. Properti Sumber dari kontrol Frame terikat ke properti CurrentPage. Akibatnya, pengaturan properti memperbarui Sumber dan memicu navigasi.

Kembali ke Layanan Navigasi. Metode NavigateTo menerima nama halaman target. Untuk memastikan bahwa ViewModels tidak memiliki masalah UI, nama yang digunakan adalah nama ViewModel untuk ditampilkan. Saya benar-benar membuat enumerasi yang memiliki bidang untuk setiap ViewModel yang dapat dinavigasi sebagai penolong dan untuk menghilangkan string sihir di seluruh aplikasi. Fungsi pemetaan yang saya sebutkan di atas akan menghapus akhiran "ViewModel" dari nama, menambahkan "Halaman" ke nama dan mengatur nama lengkap menjadi "Tampilan {Nama} Halaman.xaml".

Jadi, misalnya, untuk menavigasi ke tampilan detail pelanggan, saya dapat menelepon:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

Nilai dari CustomerDetails adalah "CustomerDetailsViewModel" yang dipetakan ke "Views \ CustomerDetailsPage.xaml".

Keindahan dari pendekatan ini adalah bahwa UI sepenuhnya dipisahkan dari ViewModels namun kami memiliki dukungan navigasi penuh. Namun saya sekarang dapat memeriksa kembali aplikasi saya dan kapan pun saya mau tanpa perubahan kode.

Semoga penjelasannya membantu.

SonOfPirate
sumber
2

Mirip dengan apa yang dikatakan Rachel, aplikasi saya yang kebanyakan-MVVM memiliki Presenteruntuk menangani sakelar antar windows atau halaman. Rachel menyebut ini suatu ApplicationViewModel, tetapi dalam pengalaman saya, umumnya harus melakukan lebih dari sekadar menjadi target yang mengikat (seperti menerima pesan, membuat Windows, dll.) Sehingga secara teknis lebih seperti tradisional Presenteratau Controller.

Dalam aplikasi saya, saya Presentermulai dengan a CurrentViewModel. The Presentermemotong semua komunikasi antara Viewdan ViewModel. Salah satu hal yang ViewModeldapat dilakukan selama interaksi adalah mengembalikan yang baru ViewModel, yang berarti halaman baru atau baru Windowharus ditampilkan. Yang Presentermengurus menciptakan atau menimpa Viewuntuk yang baru ViewModeldan mengatur DataContext.

Hasil dari suatu tindakan dapat juga bahwa a ViewModeladalah "lengkap", dalam hal Presenterini mendeteksi ini dan menutup jendela, atau ViewModelmengeluarkan ini dari tumpukan VM dan kembali untuk menampilkan halaman sebelumnya.

Scott Whitlock
sumber
Bagaimana Presenter mengetahui tampilan apa yang akan ditampilkan?
SonOfPirate
1
@SonOfPirate - Ini biasanya dilakukan melalui mekanisme WPF. Orang yang Presenterbaru saja menempel kembali ViewModeldi pohon visual, dan WPF mengambil yang sesuai View, menghubungkan DataContext, dan menempatkan itu di pohon visual sebagai gantinya. Anda bisa melakukan ini dengan menggunakan DataTemplates yang menyatakan ViewModeljenis apa yang mereka render, atau Anda bisa membuat pemilih templat data kustom. Itu masih dalam ranah fitur WPF.
Scott Whitlock