Tutup Jendela dari ViewModel

96

Saya membuat sebuah Login menggunakan a window controluntuk memungkinkan pengguna login ke WPFaplikasi yang saya buat.

Sejauh ini, saya telah membuat metode yang memeriksa apakah pengguna telah memasukkan kredensial yang benar untuk usernamedan passworddi textboxlayar login, bindingdua properties.

Saya telah mencapai ini dengan membuat boolmetode, seperti;

public bool CheckLogin()
{
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in.");

        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
}

public ICommand ShowLoginCommand
{
    get
    {
        if (this.showLoginCommand == null)
        {
            this.showLoginCommand = new RelayCommand(this.LoginExecute, null);
        }
        return this.showLoginCommand;
    }
}

private void LoginExecute()
{
    this.CheckLogin();
} 

Saya juga memiliki commandyang saya bindke tombol saya dalam xamlseperti itu;

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />

Ketika saya memasukkan nama pengguna dan kata sandi, ia menjalankan kode yang sesuai, apakah itu benar, atau salah. Tetapi bagaimana saya bisa menutup jendela ini dari ViewModel jika nama pengguna dan kata sandi sudah benar?

Saya sebelumnya telah mencoba menggunakan a dialog modaltetapi tidak berhasil. Selanjutnya, dalam app.xaml saya, saya telah melakukan sesuatu seperti berikut, yang memuat halaman login terlebih dahulu, kemudian setelah benar, memuat aplikasi sebenarnya.

private void ApplicationStart(object sender, StartupEventArgs e)
{
    Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var dialog = new UserView();

    if (dialog.ShowDialog() == true)
    {
        var mainWindow = new MainWindow();
        Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
        Current.MainWindow = mainWindow;
        mainWindow.Show();
    }
    else
    {
        MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK);
        Current.Shutdown(-1);
    }
}

Pertanyaan: Bagaimana cara menutup Login Window controldari ViewModel?

Terima kasih sebelumnya.

WPFNoob
sumber

Jawaban:

153

Anda dapat meneruskan jendela ke ViewModel Anda menggunakan CommandParameter. Lihat Contoh saya di bawah ini.

Saya telah menerapkan CloseWindowMetode yang mengambil Windows sebagai parameter dan menutupnya. Jendela diteruskan ke ViewModel melalui CommandParameter. Perhatikan bahwa Anda perlu menentukan x:Nameuntuk jendela yang harus ditutup. Di Jendela XAML saya, saya memanggil metode ini melalui Commanddan meneruskan jendela itu sendiri sebagai parameter untuk menggunakan ViewModel CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"

ViewModel

public RelayCommand<Window> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}

private void CloseWindow(Window window)
{
    if (window != null)
    {
       window.Close();
    }
}

Melihat

<Window x:Class="ClientLibTestTool.ErrorView"
        x:Name="TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="{x:Static localization:localization.HeaderErrorView}"
        Height="600" Width="800"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen">
    <Grid> 
        <Button Content="{x:Static localization:localization.ButtonClose}" 
                Height="30" 
                Width="100" 
                Margin="0,0,10,10" 
                IsCancel="True" 
                VerticalAlignment="Bottom" 
                HorizontalAlignment="Right" 
                Command="{Binding CloseWindowCommand, Mode=OneWay}" 
                CommandParameter="{Binding ElementName=TestWindow}"/>
    </Grid>
</Window>

Perhatikan bahwa saya menggunakan kerangka kerja ringan MVVM, tetapi prinsipnya berlaku untuk setiap aplikasi wpf.

Solusi ini melanggar pola MVVM, karena model tampilan tidak boleh mengetahui apa pun tentang Implementasi UI. Jika Anda ingin mengikuti paradigma pemrograman MVVM secara ketat, Anda harus mengabstraksi jenis tampilan dengan antarmuka.

Solusi sesuai MVVM (Sebelumnya EDIT2)

pengguna Crono menyebutkan poin yang valid di bagian komentar:

Meneruskan objek Window ke model tampilan memecah IMHO pola MVVM, karena memaksa vm Anda untuk mengetahui apa yang sedang dilihat.

Anda dapat memperbaikinya dengan memperkenalkan antarmuka yang berisi metode tutup.

Antarmuka:

public interface ICloseable
{
    void Close();
}

ViewModel Anda yang telah direfraktorisasi akan terlihat seperti ini:

ViewModel

public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(ICloseable window)
{
    if (window != null)
    {
        window.Close();
    }
}

Anda harus mereferensikan dan mengimplementasikan ICloseableantarmuka dalam tampilan Anda

Lihat (Kode di belakang)

public partial class MainWindow : Window, ICloseable
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

Jawaban untuk pertanyaan awal: (sebelumnya EDIT1)

Tombol Login Anda (Menambahkan CommandParameter):

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>

Kode Anda:

 public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution!

 public MainViewModel() 
 {
     //initialize the CloseWindowCommand. Again, mind the <Window>
     //you don't have to do this in your constructor but it is good practice, thought
     this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
 }

 public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter
 {
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in.");
        this.CloseWindow(loginWindow); //Added call to CloseWindow Method
        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
 }

 //Added CloseWindow Method
 private void CloseWindow(Window window)
 {
     if (window != null)
     {
         window.Close();
     }
 }
Joel
sumber
1
Terima kasih atas pembaruan @Joel. Satu pertanyaan terakhir, karena metode mengambil parameter Window, dan ketika saya memanggil metode itu dalam perintah saya, ia mengharapkan parameter, apakah saya akan membuat parameter Window lokal yang dipanggil untuk metode tersebut, misalnya; private void LoginExecute(){this.CheckLogin();}<- CheckLogin perlu menggunakan parameter.
WPFNoob
maaf saya tidak mengerti, bisakah Anda menjelaskan sedikit pertanyaan Anda?
Yoel
14
Jika Anda tidak suka menamai jendela Anda, Anda juga dapat mengikat parameter seperti ini:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman
33
Meneruskan Windowobjek ke model tampilan mematahkan IMHO pola MVVM, karena memaksa vm Anda untuk mengetahui apa yang sedang dilihat. Bagaimana jika tampilan adalah tab yang digalangkan di antarmuka MDI? Cara yang tepat untuk melakukan IMHO ini adalah dengan melewati beberapa jenis antarmuka IUIHost yang mengimplementasikan metode Tutup, dan memiliki tampilan apa pun yang Anda inginkan untuk menunjukkan vm Anda menerapkannya.
Crono
2
Tidak apa-apa karena antarmuka menyembunyikan implementasi konkret ke ViewModel. ViewModel tidak tahu apa-apa tentang tampilan kecuali bahwa ia mengimplementasikan metode Close (). Dengan demikian, tampilan bisa berupa apa saja: Jendela WPF, Formulir WinForms, Aplikasi UWP atau bahkan WPF Grid. Ini memisahkan tampilan dari viewmodel.
Joel
35

Saya biasanya meletakkan acara pada model tampilan ketika saya perlu melakukan ini dan kemudian menghubungkannya ke Window.Close()saat mengikat model tampilan ke jendela

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

    private void Login()
    {
        // Login logic here
        OnRequestClose(this, new EventArgs());
    }
}

Dan saat membuat jendela login

var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
    DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();

loginWindow.ShowDialog(); 
ChrisO
sumber
11
Delegasi anonim ditulis dengan cepat, tetapi perlu dicatat bahwa acara tersebut tidak dapat dibatalkan pendaftarannya (yang mungkin menjadi masalah atau tidak). Biasanya lebih baik dengan event handler yang lengkap.
Mathieu Guindon
1
Saya paling suka ini. Ada juga yang sulit untuk menghindari pemrosesan khusus saat menampilkan jendela (misalnya Loaded, ContentRendereduntuk jendela utama, layanan dialog, dll.), Menambahkan sedikit ke dalamnya melalui acara ViewModel cukup bersih bagi saya. 3 baris kode tidak benar-benar membutuhkan solusi yang dapat digunakan kembali. PS: MVVM murni untuk kutu buku.
Sinatr
Wah, ini membantu saya.
Dimitri
1
Ini jauh lebih baik daripada jawaban yang diterima, karena tidak merusak pola MVVM.
Spook
35

Tetap MVVM, saya pikir menggunakan Behaviors from the Blend SDK (System.Windows.Interactivity) atau permintaan interaksi khusus dari Prism dapat bekerja sangat baik untuk situasi semacam ini.

Jika mengikuti rute Perilaku, berikut gambaran umumnya:

public class CloseWindowBehavior : Behavior<Window>
{
    public bool CloseTrigger
    {
        get { return (bool)GetValue(CloseTriggerProperty); }
        set { SetValue(CloseTriggerProperty, value); }
    }

    public static readonly DependencyProperty CloseTriggerProperty =
        DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(CloseWindowBehavior), new PropertyMetadata(false, OnCloseTriggerChanged));

    private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as CloseWindowBehavior;

        if (behavior != null)
        {
            behavior.OnCloseTriggerChanged();
        }
    }

    private void OnCloseTriggerChanged()
    {
        // when closetrigger is true, close the window
        if (this.CloseTrigger)
        {
            this.AssociatedObject.Close();
        }
    }
}

Kemudian di jendela Anda, Anda hanya perlu mengikat CloseTrigger ke nilai boolean yang akan disetel saat Anda ingin jendela ditutup.

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:TestApp"
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Behaviors>
        <local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" />
    </i:Interaction.Behaviors>

    <Grid>

    </Grid>
</Window>

Terakhir, DataContext / ViewModel Anda akan memiliki properti yang akan Anda setel saat Anda ingin jendela ditutup seperti ini:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private bool closeTrigger;

    /// <summary>
    /// Gets or Sets if the main window should be closed
    /// </summary>
    public bool CloseTrigger
    {
        get { return this.closeTrigger; }
        set
        {
            this.closeTrigger = value;
            RaisePropertyChanged(nameof(CloseTrigger));
        }
    }

    public MainWindowViewModel()
    {
        // just setting for example, close the window
        CloseTrigger = true;
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

(setel Window.DataContext = new MainWindowViewModel ())

Steve Van Treeck
sumber
Terima kasih atas balasan @Steve, Anda menyebutkan tentang mengikat CloseTrigger ke suatu booleannilai. Ketika Anda mengatakan itu, apakah Anda bermaksud agar saya membuat DataTriggeruntuk mencapainya?
WPFNoob
Maaf, saya seharusnya lebih eksplisit - saya akan memiliki properti di viewmodel saya (dalam contoh di atas, yang disebut CloseTrigger) yang akan disetel ke true, yang pada akhirnya akan memicu perilaku. Saya memperbarui jawaban
Steve Van Treeck
Ini berhasil, tetapi saya harus mengubah cara aplikasi saya dimuat. Karena saya menggunakan Window untuk aplikasi utama saya, itu juga mematikan semua jendela anak. Terima kasih.
WPFNoob
Menyetel properti ke true untuk melakukan tindakan adalah IMO yang berbau.
Josh Noe
22

Mungkin terlambat, tapi inilah jawaban saya

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}
Ahmed Abuelnour
sumber
1
mengapa ini bukan jawaban yang sebenarnya?
pengguna2529011
1
@ user2529011 beberapa, setidaknya, akan mengeluh bahwa viewmodel seharusnya tidak tahu apa-apa tentang Application.Current.Windows
gusmally mendukung Monica
-1. Model tampilan seharusnya tidak tahu apa-apa tentang tampilan. Anda mungkin juga hanya menulisnya di kode di belakang untuk masalah itu.
Alejandro
13

Ini adalah sesuatu yang saya gunakan di beberapa proyek. Ini mungkin terlihat seperti retasan, tetapi berfungsi dengan baik.

public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window
{
    public static readonly DependencyProperty DialogResultProperty = 
        DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties), 
        new PropertyMetaData(default(bool?), OnDialogResultChanged));

    public bool? DialogResult
    {
        get { return (bool?)GetValue(DialogResultProperty); }
        set { SetValue(DialogResultProperty, value); }
    }

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = d as Window;
        if (window == null)
            return;

        window.DialogResult = (bool?)e.NewValue;
    }
}

Sekarang Anda dapat mengikat DialogResultke VM dan menyetel nilainya untuk sebuah properti. Wasiat Windowakan ditutup, saat nilainya ditetapkan.

<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />

Ini adalah abstrak dari apa yang berjalan di lingkungan produksi kami

<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" 
        xmlns:hlp="clr-namespace:AC.Frontend.Helper"
        MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen" Title="{Binding Title}"
        hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
        Language="{Binding UiCulture, Source={StaticResource Strings}}">
        <!-- A lot more stuff here -->
</Window>

Seperti yang Anda lihat, saya mendeklarasikan namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper" terlebih dahulu dan kemudian mengikat hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

The AttachedPropertyterlihat seperti ini. Ini tidak sama dengan yang saya posting kemarin, tetapi IMHO seharusnya tidak berpengaruh apa pun.

public class AttachedProperties
{
    #region DialogResult

    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wnd = d as Window;
        if (wnd == null)
            return;

        wnd.DialogResult = (bool?) e.NewValue;
    }

    public static bool? GetDialogResult(DependencyObject dp)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        return (bool?)dp.GetValue(DialogResultProperty);
    }

    public static void SetDialogResult(DependencyObject dp, object value)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        dp.SetValue(DialogResultProperty, value);
    }

    #endregion
}
DHN
sumber
Tidak, ini bukan pertanyaan yang konyol. Letakkan saja deklarasi pengikatan di <Window />elemen seperti yang saya ilustrasikan di potongan saya. Saya terlalu malas untuk menulis sisanya (deklarasi namespace dll), yang biasanya juga dideklarasikan di sana.
DHN
1
Tolong lihat suntingan saya. Saya memposting kode produksi, jadi saya yakin itu berfungsi. Ini terlihat sedikit berbeda, tetapi kode yang saya posting kemarin juga harus berfungsi.
DHN
Terima kasih sudah menyelesaikannya. Ternyata saya memanggil namespace yang salah: S. Apakah saya hanya perlu membuat datatriggerdan menetapkannya ke tombol agar berfungsi? Sekali lagi maaf untuk pertanyaan nooby.
WPFNoob
Terima kasih - yah, saya hanya menyadari diri saya mengajukan terlalu banyak pertanyaan yang mungkin tampak konyol dan bodoh serta membuang-buang waktu orang! Tapi kembali ke pertanyaanku. Setelah semua yang Anda sebutkan, bagaimana cara menutup jendela? Gunakan DataTrigger¬ and setting value true`?
WPFNoob
1
Nah itu bagiannya, saya serahkan kepada Anda. ; o) Pikirkan tentang DataContextdari Dialog. Saya berharap, bahwa VM ditetapkan sebagai DataContextmenyediakan perintah, yang menetapkan properti DialogResultatau apa pun yang Anda terikat trueatau false, sehingga Dialogditutup.
DHN
13

Jalan mudah

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Terapkan ke ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

    public MyViewVM()
    {
        _viewId = Guid.NewGuid();
    }
}

Tambahkan pembantu pengelola jendela umum

public static class WindowManager
{
    public static void CloseWindow(Guid id)
    {
        foreach (Window window in Application.Current.Windows)
        {
            var w_id = window.DataContext as IRequireViewIdentification;
            if (w_id != null && w_id.ViewID.Equals(id))
            {
                window.Close();
            }
        }
    }
}

Dan tutup seperti ini di viewmodel

WindowManager.CloseWindow(ViewID);
RassK
sumber
Solusi yang sangat bagus.
DonBoitnott
saya mengubah sedikit WindowManager untuk menyetel hasil dialografik ketika menutup win public static void CloseWindow (ID Panduan, dialogResult bool) {foreach (Jendela jendela di Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; jika (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} menyebutnya seperti: WindowManager.CloseWindow (_viewId, true);
lebhero
Solusi bagus, meskipun membuat kopling erat antara model tampilan dan WindowManager, yang pada gilirannya terkait erat dengan View(dalam hal PresentationFramework). Akan lebih baik jika WindowManagerlayanan diteruskan ke viewmodel melalui antarmuka. Kemudian Anda akan dapat (katakanlah) memigrasi solusi Anda ke platform yang berbeda dengan mudah.
Spook
4

Berikut ini contoh sederhana menggunakan MVVM Light Messenger alih-alih sebuah acara. Model tampilan mengirimkan pesan tutup saat tombol diklik:

    public MainViewModel()
    {
        QuitCommand = new RelayCommand(ExecuteQuitCommand);
    }

    public RelayCommand QuitCommand { get; private set; }

    private void ExecuteQuitCommand() 
    {
        Messenger.Default.Send<CloseMessage>(new CloseMessage());
    }

Kemudian itu diterima dalam kode di belakang jendela.

    public Main()
    {   
        InitializeComponent();
        Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage);
    }

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }
Hamish Gunn
sumber
Bisakah Anda memberi saran, di mana saya dapat menemukan implementasi CloseMessage?
Roman O
CloseMessage hanyalah kelas kosong, digunakan untuk mengidentifikasi jenis pesan yang dikirim. (Ini juga bisa berisi info pesan yang kompleks, yang tidak diperlukan di sini.)
IngoB
4

Bagaimana kalau begini ?

ViewModel:

class ViewModel
{
    public Action CloseAction { get; set; }
    private void Stuff()
    {
       // Do Stuff
       CloseAction(); // closes the window
    }
}

Dalam ViewModel Anda, gunakan CloseAction () untuk menutup jendela seperti pada contoh di atas.

Melihat:

public View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if (vm.CloseAction == null)
        vm.CloseAction = new Action(() => this.Close());
}
Segera
sumber
4

Saya tahu ini adalah posting lama, mungkin tidak ada yang akan menggulir sejauh ini, saya tahu saya tidak melakukannya. Jadi, setelah berjam-jam mencoba berbagai hal, saya menemukan blog ini dan bung membunuhnya. Cara termudah untuk melakukan ini, mencobanya dan itu berfungsi seperti pesona.

Blog

Di ViewModel:

...

public bool CanClose { get; set; }

private RelayCommand closeCommand;
public ICommand CloseCommand
{
    get
    {
        if(closeCommand == null)
        (
            closeCommand = new RelayCommand(param => Close(), param => CanClose);
        )
    }
}

public void Close()
{
    this.Close();
}

...

tambahkan properti Action ke ViewModel, tetapi tentukan dari file di belakang kode View. Ini akan membiarkan kita secara dinamis menentukan referensi pada ViewModel yang mengarah ke View.

Di ViewModel, kami hanya akan menambahkan:

public Action CloseAction { get; set; }

Dan di Tampilan, kami akan mendefinisikannya seperti ini:

public View()
{
    InitializeComponent() // this draws the View
    ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(() => this.Close());
}
Serlok
sumber
Tautan rusak: /
biasanya mendukung Monica
@ biasanya apakah kamu yakin? Saya membukanya secara normal
Serlok
2

Anda dapat membuat penangan Peristiwa baru di ViewModel seperti ini.

public event EventHandler RequestClose;

    protected void OnRequestClose()
    {
        if (RequestClose != null)
            RequestClose(this, EventArgs.Empty);
    }

Kemudian Tentukan RelayCommand untuk ExitCommand.

private RelayCommand _CloseCommand;
    public ICommand CloseCommand
    {
        get
        {
            if(this._CloseCommand==null)
                this._CloseCommand=new RelayCommand(CloseClick);
            return this._CloseCommand;
        }
    }

    private void CloseClick(object obj)
    {
        OnRequestClose();
    }

Kemudian Di set file XAML

<Button Command="{Binding CloseCommand}" />

Setel DataContext di File xaml.cs dan Berlangganan ke acara yang kami buat.

public partial class MainWindow : Window
{
    private ViewModel mainViewModel = null;
    public MainWindow()
    {
        InitializeComponent();
        mainViewModel = new ViewModel();
        this.DataContext = mainViewModel;
        mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); };
    }
}
Jyotirmaya Prusty
sumber
Saya menggunakan MVVM Light Messenger sebagai ganti acara.
Hamish Gunn
1

Cara yang saya tawarkan adalah Menyatakan acara di ViewModel dan menggunakan campuran InvokeMethodAction seperti di bawah ini.

Contoh ViewModel

public class MainWindowViewModel : BindableBase, ICloseable
{
    public DelegateCommand SomeCommand { get; private set; }
    #region ICloseable Implementation
    public event EventHandler CloseRequested;        

    public void RaiseCloseNotification()
    {
        var handler = CloseRequested;
        if (handler != null)
        {
            handler.Invoke(this, EventArgs.Empty);
        }
    }
    #endregion

    public MainWindowViewModel()
    {
        SomeCommand = new DelegateCommand(() =>
        {
            //when you decide to close window
            RaiseCloseNotification();
        });
    }
}

I Antarmuka yang dapat ditutup adalah seperti di bawah ini tetapi tidak perlu melakukan tindakan ini. ICloseable akan membantu dalam membuat layanan tampilan umum, jadi jika Anda membuat tampilan dan ViewModel dengan injeksi ketergantungan maka yang dapat Anda lakukan adalah

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Penggunaan ICloseable

var viewModel = new MainWindowViewModel();
        // As service is generic and don't know whether it can request close event
        var window = new Window() { Content = new MainView() };
        var closeable = viewModel as ICloseable;
        if (closeable != null)
        {
            closeable.CloseRequested += (s, e) => window.Close();
        }

Dan di bawah ini adalah Xaml, Anda dapat menggunakan xaml ini meskipun Anda tidak mengimplementasikan antarmuka, ini hanya membutuhkan model tampilan Anda untuk memunculkan CloseRquested.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFRx"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" 
d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}">

<i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" >
        <ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>
    <Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/>
</Grid>

Rajnikant
sumber
1

Anda dapat menggunakan Messengerdari MVVMLight toolkit. di ViewModelkirim pesan Anda seperti ini:
Messenger.Default.Send(new NotificationMessage("Close"));
lalu di kode windows Anda di belakang, setelah InitializeComponent, daftar untuk pesan itu seperti ini:

Messenger.Default.Register<NotificationMessage>(this, m=>{
    if(m.Notification == "Close") 
    {
        this.Close();
    }
   });

Anda dapat menemukan lebih banyak tentang MVVMLight toolkit di sini: MVVMLight toolkit di Codeplex

Perhatikan bahwa tidak ada aturan "tidak ada kode di belakang sama sekali" di MVVM dan Anda dapat mendaftar untuk pesan dalam tampilan di belakang kode.

Amir Oveisi
sumber
0

Itu mudah. Anda dapat membuat kelas ViewModel Anda sendiri untuk Login - LoginViewModel. Anda bisa membuat view var dialog = new UserView (); di dalam LoginViewModel Anda. Dan Anda dapat mengatur Command LoginCommand menjadi tombol.

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />

dan

<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />

Kelas ViewModel:

public class LoginViewModel
{
    Window dialog;
    public bool ShowLogin()
    {
       dialog = new UserView();
       dialog.DataContext = this; // set up ViewModel into View
       if (dialog.ShowDialog() == true)
       {
         return true;
       }

       return false;
    }

    ICommand _loginCommand
    public ICommand LoginCommand
    {
        get
        {
            if (_loginCommand == null)
                _loginCommand = new RelayCommand(param => this.Login());

            return _loginCommand;
        }
    }

    public void CloseLoginView()
    {
            if (dialog != null)
          dialog.Close();
    }   

    public void Login()
    {
        if(CheckLogin()==true)
        {
            CloseLoginView();         
        }
        else
        {
          // write error message
        }
    }

    public bool CheckLogin()
    {
      // ... check login code
      return true;
    }
}
misak
sumber
3
Ya, ini juga solusi yang valid. Tetapi jika Anda ingin tetap berpegang pada MVVM dan pemisahan VM dan tampilan, Anda akan merusak polanya.
DHN
Hai @misak - setelah mencoba mengimplementasikan solusi Anda (seperti jawaban lain), ini melempar Object reference not set to an instance of an object.untuk metode CloseLoginView. Ada saran bagaimana mengatasi masalah itu?
WPFNoob
@WPFNoob - Saya baki solusi ini lagi. Contoh bekerja dengan benar. Apakah Anda ingin mengirim solusi studio visual lengkap melalui email?
misak
@WPFNoob - Saya melihat masalahnya. Anda membuat instance sebagai var dialog = new UserView () ;. Hapus kata kunci var (contoh lokal) menimpa contoh global di LoginViewModel
misak
0

Ini adalah cara saya melakukannya dengan cukup sederhana:

YourWindow.xaml.cs

//In your constructor
public YourWindow()
{
    InitializeComponent();
    DataContext = new YourWindowViewModel(this);
}

YourWindowViewModel.cs

private YourWindow window;//so we can kill the window

//In your constructor
public YourWindowViewModel(YourWindow window)
{
    this.window = window;
}

//to close the window
public void CloseWindow()
{
    window.Close();
}

Saya tidak melihat ada yang salah dengan jawaban yang Anda pilih, saya hanya berpikir ini mungkin cara yang lebih sederhana untuk melakukannya!

thestephenstanton
sumber
8
Ini membutuhkan ViewModel Anda untuk mengetahui dan mereferensikan View Anda.
AndrewS
@AndrewS mengapa itu buruk?
thestephenstanton
9
Untuk mengikuti pola MVVM, ViewModel seharusnya tidak mengetahui tentang View.
MetalMikester
1
Untuk memperluas ini, tujuan MVVM adalah membuat sebagian besar unit kode GUI Anda dapat diuji. Tampilan memiliki banyak dependensi yang membuatnya tidak mungkin untuk pengujian unit. ViewModels harus dapat diuji unit, tetapi jika Anda memberi mereka ketergantungan langsung pada tampilan, mereka tidak akan bisa.
ILMTitan
Dan untuk mengembangkan ini lebih jauh, MVVM yang ditulis dengan benar memungkinkan Anda untuk memindahkan solusi ke platform yang berbeda dengan mudah. Secara khusus, Anda harus dapat menggunakan kembali model tampilan Anda tanpa perubahan apa pun. Dalam hal ini jika Anda memindahkan solusi Anda ke Android, itu tidak akan berfungsi, karena Android tidak memiliki konsep Window. -1 untuk solusi pemecah MVVM.
Spook
0

Anda dapat memperlakukan jendela sebagai layanan (mis. Layanan UI) dan mengirimkan dirinya sendiri ke viewmodel melalui antarmuka , seperti:

public interface IMainWindowAccess
{
    void Close(bool result);
}

public class MainWindow : IMainWindowAccess
{
    // (...)
    public void Close(bool result)
    {
        DialogResult = result;
        Close();
    }
}

public class MainWindowViewModel
{
    private IMainWindowAccess access;

    public MainWindowViewModel(IMainWindowAccess access)
    {
        this.access = access;
    }

    public void DoClose()
    {
        access.Close(true);
    }
}

Solusi ini memiliki sebagian besar keuntungan dalam meneruskan tampilan itu sendiri ke model tampilan tanpa kerugian merusak MVVM, karena meskipun tampilan secara fisik diteruskan ke model tampilan, yang terakhir masih tidak tahu tentang model tampilan, ia hanya melihat beberapa IMainWindowAccess. Jadi misalnya jika kita ingin memigrasi solusi ini ke platform lain, itu hanya masalah penerapan IMainWindowAccessdengan benar, katakanlah, sebuah Activity.

Saya memposting solusi di sini untuk mengusulkan pendekatan yang berbeda dari peristiwa (meskipun sebenarnya sangat mirip), karena tampaknya sedikit lebih sederhana daripada peristiwa untuk diterapkan (melampirkan / melepaskan dll.), Tetapi masih selaras dengan baik dengan pola MVVM.

Menakuti
sumber
-1

Anda dapat menutup jendela saat ini hanya dengan menggunakan kode berikut:

Application.Current.Windows[0].Close();
chandudab
sumber
6
Jika Anda memiliki lebih dari satu jendela, ini mungkin menutup jendela yang salah.
Sasha
17
ya Tuhan! Anda telah membantai MVVM
Hossein Shahdoost
-7

System.Environment.Exit (0); dalam model tampilan akan bekerja.

rjain
sumber
6
Tidak, itu tidak akan. Ini akan keluar dari aplikasi, dan tidak menutup jendela saat ini.
Tilak
ini memecahkan masalah saya, karena menutup mainWindow (untuk saya) == keluar dari aplikasi. Semua metode yang diusulkan kecuali yang satu ini memiliki poin rumit ketika dipanggil dari utas yang berbeda; tetapi pendekatan ini tidak terlalu peduli siapa utas pemanggil :) hanya itu yang saya butuhkan!
Hamed
BuAHahahAHahahAha maaf tidak bisa menahan
L.Trabacchin