Fechar janela de ViewModel


95

Estou criando um login usando um window controlpara permitir que um usuário faça login em um WPFaplicativo que estou criando.

Até agora, criei um método que verifica se o usuário inseriu as credenciais corretas para o usernamee passwordem a textboxna tela de login, bindingdois properties.

Eu consegui isso criando um boolmétodo, assim;

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();
} 

Eu também tenho um commandque eu bindno meu botão dentro do xamlmesmo;

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

Ao inserir o nome de usuário e a senha ele executa o código apropriado, esteja certo ou errado. Mas como posso fechar esta janela do ViewModel quando o nome de usuário e a senha estão corretos?

Eu já tentei usar um, dialog modalmas não deu muito certo. Além disso, em meu app.xaml, fiz algo como o seguinte, que carrega a página de login primeiro e, depois de verdadeiro, carrega o aplicativo real.

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);
    }
}

Pergunta: Como posso fechar o Login Window controldo ViewModel?

Desde já, obrigado.


Respostas:


147

Você pode passar a janela para seu ViewModel usando o CommandParameter. Veja meu exemplo abaixo.

Eu implementei um CloseWindowmétodo que pega um Windows como parâmetro e o fecha. A janela é passada para o ViewModel via CommandParameter. Observe que você precisa definir um x:Namepara a janela que deve ser fechada. Em minha janela XAML, chamo esse método via Commande passo a própria janela como um parâmetro para o ViewModel usando 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();
    }
}

Visão

<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>

Observe que estou usando a estrutura leve MVVM, mas o princípio se aplica a todos os aplicativos wpf.

Esta solução viola o padrão MVVM, porque o modelo de visualização não deve saber nada sobre a implementação da IU. Se você deseja seguir estritamente o paradigma de programação MVVM, você deve abstrair o tipo de visualização com uma interface.

Solução em conformidade com MVVM (antigo EDIT2)

o usuário Crono menciona um ponto válido na seção de comentários:

Passar o objeto Window para o modelo de visualização quebra o padrão MVVM IMHO, porque força sua VM a saber em que está sendo visualizado.

Você pode corrigir isso introduzindo uma interface que contém um método de fechamento.

Interface:

public interface ICloseable
{
    void Close();
}

Seu ViewModel refatorado terá a seguinte aparência:

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();
    }
}

Você deve referenciar e implementar a ICloseableinterface em sua visão

Ver (código por trás)

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

Resposta à pergunta original: (antigo EDIT1)

Seu botão de login (CommandParameter adicionado):

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

Seu código:

 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();
     }
 }

1
Obrigado pela atualização @Joel. Uma última pergunta, devido ao método tomar um parâmetro de Window, e quando eu chamo esse método dentro do meu comando, ele espera um parâmetro, eu criaria um parâmetro Window local que é chamado para o método, por exemplo; private void LoginExecute(){this.CheckLogin();}<- CheckLogin precisa de um parâmetro.
WPFNoob

desculpe, eu não entendi, você poderia esclarecer sua pergunta um pouco?
Joel

14
Se você não gosta de nomear suas janelas, você também pode vincular o parâmetro desta forma:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
Passar o Windowobjeto para o modelo de visualização quebra o padrão MVVM IMHO, porque força sua VM a saber em que está sendo visualizado. E se a visualização fosse uma guia encaixada em uma interface MDI? A maneira correta de fazer isso IMHO é passar algum tipo de interface IUIHost que implemente um método Close, e ter qualquer visão que você queira mostrar ao seu VM implementá-lo.
Crono

2
Está tudo bem porque a interface oculta a implementação concreta do ViewModel. O ViewModel não sabe nada sobre a visualização, exceto que implementa um método Close (). Assim, a visualização pode ser qualquer coisa: uma janela WPF, formulário WinForms, aplicativo UWP ou até mesmo uma grade WPF. Ele separa a visão do modelo de visão.
Joel de

34

Mantendo o MVVM, acho que usar Behaviors from the Blend SDK (System.Windows.Interactivity) ou uma solicitação de interação personalizada do Prism pode funcionar muito bem para esse tipo de situação.

Se estiver seguindo a rota do comportamento, aqui está a ideia geral:

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();
        }
    }
}

Então, em sua janela, você apenas vincularia o CloseTrigger a um valor booleano que seria definido quando você desejasse que a janela fechasse.

<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>

Finalmente, seu DataContext / ViewModel teria uma propriedade que você definiu quando queria que a janela fechasse assim:

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;
}

(defina seu Window.DataContext = new MainWindowViewModel ())


Obrigado pela resposta @Steve, você mencionou sobre vincular o CloseTrigger a um booleanvalor. Quando você disse isso, queria que eu criasse um DataTriggerpara alcançá-lo?
WPFNoob

Desculpe, eu deveria ter sido mais explícito - eu teria uma propriedade no meu modelo de visualização (no exemplo acima, um chamado CloseTrigger) que seria definido como verdadeiro, o que acabaria acionando o comportamento.
Atualizei

Isso funcionou, mas eu tive que mudar a maneira como meu aplicativo carregava. Como eu estava usando uma janela para meu aplicativo principal, ele eliminou todas as janelas filho também. Obrigado.
WPFNoob

Definir uma propriedade como true para executar uma ação é uma IMO fedorenta.
Josh Noe

33

Normalmente coloco um evento no modelo de visualização quando preciso fazer isso e, em seguida, conecto-o ao Window.Close()ao vincular o modelo de visualização à janela

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

E ao criar a janela de login

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

loginWindow.ShowDialog(); 

11
O delegado anônimo é escrito rapidamente, mas é importante notar que o evento não pode ser cancelado (o que pode ou não ser um problema). Normalmente é melhor com um manipulador de eventos completo.
Mathieu Guindon

Eu gosto mais disso. É difícil evitar o processamento especial ao exibir a janela (por exemplo Loaded, ContentRenderedpara a janela principal, serviços de diálogo, etc.), adicionar um pouco a ela por meio do evento ViewModel é bastante simples para mim. 3 linhas de código realmente não precisam de nenhuma solução de reutilização. PS: MVVM puro é para nerds de qualquer maneira.
Sinatr

Rapaz, isso me ajudou.
Dimitri

Isso é muito melhor do que a resposta aceita, porque não quebra o padrão MVVM.
Spook

22

pode ser tarde, mas aqui está a minha resposta

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}

1
por que esta não é a resposta real?
user2529011

1
@ user2529011 alguns, pelo menos, reclamariam que o viewmodel não deveria saber nada sobre Application.Current.Windows
geralmente suporta Monica

-1. O modelo de visão não deve saber nada sobre a visão. Você também pode simplesmente escrever no código por trás desse assunto.
Alejandro

13

Bem, aqui está algo que usei em vários projetos. Pode parecer um hack, mas funciona bem.

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;
    }
}

Agora você pode ligar DialogResulta uma VM e definir seu valor de uma propriedade. O Windowserá fechado, quando o valor for definido.

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

Este é um resumo do que está sendo executado em nosso ambiente de produção

<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>

Como você pode ver, estou declarando o namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper"primeiro e depois a vinculação hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

A AttachedPropertyaparência é assim. Não é o mesmo que postei ontem, mas IMHO não deve surtir efeito.

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
}

Não, não é uma pergunta boba. Basta colocar a declaração da ligação no <Window />elemento conforme ilustrei no meu recorte. Eu estava com preguiça de escrever o resto (declarações de namespace etc), que geralmente também é declarado lá.
DHN

1
Por favor, consulte minha edição. Postei o código de produção, então tenho certeza de que está funcionando. Parece um pouco diferente, mas o código que postei ontem também deve funcionar.
DHN

Obrigado por esclarecer isso. Descobri que eu estava chamando o namespace errado: S. Eu só preciso criar um datatriggere atribuí-lo ao botão para que funcione? Mais uma vez, desculpe pela pergunta nooby.
WPFNoob

Obrigado - bem, estou ciente de que estou fazendo muitas perguntas que podem parecer bobas e estúpidas e desperdiçando o tempo das pessoas! Mas voltando à minha pergunta. Depois de tudo que você mencionou, como faço para fechar a janela? Use um DataTrigger¬ and setting value true`?
WPFNoob

1
Bem, essa é a parte, estou deixando para você. ; o) Pense sobre DataContexto Dialog. Eu esperaria que a VM definida como DataContextfornecesse um comando, que define a propriedade DialogResultou o que você vinculou trueou false, para que o Dialogfeche.
DHN

13

Jeito fácil

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Implementar em ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Adicionar ajudante do gerenciador de janelas geral

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();
            }
        }
    }
}

E feche assim no modelo de visão

WindowManager.CloseWindow(ViewID);

Uma solução muito boa.
DonBoitnott

Eu mudei o WindowManager um pouco para definir o dialogresult ao fechar o win public static void CloseWindow (Guid id, bool dialogResult) {foreach (Window window in Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} chame-o assim: WindowManager.CloseWindow (_viewId, true);
Lebhero

Uma boa solução, porém, faz um acoplamento estreito entre o modelo de visão e WindowManager, que por sua vez é estreitamente acoplado com View(em termos de PresentationFramework). Seria melhor se WindowManagerum serviço fosse passado para o viewmodel por meio de uma interface. Então você seria capaz de (digamos) migrar sua solução para uma plataforma diferente facilmente.
Spook

4

Aqui está um exemplo simples usando o MVVM Light Messenger em vez de um evento. O modelo de visualização envia uma mensagem de fechamento quando um botão é clicado:

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

    public RelayCommand QuitCommand { get; private set; }

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

Em seguida, ele é recebido no código por trás da janela.

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

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }

Você pode aconselhar onde posso encontrar a implementação do CloseMessage?
Roman O

CloseMessage é apenas uma classe vazia, usada para identificar o tipo de mensagem que está sendo enviada. (Também pode conter informações de mensagens complexas, que não são necessárias aqui).
IngoB,

4

Que tal isso ?

ViewModel:

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

Em seu ViewModel, use CloseAction () para fechar a janela, como no exemplo acima.

Visão:

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());
}

3

Eu sei que este é um post antigo, provavelmente ninguém iria rolar até aqui, eu sei que não. Então, depois de horas tentando coisas diferentes, eu encontrei esse blog e cara o matou. A maneira mais simples de fazer isso, experimentei e funciona como um encanto.

Blog

No 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();
}

...

adicione uma propriedade Action ao ViewModel, mas defina-a a partir do arquivo code-behind do View. Isso nos permitirá definir dinamicamente uma referência no ViewModel que aponta para o View.

No ViewModel, vamos simplesmente adicionar:

public Action CloseAction { get; set; }

E na View, vamos defini-lo como:

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());
}

O link está quebrado: /
gusmally suporta Monica

@gusmally, você tem certeza? Eu abri normalmente, tente novamente jkshay.com/…
Serlok

2

Você pode criar um novo manipulador de eventos no ViewModel como este.

public event EventHandler RequestClose;

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

Em seguida, defina RelayCommand para 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();
    }

Em seguida, no conjunto de arquivos XAML

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

Defina o DataContext no arquivo xaml.cs e inscreva-se no evento que criamos.

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(); };
    }
}

Usei um MVVM Light Messenger em vez do evento.
Hamish Gunn,

1

Minha maneira oferecida é Declare evento em ViewModel e use blend InvokeMethodAction como abaixo.

ViewModel de amostra

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();
        });
    }
}

A interface que pode ser fechada é a seguinte, mas não requer a execução desta ação. ICloseable ajudará na criação de um serviço de visão genérico, então se você construir visão e ViewModel por injeção de dependência, então o que você pode fazer é

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Uso de 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();
        }

E abaixo está o Xaml, você pode usar este xaml mesmo se não implementar a interface, ele só precisará do seu modelo de visualização para gerar 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>


1

Você pode usar Messengerdo kit de ferramentas MVVMLight. em seu ViewModelenviar uma mensagem como esta:
Messenger.Default.Send(new NotificationMessage("Close"));
então em seu código do windows, depois InitializeComponent, cadastre-se para aquela mensagem assim:

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

você pode encontrar mais sobre o kit de ferramentas MVVMLight aqui: kit de ferramentas MVVMLight no Codeplex

Observe que não há uma "regra sem code-behind" no MVVM e você pode fazer o registro de mensagens em uma visão code-behind.


0

É simples. Você pode criar sua própria classe ViewModel para Login - LoginViewModel. Você pode criar view var dialog = new UserView (); dentro do seu LoginViewModel. E você pode configurar o Comando LoginCommand no botão.

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

e

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

Classe 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;
    }
}

3
Sim, também é uma solução válida. Mas se você quiser se ater ao MVVM e ao desacoplamento de VMs e visualizações, você vai quebrar o padrão.
DHN

Olá @misak - tendo tentando implementar sua solução (como as outras respostas), ele lança um Object reference not set to an instance of an object.para o método CloseLoginView. Alguma sugestão de como resolver esse problema?
WPFNoob

@WPFNoob - Eu uso esta solução novamente. O exemplo funciona corretamente. Quer enviar uma solução completa do Visual Studio por e-mail?
misak

@WPFNoob - vejo o problema. Você está criando uma instância como var dialog = new UserView () ;. Limpar palavra-chave var (instância local) substitui a instância global em LoginViewModel
misak

0

Esta é uma maneira bastante simples de fazer:

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();
}

Não vejo nada de errado com a resposta que você escolheu, apenas pensei que essa poderia ser uma maneira mais simples de fazer isso!


8
Isso requer que seu ViewModel conheça e faça referência a sua View.
AndrewS

@AndrewS por que isso é ruim?
thestephenstanton

9
Para seguir o padrão MVVM, o ViewModel não deve saber sobre o View.
MetalMikester

1
Para expandir isso, o objetivo do MVVM é tornar a maior parte de sua unidade de código GUI testável. As visualizações têm uma tonelada de dependências que as tornam impossíveis de teste de unidade. ViewModels devem ser testáveis ​​por unidade, mas se você der a eles uma dependência direta da visão, eles não serão.
ILMTitan

E para expandir isso ainda mais, o MVVM escrito corretamente permite que você migre a solução para uma plataforma diferente facilmente. Em particular, você deve ser capaz de reutilizar seus viewmodels sem quaisquer alterações. Nesse caso, se você movesse sua solução para o Android, ela não funcionaria, porque o Android não tem o conceito de janela. -1 para uma solução de quebra de MVVM.
Spook

0

Você pode tratar a janela como um serviço (por exemplo, serviço de IU) e passar para o modelo de visualização por meio de uma interface , como:

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);
    }
}

Essa solução tem muitas vantagens de passar a própria visão para o modelo de visão sem ter o lado negativo de quebrar o MVVM, porque embora a visão fisicamente seja passada para o modelo de visão, este ainda não conhece o primeiro, vê apenas alguns IMainWindowAccess. Por exemplo, se quiséssemos migrar essa solução para outra plataforma, seria apenas uma questão de implementar IMainWindowAccessadequadamente para, digamos, um Activity.

Estou postando a solução aqui para propor uma abordagem diferente dos eventos (embora seja na verdade muito semelhante), porque parece um pouco mais simples do que eventos para implementar (anexando / desanexando etc.), mas ainda se alinha bem com o padrão MVVM.


-1

Você pode fechar a janela atual apenas usando o seguinte código:

Application.Current.Windows[0].Close();

6
Se você tiver mais de uma janela, isso pode fechar a janela errada.
Sasha

17
Oh Deus! você abateu MVVM
Hossein Shahdoost

-7

System.Environment.Exit (0); no modelo de exibição funcionaria.


6
Não, não vai. Ele sairá do aplicativo e não fechará a janela atual.
Tilak

isso resolveu meu problema, pois fechando o mainWindow (pra mim) == saindo do aplicativo. Todos os métodos propostos, exceto este, tinham pontos complicados quando eram chamados de threads diferentes; mas esta abordagem realmente não se importa com quem é o thread de chamada :) isso era tudo que eu precisava!
Hamed

BuAHahahAHahahAha desculpe, não pude resistir
L.Trabacchin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.