Menangani Dialog di WPF dengan MVVM

235

Dalam pola MVVM untuk WPF, penanganan dialog adalah salah satu operasi yang lebih kompleks. Karena model tampilan Anda tidak tahu apa-apa tentang tampilan, komunikasi dialog bisa menarik. Saya dapat mengekspos sebuahICommand bahwa ketika tampilan memanggilnya, sebuah dialog dapat muncul.

Adakah yang tahu cara yang baik untuk menangani hasil dari dialog? Saya berbicara tentang dialog windows seperti MessageBox.

Salah satu cara kami melakukan ini adalah memiliki acara di viewmodel yang berlangganan akan berlangganan ketika dialog diperlukan.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Ini tidak apa-apa, tetapi itu berarti bahwa tampilan memerlukan kode yang merupakan sesuatu yang ingin saya hindari.

Ray Booysen
sumber
Mengapa tidak mengikat ke objek pembantu di View?
Paul Williams
1
Tidak yakin apa yang kamu maksud.
Ray Booysen
1
Jika saya mengerti pertanyaannya, Anda tidak ingin VM muncul dialog, dan Anda tidak ingin kode-belakang di View. Terlebih lagi sepertinya Anda lebih suka perintah ke acara. Saya setuju dengan semua ini, jadi saya menggunakan kelas helper di View yang memperlihatkan perintah untuk menangani dialog. Saya menjawab pertanyaan ini di utas lain di sini: stackoverflow.com/a/23303267/420400 . Namun, kalimat terakhir membuatnya terdengar seperti Anda tidak ingin setiap kode sama sekali, di mana saja di Lihat. Saya mengerti kekhawatiran itu tetapi kode yang dipermasalahkan hanya bersyarat, dan kemungkinan tidak akan berubah.
Paul Williams
4
Model tampilan harus selalu bertanggung jawab atas logika di balik pembuatan kotak dialog, itulah alasan utama keberadaannya di tempat pertama. Yang mengatakan itu tidak (dan tidak seharusnya) melakukan angkat berat menciptakan tampilan itu sendiri. Saya menulis artikel tentang hal ini di codeproject.com/Articles/820324/... di mana saya menunjukkan bahwa seluruh siklus hidup kotak dialog dapat dikelola melalui pengikatan data WPF biasa dan tanpa memutus pola MVVM.
Mark Feldman

Jawaban:

131

Saya menyarankan untuk tidak melanjutkan dialog modal tahun 1990 dan alih-alih menerapkan kontrol sebagai overlay (kanvas + penentuan posisi absolut) dengan visibilitas yang terikat pada boolean di VM. Lebih dekat ke kontrol tipe ajax.

Ini sangat berguna:

<BooleanToVisibilityConverter x:Key="booltoVis" />

seperti dalam:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Inilah cara saya menerapkannya sebagai kontrol pengguna. Mengklik 'x' menutup kontrol dalam baris kode di kode kontrol pengguna di belakang. (Karena saya memiliki Tampilan saya di .exe dan ViewModels di dll, saya tidak merasa buruk tentang kode yang memanipulasi UI.)

Dialog Wpf

Jeffrey Knight
sumber
20
Ya saya juga suka ide ini tetapi ingin melihat beberapa contoh kontrol ini dalam hal bagaimana menunjukkannya, dan mengambil hasil dialog dari itu dll. Terutama dalam skenario MVVM di Silverlight.
Roboblob
16
Bagaimana Anda mencegah pengguna berinteraksi dengan kontrol di bawah lapisan dialog ini?
Andrew Garrison
17
Masalah dengan pendekatan ini adalah bahwa Anda tidak dapat membuka dialog modal kedua dari yang pertama, setidaknya bukan tanpa modifikasi berat pada sistem overlay ...
Thomas Levesque
6
Masalah lain dengan pendekatan ini adalah bahwa "dialog" tidak dapat dipindahkan. Dalam aplikasi kita, kita harus memiliki dialog bergerak sehingga pengguna dapat melihat apa yang ada di baliknya.
JAB
13
Pendekatan ini tampaknya mengerikan bagi saya. Apa yang saya lewatkan? Bagaimana ini lebih baik daripada kotak dialog nyata?
Jonathan Wood
51

Anda harus menggunakan mediator untuk ini. Mediator adalah pola desain umum yang juga dikenal sebagai Messenger dalam beberapa implementasinya. Ini adalah paradigma tipe Register / Beritahu dan memungkinkan ViewModel dan Tampilan Anda untuk berkomunikasi melalui mekanisme pesan yang digabungkan dengan rendah.

Anda harus memeriksa grup Google WPF Disciples, dan cukup mencari Mediator. Anda akan jauh lebih senang dengan jawabannya ...

Namun Anda dapat mulai dengan ini:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Nikmati !

Sunting: Anda dapat melihat jawaban untuk masalah ini dengan MVVM Light Toolkit di sini:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

Roubachof
sumber
2
Marlon grech baru saja memposting implementasi mediator yang baru: marlongrech.wordpress.com/2009/04/16/…
Roubachof
21
Hanya sebuah komentar: pola Mediator tidak diperkenalkan oleh WPF Disciples, ini adalah pola GoF klasik ... ( dofactory.com/Patterns/PatternMediator.aspx ). Jawaban yang bagus sebaliknya;)
Thomas Levesque
10
Tolong tuhan, jangan gunakan mediator atau kurir terkutuk. Kode semacam itu dengan lusinan pesan terbang menjadi sangat sulit untuk di-debug kecuali jika Anda entah bagaimana dapat mengingat semua poin di seluruh basis kode Anda yang berlangganan dan menangani setiap peristiwa. Itu menjadi mimpi buruk bagi para devs baru. Bahkan, saya menganggap seluruh pustaka MvvMLight sebagai anti-pola besar-besaran untuk penggunaan pesan asyncronous yang meresap dan tidak perlu. Solusinya sederhana: panggil layanan dialog terpisah (mis., IDialogService) dari desain Anda. Antarmuka memiliki metode dan acara untuk panggilan balik.
Chris Bordeman
34

Dialog MVVM yang baik harus:

  1. Dideklarasikan hanya dengan XAML.
  2. Dapatkan semua perilaku itu dari penyatuan data.

Sayangnya, WPF tidak menyediakan fitur-fitur ini. Menampilkan dialog memerlukan panggilan kode-balik ShowDialog(). Kelas Window, yang mendukung dialog, tidak dapat dideklarasikan dalam XAML sehingga tidak dapat dengan mudah didekati DataContext.

Untuk mengatasi ini, saya menulis kontrol rintisan XAML yang duduk di pohon logis dan relay penyatuan data ke Windowdan menangani menampilkan dan menyembunyikan dialog. Anda dapat menemukannya di sini: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Ini sangat sederhana untuk digunakan dan tidak memerlukan perubahan aneh pada ViewModel Anda dan tidak memerlukan acara atau pesan. Panggilan dasar terlihat seperti ini:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Anda mungkin ingin menambahkan gaya yang ditetapkan Showing. Saya jelaskan di artikel saya. Saya harap ini membantu Anda.

pengguna92541
sumber
2
Itu pendekatan yang sangat menarik untuk masalah menampilkan jendela dialog di MVVM.
dthrasher
2
"Showing a dialog requires a code-behind"mmm Anda bisa menyebutnya di ViewModel
Brock Hensley
Saya akan menambahkan poin 3 - Anda bebas untuk mengikat ke objek lain dalam tampilan. Meninggalkan kode dialog di belakang kosong tidak menyiratkan bahwa tidak ada kode C # di manapun dalam tampilan, dan penyatuan data tidak menyiratkan mengikat ke VM.
Paul Williams
25

Saya menggunakan ini pendekatan untuk dialog dengan MVVM.

Yang harus saya lakukan sekarang adalah memanggil yang berikut dari model tampilan saya.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
blindmeis
sumber
perpustakaan mana yang berasal dari uiDialogService?
aggietech
1
tidak ada perpustakaan. hanyalah antarmuka dan implementasi kecil: stackoverflow.com/questions/3801681/… . untuk menjadi adil, ia memiliki beberapa kelebihan untuk kebutuhan saya :) (tinggi, lebar, propertysettings dan sebagainya)
blindmeis
16

Solusi saya saat ini menyelesaikan sebagian besar masalah yang Anda sebutkan namun sepenuhnya abstrak dari platform hal-hal spesifik dan dapat digunakan kembali. Saya juga tidak menggunakan kode-belakang hanya mengikat dengan DelegateCommands yang mengimplementasikan ICommand. Dialog pada dasarnya adalah View - kontrol terpisah yang memiliki ViewModel sendiri dan ditampilkan dari ViewModel pada layar utama tetapi dipicu dari UI melalui pengikatan DelagateCommand.

Lihat solusi Silverlight 4 lengkap di sini. Dialog Modal dengan MVVM dan Silverlight 4

Roboblob
sumber
Sama seperti pendekatan @Elad Katz, jawaban Anda tidak memiliki konten tertaut - harap tingkatkan jawaban Anda dengan memasukkannya karena itulah yang dianggap sebagai jawaban yang baik di sini pada SO. Meskipun demikian, terima kasih atas kontribusi Anda! :)
Yoda
6

Saya benar-benar berjuang dengan konsep ini untuk sementara waktu ketika belajar (masih belajar) MVVM. Apa yang saya putuskan, dan apa yang saya pikirkan sudah diputuskan orang lain tetapi yang tidak jelas bagi saya adalah ini:

Pikiran asli saya adalah bahwa ViewModel tidak boleh diizinkan untuk memanggil kotak dialog secara langsung karena tidak ada bisnis yang memutuskan bagaimana dialog akan muncul. Karena ini saya mulai berpikir tentang bagaimana saya bisa mengirimkan pesan seperti yang saya miliki di MVP (yaitu View.ShowSaveFileDialog ()). Namun, saya pikir ini adalah pendekatan yang salah.

Tidak masalah bagi ViewModel untuk memanggil dialog secara langsung. Namun, ketika Anda menguji ViewModel, itu berarti dialog akan muncul selama pengujian Anda, atau gagal semuanya (tidak pernah benar-benar mencoba ini).

Jadi, yang perlu terjadi adalah saat pengujian adalah menggunakan versi "uji" dialog Anda. Ini berarti bahwa untuk selamanya dialog yang Anda miliki, Anda perlu membuat Interface dan mengejek respons dialog atau membuat tiruan pengujian yang akan memiliki perilaku default.

Anda seharusnya sudah menggunakan semacam Service Locator atau IoC yang dapat Anda konfigurasi untuk memberikan versi yang benar tergantung pada konteksnya.

Menggunakan pendekatan ini, ViewModel Anda masih dapat diuji dan tergantung pada bagaimana Anda mengejek dialog Anda, Anda dapat mengontrol perilaku.

Semoga ini membantu.

Mike Rowley
sumber
6

Ada dua cara yang baik untuk melakukan ini, 1) layanan dialog (mudah, bersih), dan 2) lihat dibantu. View dibantu menyediakan beberapa fitur yang rapi, tetapi biasanya tidak sepadan.

LAYANAN DIALOG

a) antarmuka layanan dialog seperti via konstruktor atau wadah ketergantungan:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Implementasi IDialogService Anda harus membuka jendela (atau menyuntikkan kontrol ke jendela aktif), membuat tampilan yang sesuai dengan nama tipe dlgVm yang diberikan (menggunakan registrasi atau konvensi wadah atau ContentPresenter dengan tipe DataTemplates terkait). ShowDialogAsync harus membuat TaskCompletionSource dan mengembalikan proposinya .Task. Kelas DialogViewModel sendiri membutuhkan acara yang dapat Anda panggil di kelas turunan saat Anda ingin menutup, dan tonton di tampilan dialog untuk benar-benar menutup / menyembunyikan dialog dan menyelesaikan TaskCompletionSource.

b) Untuk menggunakannya, cukup panggil tunggu ini. DialogService.ShowDialog (myDlgVm) pada contoh Anda dari beberapa kelas yang diturunkan dari DialogViewModel. Setelah menunggu pengembalian, lihat properti yang telah Anda tambahkan pada dialog VM Anda untuk menentukan apa yang terjadi; Anda bahkan tidak memerlukan panggilan balik.

LIHAT BANTUAN

Ini membuat Anda mendengarkan acara di model tampilan. Ini semua bisa dibungkus menjadi Perilaku Blend untuk menghindari kode di belakang dan penggunaan sumber daya jika Anda cenderung (FMI, subkelas kelas "Perilaku" untuk melihat semacam properti terlampir Blendable pada steroid). Untuk saat ini, kami akan melakukan ini secara manual pada setiap tampilan:

a) Buat OpenXXXXXDialogEvent dengan payload kustom (kelas turunan DialogViewModel).

b) Minta tampilan untuk berlangganan acara tersebut di acara OnDataContextChanged-nya. Pastikan untuk menyembunyikan dan berhenti berlangganan jika nilai lama! = Null dan pada acara Window's Unloaded.

c) Ketika acara menyala, mintalah tampilan untuk membuka tampilan Anda, yang mungkin ada di sumber daya pada halaman Anda, atau Anda dapat menemukannya dengan konvensi di tempat lain (seperti dalam pendekatan layanan dialog).

Pendekatan ini lebih fleksibel, tetapi membutuhkan lebih banyak pekerjaan untuk digunakan. Saya tidak banyak menggunakannya. Satu keuntungan yang bagus adalah kemampuan untuk menempatkan tampilan secara fisik di dalam tab, misalnya. Saya telah menggunakan algoritma untuk menempatkannya di batas kontrol pengguna saat ini, atau jika tidak cukup besar, melintasi pohon visual sampai wadah yang cukup besar ditemukan.

Ini memungkinkan dialog menjadi dekat dengan tempat mereka sebenarnya digunakan, hanya meredupkan bagian dari aplikasi yang terkait dengan aktivitas saat ini, dan membiarkan pengguna bergerak di dalam aplikasi tanpa harus secara manual mendorong dialog, bahkan memiliki beberapa kuasi- dialog modal terbuka pada tab atau sub-tampilan yang berbeda.

Chris Bordeman
sumber
Layanan dialog tentu saja lebih mudah, dan apa yang biasanya saya lakukan. Ini juga membuatnya mudah untuk menutup dialog tampilan dari model tampilan induk, yang diperlukan ketika model tampilan induk ditutup atau dibatalkan.
Chris Bordeman
4

Gunakan perintah yang dapat dibekukan

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}
Maxm007
sumber
Kode ini memerlukan beberapa pekerjaan, tetapi ini adalah ide terbaik sejauh ini, terutama untuk dialog sistem seperti dialog file atau printer. Dialog milik Lihat jika ada sesuatu. Untuk dialog file, hasilnya (nama file dipilih) dapat diteruskan ke perintah dalam sebagai parameternya.
Anton Tykhyy
3

Saya pikir penanganan dialog harus menjadi tanggung jawab pandangan, dan pandangan perlu memiliki kode untuk mendukungnya.

Jika Anda mengubah ViewModel - Lihat interaksi untuk menangani dialog maka ViewModel bergantung pada implementasi itu. Cara paling sederhana untuk mengatasi masalah ini adalah membuat View bertanggung jawab untuk melakukan tugas. Jika itu berarti menampilkan dialog maka baik-baik saja, tetapi juga bisa menjadi pesan status di bilah status dll.

Maksud saya adalah bahwa seluruh titik pola MVVM adalah memisahkan logika bisnis dari GUI, jadi Anda tidak boleh mencampurkan logika GUI (untuk menampilkan dialog) di lapisan bisnis (ViewModel).

Cameron MacFarland
sumber
2
VM tidak akan pernah menangani dialog, dalam contoh saya itu hanya akan memiliki acara yang akan memerlukan dialog untuk menjalankan dan meneruskan info dalam beberapa bentuk EventArgs. Jika tampilan bertanggung jawab, bagaimana cara menyampaikan info ke VM?
Ray Booysen
Katakanlah VM perlu menghapus sesuatu. VM memanggil metode pada Hapus Hapus yang mengembalikan boolean. View kemudian dapat menghapus item secara langsung dan mengembalikan true, atau menampilkan dialog konfirmasi dan mengembalikan true / false tergantung pada jawaban pengguna.
Cameron MacFarland
VM tidak tahu apa-apa tentang dialog tetapi hanya meminta tampilan untuk menghapus sesuatu, yang mana tampilan dikonfirmasi atau ditolak.
Cameron MacFarland
Saya selalu berpikir bahwa titik MVVM adalah Model: business logic, ViewModel: GUI logic dan View: no logic. Entah bagaimana yang bertentangan dengan paragraf terakhir Anda. Tolong jelaskan!
David Schmitt
2
Pertama-tama harus ditentukan jika meminta konfirmasi pra-hapus adalah logika bisnis atau logika tampilan. Jika ini adalah logika bisnis, metode DeleteFile dalam model tidak boleh melakukannya, melainkan mengembalikan objek pertanyaan konfirmasi. Ini akan mencakup referensi ke delegasi yang melakukan penghapusan yang sebenarnya. Jika ini bukan logika bisnis, VM harus membangun VM pertanyaan di DeleteFileCommand, dengan dua anggota ICommand. Satu untuk ya dan satu untuk tidak. Mungkin ada argumen untuk kedua tampilan, dan di RL sebagian besar penggunaan mungkin akan menemui keduanya.
Guge
3

Alternatif yang menarik adalah dengan menggunakan Pengontrol yang bertanggung jawab untuk menampilkan tampilan (dialog).

Cara kerjanya ditunjukkan oleh WPF Application Framework (WAF) .

jbe
sumber
3

Mengapa tidak hanya membuat acara di VM dan berlangganan acara di tampilan? Ini akan membuat logika aplikasi dan tampilan terpisah dan masih memungkinkan Anda untuk menggunakan jendela anak untuk dialog.

Eric Grover
sumber
3

Saya telah menerapkan Perilaku yang mendengarkan Pesan dari ViewModel. Ini didasarkan pada solusi Laurent Bugnion, tetapi karena tidak menggunakan kode di belakang dan lebih dapat digunakan kembali, saya pikir itu lebih elegan.

Cara membuat WPF berperilaku seolah-olah MVVM didukung di luar kotak

Elad Katz
sumber
1
Anda harus memasukkan kode lengkap di sini karena itulah yang diperlukan SO untuk jawaban yang baik. Meskipun demikian, pendekatan tertautnya cukup rapi, jadi terima kasih untuk itu! :)
Yoda
2
@yoda kode lengkapnya cukup panjang, dan itulah sebabnya saya lebih suka menautkannya. Saya telah mengedit jawaban saya untuk mencerminkan perubahan dan menunjukkan tautan yang tidak rusak
Elad Katz
Terima kasih untuk perbaikannya. Meskipun demikian, lebih baik memberikan kode 3 halaman penuh gulungan panjang di sini pada SO daripada tautan yang mungkin offline suatu hari nanti. Artikel bagus untuk topik yang rumit selalu panjang - dan saya tidak melihat manfaat apa pun dalam membuka tab baru, beralih ke tab tersebut dan gulir ke sana sambil menggulir halaman yang sama / tab yang saya pakai sebelumnya. ;)
Yoda
@EladKatz Saya telah melihat bahwa Anda telah membagikan sebagian implementasi WPF Anda di tautan yang Anda berikan. Apakah Anda punya solusi untuk membuka jendela baru dari ViewModel? Pada dasarnya saya memiliki dua bentuk dan masing-masing memiliki satu ViewModel. Satu pengguna mengklik tombol formulir lain muncul dan viewmodel1 mengirimkan objeknya ke viewmodel2. Dalam formulir 2 pengguna dapat mengubah objek dan ketika mereka menutup jendela, objek yang diperbarui akan dikirim kembali ke ViewModel pertama. Apakah Anda punya solusi untuk ini?
Ehsan
2

Saya pikir view bisa memiliki kode untuk menangani event dari model view.

Bergantung pada peristiwa / skenario, itu juga bisa memiliki pemicu acara yang berlangganan untuk melihat model acara, dan satu atau lebih tindakan untuk meminta tanggapan.

Nikhil Kothari
sumber
1

Karl Shifflett telah membuat contoh aplikasi untuk menampilkan kotak dialog menggunakan pendekatan layanan dan pendekatan Prism InteractionRequest.

Saya suka pendekatan layanan - Ini kurang fleksibel sehingga pengguna cenderung untuk memecahkan sesuatu :) Ini juga konsisten dengan bagian WinForms dari aplikasi saya (MessageBox.Show) Tetapi jika Anda berencana untuk menampilkan banyak dialog yang berbeda, maka InteractionRequest adalah cara yang lebih baik untuk pergi.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

berselancar
sumber
1

Saya tahu ini adalah pertanyaan lama, tetapi ketika saya melakukan pencarian ini, saya menemukan banyak pertanyaan terkait, tetapi saya tidak menemukan jawaban yang benar-benar jelas. Jadi saya membuat implementasi kotak dialog / messagebox / popin saya sendiri, dan saya bagikan!
Saya pikir itu adalah "bukti MVVM", dan saya mencoba membuatnya sederhana dan tepat, tetapi saya baru di WPF, jadi jangan ragu untuk berkomentar, atau bahkan membuat permintaan tarik.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Anda bisa menggunakannya seperti ini:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Atau seperti ini jika Anda ingin popin yang lebih canggih:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

Dan itu menunjukkan hal-hal seperti ini:

2

Xav987
sumber
1

Pendekatan standar

Setelah menghabiskan bertahun-tahun menangani masalah ini di WPF, saya akhirnya menemukan cara standar untuk mengimplementasikan dialog di WPF. Inilah keuntungan dari pendekatan ini:

  1. BERSIH
  2. Tidak melanggar pola desain MVVM
  3. ViewModal tidak pernah merujuk pustaka UI apa pun (WindowBase, PresentationFramework, dll.)
  4. Sempurna untuk pengujian otomatis
  5. Dialog dapat diganti dengan mudah.

Jadi apa kuncinya. Ini adalah DI + IoC .

Inilah cara kerjanya. Saya menggunakan MVVM Light, tetapi pendekatan ini juga dapat diperluas ke kerangka kerja lain:

  1. Tambahkan proyek Aplikasi WPF ke solusi Anda. Sebut itu Aplikasi .
  2. Tambahkan Perpustakaan Kelas ViewModal. Sebut saja VM .
  3. Referensi aplikasi proyek VM. Proyek VM tidak tahu apa-apa tentang App.
  4. Tambahkan referensi NuGet ke MVVM Light untuk kedua proyek . Saya menggunakan MVVM Light Standard hari ini, tetapi Anda baik-baik saja dengan versi Kerangka penuh juga.
  5. Tambahkan antarmuka IDialogService ke proyek VM:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Mengekspos properti statis publik IDialogServicejenis di Anda ViewModelLocator, tetapi biarkan bagian pendaftaran untuk lapisan Lihat untuk melakukan. Ini kuncinya .:

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Tambahkan implementasi antarmuka ini dalam proyek Aplikasi.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Sementara beberapa fungsi ini bersifat umum ( ShowMessage,AskBooleanQuestion dll.), Yang lain khusus untuk proyek ini dan menggunakan custom Window. Anda dapat menambahkan lebih banyak jendela khusus dengan cara yang sama. Kuncinya adalah untuk menjaga elemen khusus UI di lapisan Lihat dan hanya mengekspos data yang dikembalikan menggunakan POCO di lapisan VM .
  9. Lakukan Pendaftaran IoC antarmuka Anda di lapisan Lihat menggunakan kelas ini. Anda dapat melakukan ini di konstruktor tampilan utama Anda (setelahInitializeComponent() panggilan):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Ini dia. Anda sekarang memiliki akses ke semua fungsi dialog Anda di VM dan Lihat lapisan. Lapisan VM Anda dapat memanggil fungsi-fungsi ini seperti ini:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Begitu bersih Anda lihat. Lapisan VM tidak tahu apa-apa tentang bagaimana pertanyaan Ya / Tidak akan disajikan kepada pengguna oleh lapisan UI dan masih dapat berhasil bekerja dengan hasil yang dikembalikan dari dialog.

Fasilitas gratis lainnya

  1. Untuk pengujian unit penulisan, Anda dapat memberikan implementasi kustom untuk IDialogService dalam proyek Uji Anda dan mendaftarkan kelas itu di IoC di konstruktor kelas tes Anda.
  2. Anda harus mengimpor beberapa ruang nama seperti Microsoft.Win32untuk mengakses dialog Open and Save. Saya telah meninggalkan mereka karena ada juga versi WinForms dari dialog ini tersedia, ditambah seseorang mungkin ingin membuat versi mereka sendiri. Perhatikan juga bahwa beberapa pengenal yang digunakan DialogPresenteradalah nama-nama windows saya sendiri (misSettingsWindow .). Anda harus menghapusnya dari antarmuka dan implementasi atau menyediakan jendela Anda sendiri.
  3. Jika VM Anda melakukan multi-threading, hubungi MVVM Light di DispatcherHelper.Initialize()awal siklus hidup aplikasi Anda.
  4. Kecuali DialogPresenteryang disuntikkan di layer View, ViewModals lain harus didaftarkan ViewModelLocatordan kemudian properti statis publik dari tipe itu harus diekspos untuk dikonsumsi oleh layer View. Sesuatu seperti ini:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Sebagian besar, dialog Anda seharusnya tidak memiliki kode-belakang untuk hal-hal seperti mengikat atau mengatur DataContext dll. Anda bahkan tidak boleh melewatkan hal-hal sebagai parameter konstruktor. XAML dapat melakukan itu semua untuk Anda, seperti ini:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Pengaturan DataContextdengan cara ini memberi Anda semua jenis manfaat waktu-desain seperti Intellisense dan pelengkapan otomatis.

Semoga itu bisa membantu semua orang.

dotNET
sumber
0

Saya sedang merenungkan masalah yang sama ketika menanyakan bagaimana tampilan model untuk suatu tugas atau dialog .

Solusi saya saat ini terlihat seperti ini:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Ketika model tampilan memutuskan bahwa input pengguna diperlukan, itu menarik contoh SelectionTaskModeldengan pilihan yang mungkin bagi pengguna. Infrastruktur menangani memunculkan tampilan yang sesuai, yang pada waktu yang tepat akan memanggil Choose()fungsi dengan pilihan pengguna.

David Schmitt
sumber
0

Saya berjuang dengan masalah yang sama. Saya telah menemukan cara untuk berkomunikasi antara View dan ViewModel. Anda dapat memulai mengirim pesan dari ViewModel ke View untuk mengatakannya untuk menampilkan kotak pesan dan itu akan melaporkan kembali dengan hasilnya. Kemudian ViewModel dapat merespons hasil yang dikembalikan dari View.

Saya menunjukkan ini di blog saya :

Dan Is Fiddling By Firelight
sumber
0

Saya telah menulis artikel yang cukup komprehensif tentang topik ini dan juga mengembangkan perpustakaan pop-in untuk Dialog MVVM. Kepatuhan terhadap MVVM tidak hanya mungkin tetapi sangat bersih bila diterapkan dengan benar, dan dapat dengan mudah diperluas ke perpustakaan pihak ketiga yang tidak mengikutinya sendiri:

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

Mark Feldman
sumber
0

Maaf, tetapi saya harus mengikuti. Saya telah melalui beberapa solusi yang disarankan, sebelum menemukan namespace Prism.Wpf.Interactivity dalam proyek Prism. Anda dapat menggunakan permintaan interaksi dan tindakan jendela popup untuk menggulung jendela kustom atau untuk kebutuhan sederhana ada dibangun di popup Pemberitahuan dan Konfirmasi. Ini menciptakan jendela yang benar dan dikelola seperti itu. Anda bisa melewatkan objek konteks dengan dependensi apa pun yang Anda butuhkan dalam dialog. Kami menggunakan solusi ini di tempat kerja saya sejak saya menemukannya. Kami memiliki banyak pengembang senior di sini dan tidak ada yang datang dengan sesuatu yang lebih baik. Solusi kami sebelumnya adalah layanan dialog menjadi overlay dan menggunakan kelas presenter untuk mewujudkannya, tetapi Anda harus memiliki pabrik untuk semua model dialog, dll.

Ini tidak sepele tetapi juga tidak super rumit. Dan itu dibangun untuk Prism dan karena itu yang terbaik (atau lebih baik) berlatih IMHO.

2 sen saya!

jogi
sumber
-1

EDIT: ya saya setuju ini bukan pendekatan MVVM yang benar dan saya sekarang menggunakan sesuatu yang mirip dengan apa yang disarankan oleh blindmeis.

Salah satu cara Anda dapat melakukan ini adalah

Di Main View Model Anda (tempat Anda membuka modal):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

Dan di Modal Window View / ViewModel Anda:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

atau mirip dengan apa yang diposting di sini WPF MVVM: Cara menutup jendela

Simone
sumber
2
Saya bukan downvote, tapi saya curiga itu karena view-model memiliki referensi langsung ke view.
Brian Gideon
@BrianGideon, terima kasih atas komentar Anda. Saya setuju ini bukan solusi terpisah. Sebenarnya, saya tidak menggunakan sesuatu yang mirip dengan whar yang disarankan oleh blindmeis. Terima kasih lagi.
Simone
Bentuk yang buruk untuk menjangkau ke tampilan saat begitu mudah untuk tidak melihatnya.
Chris Bordeman