Saya mencoba mempelajari WPF dan masalah MVVM, tetapi menemui hambatan. Pertanyaan ini mirip tetapi tidak persis sama dengan pertanyaan ini (menangani-dialog-in-wpf-with-mvvm) ...
Saya memiliki formulir "Login" yang ditulis menggunakan pola MVVM.
Formulir ini memiliki ViewModel yang menyimpan Username dan Password, yang terikat ke tampilan di XAML menggunakan data binding normal. Ia juga memiliki perintah "Login" yang terikat ke tombol "Login" pada formulir, agan menggunakan penyatuan data normal.
Saat perintah "Login" diaktifkan, ini memanggil fungsi di ViewModel yang berbunyi dan mengirim data melalui jaringan untuk masuk. Saat fungsi ini selesai, ada 2 tindakan:
Login tidak valid - kami hanya menampilkan MessageBox dan semuanya baik-baik saja
Login itu valid, kita perlu menutup formulir Login dan mengembalikannya benar sebagai
DialogResult
...
Masalahnya adalah, ViewModel tidak tahu apa-apa tentang tampilan aktual, jadi bagaimana cara menutup tampilan dan menyuruhnya mengembalikan DialogResult tertentu ?? Saya bisa menempelkan beberapa kode di CodeBehind, dan / atau meneruskan View ke ViewModel, tapi sepertinya itu akan mengalahkan keseluruhan poin MVVM sepenuhnya ...
Memperbarui
Pada akhirnya saya hanya melanggar "kemurnian" pola MVVM dan meminta View mempublikasikan Closed
acara, dan mengekspos Close
metode. ViewModel kemudian akan memanggil view.Close
. Tampilan ini hanya diketahui melalui antarmuka dan dihubungkan melalui wadah IOC, jadi tidak ada kemampuan pengujian atau pemeliharaan yang hilang.
Tampaknya agak konyol bahwa jawaban yang diterima adalah -5 suara! Sementara saya sangat menyadari perasaan baik yang didapat seseorang dengan memecahkan masalah sambil menjadi "murni", Tentunya saya bukan satu-satunya yang berpikir bahwa 200 baris kejadian, perintah dan perilaku hanya untuk menghindari metode satu baris dalam nama "pola" dan "kemurnian" agak konyol ....
Close
metode sederhana masih merupakan solusi terbaik. Segala sesuatu yang lain pada dialog lain yang lebih kompleks adalah MVVM dan databound, tetapi tampaknya konyol untuk menerapkan "solusi" besar di sini, bukan hanya metode sederhana ...Jawaban:
Saya terinspirasi oleh jawaban Thejuan untuk menulis properti terlampir yang lebih sederhana. Tanpa gaya, tidak ada pemicu; sebaliknya, Anda dapat melakukan ini:
<Window ... xmlns:xc="clr-namespace:ExCastle.Wpf" xc:DialogCloser.DialogResult="{Binding DialogResult}">
Ini hampir sebersih jika tim WPF telah melakukannya dengan benar dan menjadikan DialogResult sebagai properti dependensi sejak awal. Cukup letakkan
bool? DialogResult
properti di ViewModel Anda dan implementasikan INotifyPropertyChanged, dan voilà, ViewModel Anda bisa menutup Jendela (dan menyetel DialogResult) hanya dengan menyetel properti. MVVM sebagaimana mestinya.Berikut kode untuk DialogCloser:
using System.Windows; namespace ExCastle.Wpf { public static class DialogCloser { public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached( "DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged)); private static void DialogResultChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = d as Window; if (window != null) window.DialogResult = e.NewValue as bool?; } public static void SetDialogResult(Window target, bool? value) { target.SetValue(DialogResultProperty, value); } } }
Saya juga memposting ini di blog saya .
sumber
Dari sudut pandang saya, pertanyaannya cukup bagus karena pendekatan yang sama akan digunakan tidak hanya untuk jendela "Login", tetapi untuk semua jenis jendela. Saya telah meninjau banyak saran dan tidak ada yang OK untuk saya. Silakan tinjau saran saya yang diambil dari artikel pola desain MVVM .
Setiap kelas ViewModel harus mewarisi dari
WorkspaceViewModel
yang memilikiRequestClose
acara danCloseCommand
propertiICommand
tipe. Implementasi defaultCloseCommand
properti akan memunculkanRequestClose
acara tersebut.Untuk menutup jendela,
OnLoaded
metode jendela Anda harus diganti:void CustomerWindow_Loaded(object sender, RoutedEventArgs e) { CustomerViewModel customer = CustomerViewModel.GetYourCustomer(); DataContext = customer; customer.RequestClose += () => { Close(); }; }
atau
OnStartup
metode aplikasi Anda:protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); MainWindow window = new MainWindow(); var viewModel = new MainWindowViewModel(); viewModel.RequestClose += window.Close; window.DataContext = viewModel; window.Show(); }
Saya kira implementasi
RequestClose
acara danCloseCommand
properti diWorkspaceViewModel
cukup jelas, tetapi saya akan menunjukkannya agar konsisten:public abstract class WorkspaceViewModel : ViewModelBase // There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface { RelayCommand _closeCommand; public ICommand CloseCommand { get { if (_closeCommand == null) { _closeCommand = new RelayCommand( param => Close(), param => CanClose() ); } return _closeCommand; } } public event Action RequestClose; public virtual void Close() { if ( RequestClose != null ) { RequestClose(); } } public virtual bool CanClose() { return true; } }
Dan kode sumber dari
RelayCommand
:public class RelayCommand : ICommand { #region Constructors public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion // Constructors #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion // ICommand Members #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion // Fields }
PS Jangan perlakukan saya dengan buruk untuk sumber-sumber itu! Jika saya memilikinya kemarin, itu akan menyelamatkan saya beberapa jam ...
PPS Setiap komentar atau saran dipersilakan.
sumber
customer.RequestClose
di kode di belakang file XAML Anda bukankah itu melanggar pola MVVM? Anda bisa saja mengikat keClick
event handler pada tombol tutup Anda di tempat pertama melihat Anda telah menyentuh kode di belakangnya dan melakukanthis.Close()
! Baik?Saya menggunakan perilaku terlampir untuk menutup jendela. Ikat properti "sinyal" pada ViewModel Anda ke perilaku terlampir (saya sebenarnya menggunakan pemicu). Jika disetel ke true, perilaku tersebut menutup jendela.
http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/
sumber
Ada banyak komentar yang memperdebatkan pro dan kontra MVVM di sini. Bagi saya, saya setuju dengan Nir; ini masalah menggunakan pola dengan tepat dan MVVM tidak selalu cocok. Orang-orang tampaknya rela mengorbankan semua prinsip terpenting desain perangkat lunak HANYA agar sesuai dengan MVVM.
Yang mengatakan, .. saya pikir kasus Anda bisa cocok dengan sedikit refactoring.
Dalam kebanyakan kasus yang saya temui, WPF memungkinkan Anda bertahan TANPA beberapa
Window
detik. Mungkin Anda bisa mencoba menggunakanFrame
s danPage
s daripada Windows denganDialogResult
s.Dalam kasus Anda, saran saya akan
LoginFormViewModel
menanganiLoginCommand
dan jika login tidak valid, setel propertiLoginFormViewModel
ke nilai yang sesuai (false
atau beberapa nilai enum sepertiUserAuthenticationStates.FailedAuthentication
). Anda akan melakukan hal yang sama untuk login yang berhasil (true
atau nilai enum lainnya). Anda kemudian akan menggunakan aDataTrigger
yang merespons berbagai status otentikasi pengguna dan dapat menggunakan sederhanaSetter
untuk mengubahSource
properti fileFrame
.Setelah Jendela login Anda kembali,
DialogResult
menurut saya itulah yang membuat Anda bingung; yangDialogResult
sebenarnya adalah properti ViewModel Anda. Dalam pengalaman saya, yang diakui terbatas dengan WPF, ketika ada sesuatu yang terasa tidak benar biasanya karena saya berpikir dalam hal bagaimana saya akan melakukan hal yang sama di WinForms.Semoga membantu.
sumber
Dengan asumsi dialog login Anda adalah jendela pertama yang dibuat, coba ini di dalam kelas LoginViewModel Anda:
void OnLoginResponse(bool loginSucceded) { if (loginSucceded) { Window1 window = new Window1() { DataContext = new MainWindowViewModel() }; window.Show(); App.Current.MainWindow.Close(); App.Current.MainWindow = window; } else { LoginError = true; } }
sumber
Ini adalah solusi yang sederhana dan bersih - Anda menambahkan acara ke ViewModel dan menginstruksikan Jendela untuk menutup sendiri saat acara tersebut diaktifkan.
Untuk lebih jelasnya lihat posting blog saya, Tutup jendela dari ViewModel .
XAML:
<Window x:Name="this" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"> <i:Interaction.Triggers> <i:EventTrigger SourceObject="{Binding}" EventName="Closed"> <ei:CallMethodAction TargetObject="{Binding ElementName=this}" MethodName="Close"/> </i:EventTrigger> </i:Interaction.Triggers> <Window>
ViewModel:
private ICommand _SaveAndCloseCommand; public ICommand SaveAndCloseCommand { get { return _SaveAndCloseCommand ?? (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose)); } } private void SaveAndClose() { Save(); Close(); } public event EventHandler Closed; private void Close() { if (Closed != null) Closed(this, EventArgs.Empty); }
Catatan: Contoh ini menggunakan Prism's
DelegateCommand
(lihat Prism: Commanding ), tetapiICommand
implementasi apa pun dapat digunakan dalam hal ini.Anda dapat menggunakan perilaku dari paket resmi ini .
sumber
Cara saya menanganinya adalah dengan menambahkan event handler di ViewModel saya. Ketika pengguna berhasil masuk, saya akan mengaktifkan acara tersebut. Dalam View saya, saya akan melampirkan ke acara ini dan ketika diaktifkan, saya akan menutup jendela.
sumber
Inilah yang awalnya saya lakukan, yang berhasil, namun tampaknya agak bertele-tele dan jelek (statis global apa pun tidak pernah baik)
1: App.xaml.cs
public partial class App : Application { // create a new global custom WPF Command public static readonly RoutedUICommand LoggedIn = new RoutedUICommand(); }
2: LoginForm.xaml
// bind the global command to a local eventhandler <CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />
3: LoginForm.xaml.cs
// implement the local eventhandler in codebehind private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e ) { DialogResult = true; Close(); }
4: LoginFormViewModel.cs
// fire the global command from the viewmodel private void OnRemoteServerReturnedSuccess() { App.LoggedIn.Execute(this, null); }
Saya kemudian menghapus semua kode ini, dan baru saja
LoginFormViewModel
memanggil metode Tutup pada tampilan itu. Itu akhirnya menjadi jauh lebih bagus dan lebih mudah diikuti. IMHO Inti dari pola adalah memberi orang cara yang lebih mudah untuk memahami apa yang dilakukan aplikasi Anda, dan dalam hal ini, MVVM membuatnya jauh lebih sulit untuk dipahami daripada jika saya tidak menggunakannya, dan sekarang menjadi anti- pola.sumber
FYI, saya mengalami masalah yang sama ini dan saya pikir saya menemukan solusi yang tidak memerlukan global atau statika, meskipun itu mungkin bukan jawaban terbaik. Aku membiarkan kalian memutuskannya sendiri.
Dalam kasus saya, ViewModel yang memberi contoh Jendela untuk ditampilkan (sebut saja ViewModelMain) juga tahu tentang LoginFormViewModel (menggunakan situasi di atas sebagai contoh).
Jadi yang saya lakukan adalah membuat properti di LoginFormViewModel yang bertipe ICommand (Sebut saja CloseWindowCommand). Kemudian, sebelum saya memanggil .ShowDialog () di Window, saya mengatur properti CloseWindowCommand pada LoginFormViewModel ke metode window.Close () dari Window yang saya contohkan. Kemudian di dalam LoginFormViewModel yang harus saya lakukan adalah memanggil CloseWindowCommand.Execute () untuk menutup jendela.
Saya kira ini sedikit solusi / peretasan, tetapi berfungsi dengan baik tanpa benar-benar merusak pola MVVM.
Jangan ragu untuk mengkritik proses ini sebanyak yang Anda suka, saya bisa menerimanya! :)
sumber
Ini mungkin sudah sangat terlambat, tetapi saya menemukan masalah yang sama dan saya menemukan solusi yang berhasil untuk saya.
Saya tidak tahu cara membuat aplikasi tanpa dialog (mungkin itu hanya mind block). Jadi saya menemui jalan buntu dengan MVVM dan menunjukkan dialog. Jadi saya menemukan artikel CodeProject ini:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
Yang merupakan UserControl yang pada dasarnya memungkinkan sebuah jendela berada di dalam pohon visual jendela lain (tidak diizinkan di xaml). Ini juga mengekspos DependencyProperty boolean yang disebut IsShowing.
Anda dapat menyetel gaya seperti, biasanya dalam sebuah sumber-sumber, yang pada dasarnya menampilkan dialog setiap kali properti Konten kontrol! = Null melalui pemicu:
<Style TargetType="{x:Type d:Dialog}"> <Style.Triggers> <Trigger Property="HasContent" Value="True"> <Setter Property="Showing" Value="True" /> </Trigger> </Style.Triggers> </Style>
Dalam tampilan di mana Anda ingin menampilkan dialog, cukup miliki ini:
<d:Dialog Content="{Binding Path=DialogViewModel}"/>
Dan di ViewModel, yang harus Anda lakukan adalah menyetel properti ke nilai (Catatan: kelas ViewModel harus mendukung INotifyPropertyChanged agar tampilan mengetahui sesuatu telah terjadi).
seperti ini:
DialogViewModel = new DisplayViewModel();
Untuk mencocokkan ViewModel dengan View, Anda harus memiliki sesuatu seperti ini di sebuah sumber bacaan:
<DataTemplate DataType="{x:Type vm:DisplayViewModel}"> <vw:DisplayView/> </DataTemplate>
Dengan semua itu Anda mendapatkan kode satu baris untuk menampilkan dialog. Masalah yang Anda dapatkan adalah Anda tidak dapat menutup dialog hanya dengan kode di atas. Jadi itulah mengapa Anda harus meletakkan acara di kelas dasar ViewModel yang diwarisi oleh DisplayViewModel dan alih-alih kode di atas, tulis ini
var vm = new DisplayViewModel(); vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose); DialogViewModel = vm;
Kemudian Anda dapat menangani hasil dialog melalui callback.
Ini mungkin tampak sedikit rumit, tetapi begitu landasannya diletakkan, itu cukup mudah. Sekali lagi ini adalah implementasi saya, saya yakin ada yang lain :)
Semoga ini bisa membantu, ini menyelamatkan saya.
sumber
Oke, jadi pertanyaan ini hampir berusia 6 tahun dan saya masih tidak dapat menemukan di sini apa yang menurut saya merupakan jawaban yang tepat, jadi izinkan saya untuk membagikan "2 sen" saya ...
Saya sebenarnya memiliki 2 cara untuk melakukannya, yang pertama adalah yang sederhana ... yang kedua di yang benar, jadi jika Anda mencari yang benar, lewati saja # 1 dan lompat ke # 2 :
1. Cepat dan Mudah (tapi belum lengkap)
Jika saya hanya memiliki proyek kecil, terkadang saya hanya membuat CloseWindowAction di ViewModel:
public Action CloseWindow { get; set; } // In MyViewModel.cs
Dan siapa pun yang memasukkan Tampilan, atau dalam kode Tampilan di belakang, saya baru saja mengatur Metode yang akan dipanggil oleh Tindakan:
(ingat MVVM adalah tentang pemisahan View dan ViewModel ... kode View tetaplah View dan selama ada pemisahan yang tepat Anda tidak melanggar polanya)
Jika beberapa ViewModel membuat jendela baru:
private void CreateNewView() { MyView window = new MyView(); window.DataContext = new MyViewModel { CloseWindow = window.Close, }; window.ShowDialog(); }
Atau jika Anda menginginkannya di Jendela Utama, cukup letakkan di bawah konstruktor View Anda:
public MyView() { InitializeComponent(); this.DataContext = new MainViewModel { CloseWindow = this.Close }; }
bila Anda ingin menutup jendela, cukup panggil Action di ViewModel Anda.
2. Cara yang benar
Sekarang cara yang tepat untuk melakukannya adalah dengan menggunakan Prism (IMHO), dan semua tentangnya dapat ditemukan di sini .
Anda dapat membuat Permintaan Interaksi , mengisinya dengan data apa pun yang Anda perlukan di Jendela baru Anda, makan siang, menutupnya, dan bahkan menerima data kembali . Semua ini dikemas dan MVVM disetujui. Anda bahkan mendapatkan status bagaimana Jendela ditutup , seperti jika Pengguna
Canceled
atauAccepted
(tombol OK) Jendela dan data kembali jika Anda membutuhkannya . Ini sedikit lebih rumit dan Jawaban # 1, tetapi jauh lebih lengkap, dan Pola yang Direkomendasikan oleh Microsoft.Tautan yang saya berikan memiliki semua cuplikan kode dan contoh, jadi saya tidak akan repot-repot menempatkan kode apa pun di sini, cukup baca artikel mengunduh Prism Quick Start dan menjalankannya, sangat mudah untuk memahami hanya sedikit lebih verbose membuatnya berhasil, tetapi manfaatnya lebih besar dari sekadar menutup jendela.
sumber
+=
untuk menambahkan delegasi, dan panggil Tindakan, itu akan memecat semuanya .... Atau Anda akan melakukannya harus membuat logika khusus pada VM Anda sehingga akan mengetahui jendela mana yang akan ditutup (mungkin memiliki kumpulan Tindakan Tutup) .... Namun menurut saya, memiliki beberapa tampilan yang diikat ke satu VM bukanlah praktik terbaik, itu lebih baik untuk mengelola agar memiliki satu Lihat dan satu instance VM, yang terikat satu sama lain dan mungkin VM induk yang mengelola semua VM turunan yang terikat ke semua Tampilan.public partial class MyWindow: Window { public ApplicationSelection() { InitializeComponent(); MyViewModel viewModel = new MyViewModel(); DataContext = viewModel; viewModel.RequestClose += () => { Close(); }; } } public class MyViewModel { //...Your code... public event Action RequestClose; public virtual void Close() { if (RequestClose != null) { RequestClose(); } } public void SomeFunction() { //...Do something... Close(); } }
sumber
Anda bisa membuat ViewModel mengekspos peristiwa yang didaftarkan oleh View. Kemudian, saat ViewModel memutuskan waktunya untuk menutup tampilan, ia mengaktifkan peristiwa yang menyebabkan tampilan ditutup. Jika Anda ingin nilai hasil tertentu dikembalikan, Anda akan memiliki properti di ViewModel untuk itu.
sumber
Hanya untuk menambah sejumlah besar jawaban, saya ingin menambahkan yang berikut ini. Dengan asumsi bahwa Anda memiliki ICommand pada ViewModel Anda, dan Anda ingin perintah itu menutup jendelanya (atau tindakan lain dalam hal ini), Anda dapat menggunakan sesuatu seperti berikut ini.
var windows = Application.Current.Windows; for (var i=0;i< windows.Count;i++ ) if (windows[i].DataContext == this) windows[i].Close();
Ini tidak sempurna, dan mungkin sulit untuk diuji (karena sulit untuk meniru / menghentikan statis) tetapi lebih bersih (IMHO) daripada solusi lainnya.
Erick
sumber
Saya menerapkan solusi Joe White, tetapi mengalami masalah dengan sesekali " DialogResult dapat disetel hanya setelah Window dibuat dan ditampilkan sebagai kesalahan dialog ".
Saya menyimpan ViewModel setelah Tampilan ditutup dan kadang-kadang saya kemudian membuka Tampilan baru menggunakan VM yang sama. Tampaknya menutup Tampilan baru sebelum Tampilan lama dikumpulkan sampah mengakibatkan DialogResultChanged mencoba menyetel properti DialogResult pada jendela tertutup, sehingga memicu kesalahan.
Solusi saya adalah mengubah DialogResultChanged untuk memeriksa properti IsLoaded jendela :
private static void DialogResultChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = d as Window; if (window != null && window.IsLoaded) window.DialogResult = e.NewValue as bool?; }
Setelah membuat perubahan ini, semua lampiran ke dialog tertutup akan diabaikan.
sumber
Saya akhirnya memadukan jawaban Joe White dan beberapa kode dari jawaban Adam Mills , karena saya perlu menunjukkan kontrol pengguna di jendela yang dibuat secara terprogram. Jadi DialogCloser tidak perlu berada di jendela, bisa di kontrol pengguna itu sendiri
<UserControl ... xmlns:xw="clr-namespace:Wpf" xw:DialogCloser.DialogResult="{Binding DialogResult}">
Dan DialogCloser akan menemukan jendela kontrol pengguna jika tidak dilampirkan ke jendela itu sendiri.
namespace Wpf { public static class DialogCloser { public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached( "DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged)); private static void DialogResultChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = d.GetWindow(); if (window != null) window.DialogResult = e.NewValue as bool?; } public static void SetDialogResult(DependencyObject target, bool? value) { target.SetValue(DialogResultProperty, value); } } public static class Extensions { public static Window GetWindow(this DependencyObject sender_) { Window window = sender_ as Window; return window ?? Window.GetWindow( sender_ ); } } }
sumber
Perilaku adalah cara paling nyaman di sini.
Dari satu sisi, ini dapat diikat ke model tampilan yang diberikan (yang dapat menandakan "tutup formulir!")
Dari sisi lain, ia memiliki akses ke formulir itu sendiri sehingga dapat berlangganan acara khusus formulir yang diperlukan, atau menampilkan dialog konfirmasi, atau apa pun.
Menulis perilaku yang diperlukan bisa terlihat membosankan untuk pertama kalinya. Namun, mulai sekarang, Anda dapat menggunakannya kembali di setiap formulir yang Anda butuhkan dengan cuplikan XAML satu baris yang tepat. Dan jika perlu, Anda dapat mengekstraknya sebagai rakitan terpisah sehingga dapat dimasukkan ke dalam proyek berikutnya yang Anda inginkan.
sumber
Mengapa tidak melewatkan jendela sebagai parameter perintah?
C #:
private void Cancel( Window window ) { window.Close(); } private ICommand _cancelCommand; public ICommand CancelCommand { get { return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>( ( window ) => Cancel( window ), ( window ) => ( true ) ) ); } }
XAML:
<Window x:Class="WPFRunApp.MainWindow" x:Name="_runWindow" ... <Button Content="Cancel" Command="{Binding Path=CancelCommand}" CommandParameter="{Binding ElementName=_runWindow}" />
sumber
Window
jenis yang agak bukan MVVM "murni". Lihat jawaban ini , di mana VM tidak terbatas pada suatuWindow
objek.Solusi lain adalah membuat properti dengan INotifyPropertyChanged dalam Model Tampilan seperti DialogResult, lalu di Code Behind tulis ini:
public class SomeWindow: ChildWindow { private SomeViewModel _someViewModel; public SomeWindow() { InitializeComponent(); this.Loaded += SomeWindow_Loaded; this.Closed += SomeWindow_Closed; } void SomeWindow_Loaded(object sender, RoutedEventArgs e) { _someViewModel = this.DataContext as SomeViewModel; _someViewModel.PropertyChanged += _someViewModel_PropertyChanged; } void SomeWindow_Closed(object sender, System.EventArgs e) { _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged; this.Loaded -= SomeWindow_Loaded; this.Closed -= SomeWindow_Closed; } void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == SomeViewModel.DialogResultPropertyName) { this.DialogResult = _someViewModel.DialogResult; } } }
Fragmen terpenting adalah
_someViewModel_PropertyChanged
.DialogResultPropertyName
bisa menjadi beberapa string const publik diSomeViewModel
.Saya menggunakan trik semacam ini untuk membuat beberapa perubahan dalam Kontrol Tampilan jika hal ini sulit dilakukan di ViewModel. OnPropertyChanged di ViewModel Anda dapat melakukan apa pun yang Anda inginkan dalam View. ViewModel masih 'unit testable' dan beberapa baris kecil kode di belakang tidak ada bedanya.
sumber
Saya akan melakukannya dengan cara ini:
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using GalaSoft.MvvmLight.Messaging; // View public partial class TestCloseWindow : Window { public TestCloseWindow() { InitializeComponent(); Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close()); } } // View Model public class MainViewModel: ViewModelBase { ICommand _closeChildWindowCommand; public ICommand CloseChildWindowCommand { get { return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => { Messenger.Default.Send(new CloseWindowMsg()); })); } } } public class CloseWindowMsg { }
sumber
Saya telah membaca semua jawaban tetapi saya harus mengatakan, kebanyakan dari mereka tidak cukup baik atau bahkan lebih buruk.
Anda bisa menangani ini dengan cantik dengan kelas DialogService yang tanggung jawabnya adalah menampilkan jendela dialog dan mengembalikan hasil dialog. Saya telah membuat proyek sampel yang mendemonstrasikan implementasi dan penggunaannya.
berikut adalah bagian terpenting:
//we will call this interface in our viewmodels public interface IDialogService { bool? ShowDialog(object dialogViewModel, string caption); } //we need to display logindialog from mainwindow public class MainWindowViewModel : ViewModelBase { public string Message {get; set;} public void ShowLoginCommandExecute() { var loginViewModel = new LoginViewModel(); var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in"); //after dialog is closed, do someting if (dialogResult == true && loginViewModel.IsLoginSuccessful) { this.Message = string.Format("Hello, {0}!", loginViewModel.Username); } } } public class DialogService : IDialogService { public bool? ShowDialog(object dialogViewModel, string caption) { var contentView = ViewLocator.GetView(dialogViewModel); var dlg = new DialogWindow { Title = caption }; dlg.PART_ContentControl.Content = contentView; return dlg.ShowDialog(); } }
Bukankah ini lebih sederhana? lebih kaku, lebih mudah dibaca dan terakhir tetapi tidak kalah mudahnya untuk di-debug daripada EventAggregator atau solusi serupa lainnya?
seperti yang Anda lihat, Dalam model tampilan saya, saya telah menggunakan pendekatan ViewModel pertama yang dijelaskan dalam posting saya di sini: Praktik terbaik untuk memanggil View dari ViewModel di WPF
Tentu saja, dalam dunia nyata,
DialogService.ShowDialog
harus memiliki lebih banyak opsi untuk mengkonfigurasi dialog, misalnya tombol dan perintah yang harus mereka jalankan. Ada cara berbeda untuk melakukannya, tetapi di luar ruang lingkup :)sumber
Meskipun ini tidak menjawab pertanyaan tentang bagaimana melakukan ini melalui viewmodel, ini menunjukkan bagaimana melakukannya hanya dengan menggunakan XAML + SDK campuran.
Saya memilih untuk mengunduh dan menggunakan dua file dari Blend SDK, yang keduanya Anda dapat sebagai paket dari Microsoft melalui NuGet. File-file tersebut adalah:
System.Windows.Interactivity.dll dan Microsoft.Expression.Interactions.dll
Microsoft.Expression.Interactions.dll memberi Anda kemampuan bagus seperti kemampuan untuk mengatur properti atau menjalankan metode pada viewmodel Anda atau target lain dan memiliki widget lain di dalamnya juga.
Beberapa XAML:
<Window x:Class="Blah.Blah.MyWindow" ... xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" ...> <StackPanel> <Button x:Name="OKButton" Content="OK"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" PropertyName="DialogResult" Value="True" IsEnabled="{Binding SomeBoolOnTheVM}" /> </i:EventTrigger> </Button> <Button x:Name="CancelButton" Content="Cancel"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" PropertyName="DialogResult" Value="False" /> </i:EventTrigger> </Button> <Button x:Name="CloseButton" Content="Close"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <!-- method being invoked should be void w/ no args --> <ei:CallMethodAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" MethodName="Close" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> <StackPanel> </Window>
Perhatikan bahwa jika Anda hanya menggunakan perilaku OK / Cancel sederhana, Anda dapat menggunakan properti IsDefault dan IsCancel selama jendela ditampilkan dengan Window.ShowDialog ().
Saya pribadi mengalami masalah dengan tombol yang memiliki properti IsDefault disetel ke true, tetapi disembunyikan ketika halaman dimuat. Tampaknya tidak ingin bermain dengan baik setelah itu ditampilkan, jadi saya hanya mengatur properti Window.DialogResult seperti yang ditunjukkan di atas dan berfungsi untuk saya.
sumber
Ini adalah solusi bug gratis sederhana (dengan kode sumber), Ini bekerja untuk saya.
Dapatkan ViewModel Anda dari
INotifyPropertyChanged
Buat properti yang dapat diamati CloseDialog di ViewModel
public void Execute() { // Do your task here // if task successful, assign true to CloseDialog CloseDialog = true; } private bool _closeDialog; public bool CloseDialog { get { return _closeDialog; } set { _closeDialog = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName]string property = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } }
}
Lampirkan Handler di View untuk perubahan properti ini
Sekarang Anda hampir selesai. Dalam acara handler make
DialogResult = true
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "CloseDialog") { DialogResult = true; } }
sumber
Buat
Dependency Property
di AndaView
/ apa sajaUserControl
(atauWindow
Anda ingin menutup). Seperti dibawah ini:public bool CloseTrigger { get { return (bool)GetValue(CloseTriggerProperty); } set { SetValue(CloseTriggerProperty, value); } } public static readonly DependencyProperty CloseTriggerProperty = DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged))); private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) { //write Window Exit Code }
Dan ikat dari properti ViewModel Anda :
<Window x:Class="WpfStackOverflowTempProject.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="525" CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"
Properti Di
VeiwModel
:private bool closeWindow; public bool CloseWindow { get { return closeWindow; } set { closeWindow = value; RaiseChane("CloseWindow"); } }
Sekarang picu operasi penutupan dengan mengubah
CloseWindow
nilai di ViewModel. :)sumber
Di mana Anda perlu menutup jendela, cukup letakkan ini di viewmodel:
ta-da
foreach (Window window in Application.Current.Windows) { if (window.DataContext == this) { window.Close(); return; } }
sumber
Application.Current.MainWindow.Close()
Cukup!
sumber