Закрыть окно из ViewModel

97

Я создаю логин, используя, window controlчтобы позволить пользователю войти в WPFприложение, которое я создаю.

До сих пор я создал метод, который проверяет, ввел ли пользователь правильные учетные данные для usernameи passwordв a textboxна экране входа в систему, bindingдва properties.

Я добился этого, создав такой boolметод;

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

У меня также есть то, commandчто я bindк моей кнопке внутри xamlвроде так;

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

Когда я ввожу имя пользователя и пароль, он выполняет соответствующий код, независимо от того, правильный он или неправильный. Но как я могу закрыть это окно из ViewModel, если и имя пользователя, и пароль верны?

Ранее я пробовал использовать, dialog modalно это не совсем сработало. Кроме того, в моем app.xaml я сделал что-то вроде следующего: сначала загружает страницу входа, а затем, когда она истинна, загружает реальное приложение.

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

Вопрос: Как мне закрыть логин Window controlиз ViewModel?

Заранее спасибо.

WPFNoob
источник

Ответы:

154

Вы можете передать окно своей ViewModel, используя CommandParameter. См. Мой пример ниже.

Я реализовал CloseWindowметод, который принимает параметр Windows как параметр и закрывает его. Окно передается в ViewModel через CommandParameter. Обратите внимание, что вам нужно определить x:Nameдля окна, которое должно быть закрыто. В моем окне XAML я вызываю этот метод через Commandи передаю само окно в качестве параметра 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();
    }
}

Посмотреть

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

Обратите внимание, что я использую среду MVVM light, но принцип применим ко всем приложениям wpf.

Это решение нарушает шаблон MVVM, потому что модель представления ничего не должна знать о реализации пользовательского интерфейса. Если вы хотите строго следовать парадигме программирования MVVM, вам необходимо абстрагировать тип представления с помощью интерфейса.

Решение, соответствующее MVVM (бывший EDIT2)

пользователь Crono упоминает действительную точку в разделе комментариев:

Передача объекта Window в модель представления нарушает шаблон MVVM IMHO, потому что он заставляет ваш vm знать, в чем он просматривается.

Вы можете исправить это, представив интерфейс, содержащий метод закрытия.

Интерфейс:

public interface ICloseable
{
    void Close();
}

Ваша реорганизованная ViewModel будет выглядеть так:

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

Вы должны указать и реализовать ICloseableинтерфейс в своем представлении.

Просмотр (код позади)

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

Ответ на исходный вопрос: (бывший EDIT1)

Ваша кнопка входа в систему (добавлен параметр CommandParameter):

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

Ваш код:

 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
Спасибо за обновление @Joel. Один последний вопрос, поскольку метод принимает параметр Window, и когда я вызываю этот метод в моей команде, он ожидает параметр, создам ли я локальный параметр Window, который вызывается для метода, например; private void LoginExecute(){this.CheckLogin();}<- CheckLogin должен принимать параметр.
WPFNoob
извините, я не понимаю, не могли бы вы немного уточнить свой вопрос?
Joel
14
Если вам не нравится называть свои окна, вы также можете привязать параметр следующим образом:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman
33
Передача Windowобъекта в модель представления нарушает шаблон MVVM IMHO, потому что он заставляет вашу виртуальную машину знать, в чем он просматривается. Что, если бы вместо этого представление было закрепленной вкладкой в ​​интерфейсе MDI? Правильный способ сделать это IMHO - передать какой-то интерфейс IUIHost, который реализует метод Close, и иметь любое представление, которое вы хотите, чтобы показать, что ваш vm реализует его.
Crono
2
Это нормально, потому что интерфейс скрывает конкретную реализацию ViewModel. ViewModel ничего не знает о представлении, кроме того, что он реализует метод Close (). Таким образом, представление может быть чем угодно: окном WPF, формой WinForms, приложением UWP или даже сеткой WPF. Он отделяет представление от модели представления.
Joel
35

Я обычно помещаю событие в модель представления, когда мне нужно это сделать, а затем подключаю его к Window.Close()при привязке модели представления к окну.

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

И при создании окна входа в систему

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

loginWindow.ShowDialog(); 
ChrisO
источник
11
Анонимный делегат записывается быстро, но стоит отметить, что событие нельзя отменить (что может быть проблемой, а может и не быть). Обычно лучше с полноценным обработчиком событий.
Mathieu Guindon
1
Мне это нравится больше всего. В любом случае трудно избежать специальной обработки при отображении окна (например Loaded, ContentRenderedдля главного окна, диалоговых служб и т. Д.), Добавление немного к нему через событие ViewModel довольно чистое, как для меня. Три строки кода на самом деле не требуют решения для повторного использования. PS: чистый MVVM в любом случае предназначен для ботаников.
Sinatr
Мальчик, это мне помогло.
Dimitri
1
Это намного лучше, чем принятый ответ, потому что он не нарушает шаблон MVVM.
Spook
35

Оставаясь MVVM, я думаю, что использование Behaviors из Blend SDK (System.Windows.Interactivity) или пользовательского запроса взаимодействия из Prism может очень хорошо сработать в такой ситуации.

Если вы идете по маршруту поведения, вот общая идея:

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

Затем в своем окне вы просто привяжете CloseTrigger к логическому значению, которое будет установлено, когда вы хотите, чтобы окно закрылось.

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

Наконец, ваш DataContext / ViewModel будет иметь свойство, которое вы должны установить, когда хотите, чтобы окно закрывалось следующим образом:

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

(установите Window.DataContext = new MainWindowViewModel ())

Стив Ван Трик
источник
Спасибо за ответ @Steve, вы упомянули о привязке CloseTrigger к booleanзначению. Когда вы это сказали, вы имели в виду, что я создаю DataTriggerдля этого?
WPFNoob
Извините, я должен был быть более точным - у меня было бы свойство в моей модели просмотра (в приведенном выше примере, которое называется CloseTrigger), для которого будет установлено значение true, что в конечном итоге вызовет поведение. Я обновил ответ
Стив Ван Трик
Это сработало, но мне пришлось изменить способ загрузки приложения. Поскольку я использовал Window для своего основного приложения, он также убил все дочерние окна. Спасибо.
WPFNoob
Установка свойства в значение true для выполнения действия - это неприятный запах ИМО.
Джош Ной
22

может быть уже поздно, но вот мой ответ

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}
Ахмед Абуэлнур
источник
1
почему это не настоящий ответ?
user2529011
1
@ user2529011 некоторые, по крайней мере, будут жаловаться, что модель просмотра ничего не должна знать о Application.Current.Windows
gusmally поддерживает Монику
-1. Модель представления вообще ничего не должна знать о представлении. Вы можете просто написать это в коде, если на то пошло.
Алехандро
13

Вот кое-что, что я использовал в нескольких проектах. Это может выглядеть как взлом, но работает нормально.

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

Теперь вы можете DialogResultвыполнить привязку к виртуальной машине и установить для нее значение свойства. WindowЗакроется, когда значение установлено.

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

Это резюме того, что работает в нашей производственной среде.

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

Как видите, я xmlns:hlp="clr-namespace:AC.Frontend.Helper"сначала объявляю пространство имен, а затем привязкуhlp:AttachedProperties.DialogResult="{Binding DialogResult}" .

Это AttachedPropertyвыглядит так. Это не то же самое, что я писал вчера, но имхо это не должно иметь никакого эффекта.

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
источник
Нет, это не глупый вопрос. Просто поместите объявление привязки в <Window />элемент, как я проиллюстрировал в своем фрагменте. Мне было просто лень писать остальное (объявления пространств имен и т. Д.), Которые обычно там тоже объявляются.
DHN
1
Пожалуйста, обратитесь к моему редактированию. Я опубликовал производственный код, поэтому уверен, что он работает. Это выглядит немного иначе, но код, который я опубликовал вчера, также должен работать.
DHN
Спасибо, что прояснили это. Оказалось, что я назвал неправильное пространство имен: S. Мне просто нужно создать datatriggerи назначить его кнопке, чтобы она работала? Снова извините за новостной вопрос.
WPFNoob
Спасибо - ну, я просто осознаю, что задаю слишком много вопросов, которые могут показаться глупыми и глупыми, и зря трачу время людей! Но вернемся к моему вопросу. После всего, что вы упомянули, как мне закрыть окно? Использовать DataTrigger¬ and setting value истину?
WPFNoob
1
Что ж, это часть, я оставляю тебе. ; o) Подумайте об DataContextэтом Dialog. Я бы ожидал, что виртуальная машина, установленная как, DataContextпредоставляет команду, которая устанавливает свойство DialogResultили все, что вы связали trueили false, так что Dialogзакрывается.
DHN
13

Простой способ

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Реализовать в ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Добавить общий помощник оконного менеджера

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

И закройте его вот так в viewmodel

WindowManager.CloseWindow(ViewID);
РассК
источник
Очень красивое решение.
DonBoitnott
Я немного изменил WindowManager, чтобы установить диалоговое окно при закрытии открытого статического void CloseWindow (Guid id, bool dialogResult) {foreach (окно окна в Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; если (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} назовите это так: WindowManager.CloseWindow (_viewId, true);
lebhero
Хорошее решение, хотя и обеспечивает тесную связь между viewmodel и WindowManager, которая, в свою очередь, тесно связана с View(с точки зрения PresentationFramework). Было бы лучше, если WindowManagerбы сервис передавался в viewmodel через интерфейс. Тогда вы сможете (скажем) легко перенести свое решение на другую платформу.
Spook
4

Вот простой пример использования MVVM Light Messenger вместо события. Модель представления отправляет сообщение о закрытии при нажатии кнопки:

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

    public RelayCommand QuitCommand { get; private set; }

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

Затем он передается в коде за окном.

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

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }
Хэмиш Ганн
источник
Подскажите, пожалуйста, где я могу найти реализацию CloseMessage?
Roman O
CloseMessage - это просто пустой класс, используемый для определения типа отправляемого сообщения. (Он также может содержать сложную информацию о сообщениях, которая здесь не нужна.)
IngoB
4

Как насчет этого ?

ViewModel:

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

В вашей ViewModel используйте CloseAction (), чтобы закрыть окно, как в примере выше.

Посмотреть:

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());
}
Анон
источник
4

Я знаю, что это старый пост, наверное, никто не стал бы прокручивать так далеко, я знаю, что не делал. Итак, после нескольких часов пробования разных вещей, я нашел этот блог, и чувак убил его. Самый простой способ сделать это, попробовал, и он отлично работает.

Блог

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

...

добавьте свойство Action в ViewModel, но определите его из файла кода программной части View. Это позволит нам динамически определять ссылку на ViewModel, которая указывает на View.

В ViewModel мы просто добавим:

public Action CloseAction { get; set; }

А в View мы определим его как таковой:

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());
}
Серлок
источник
Ссылка не работает: /
gusmally поддерживает Монику
@gusmally ты уверен? Я открыл его нормально, попробуйте еще раз jkshay.com/…
Серлок
2

Вы можете создать новый обработчик событий в ViewModel следующим образом.

public event EventHandler RequestClose;

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

Затем определите RelayCommand для 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();
    }

Затем в наборе файлов XAML

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

Установите DataContext в файле xaml.cs и подпишитесь на созданное нами событие.

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(); };
    }
}
Джотирмая Прусты
источник
Я использовал MVVM Light Messenger вместо события.
Хэмиш Ганн
1

Мой предложенный способ - объявить событие в ViewModel и использовать смесь InvokeMethodAction, как показано ниже.

Образец 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 Closeable интерфейс, как показано ниже, но не требует выполнения этого действия. ICloseable поможет в создании общей службы просмотра, поэтому, если вы создаете представление и ViewModel путем внедрения зависимостей, вы можете сделать следующее:

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Использование 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();
        }

А ниже - Xaml. Вы можете использовать этот xaml, даже если вы не реализуете интерфейс, ему понадобится только ваша модель представления для повышения 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

Вы можете использовать Messengerиз набора инструментов MVVMLight. в вашем ViewModelотправьте сообщение, подобное этому:
Messenger.Default.Send(new NotificationMessage("Close"));
затем в своем коде Windows InitializeComponentзарегистрируйтесь для этого сообщения следующим образом:

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

вы можете найти больше об инструментарии MVVMLight здесь: инструментарий MVVMLight на Codeplex

Обратите внимание, что в MVVM нет правила «никакого кода программной части», и вы можете регистрироваться для сообщений в представлении кода программной части.

Амир Овейси
источник
0

Это просто. Вы можете создать свой собственный класс ViewModel для входа в систему - LoginViewModel. Вы можете создать представление var dialog = new UserView (); внутри вашего LoginViewModel. И вы можете настроить Command LoginCommand на кнопку.

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

а также

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

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
Да, это тоже верное решение. Но если вы хотите придерживаться MVVM и разделения виртуальных машин и представлений, вы нарушите шаблон.
DHN
Привет @misak - пытаясь реализовать ваше решение (как и другие ответы), он выдает a Object reference not set to an instance of an object.для метода CloseLoginView. Есть предложения, как решить эту проблему?
WPFNoob
@WPFNoob - Я снова трачу это решение. Пример работает правильно. Вы хотите отправить полное решение Visual Studio по электронной почте?
misak
@WPFNoob - Я вижу проблему. Вы создаете экземпляр как var dialog = new UserView () ;. Clear ключевое слово var (локальный экземпляр) перезаписывает глобальный экземпляр в LoginViewModel
misak
0

Я сделал это довольно просто:

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

Я не вижу ничего плохого в выбранном вами ответе, я просто подумал, что это может быть более простой способ сделать это!

Стефенстентон
источник
8
Это требует, чтобы ваша ViewModel знала о вашем View и ссылалась на него.
AndrewS
@AndrewS, почему это плохо?
thestephenstanton
9
Чтобы следовать шаблону MVVM, ViewModel не должен знать о View.
MetalMikester
1
Чтобы расширить это, суть MVVM в том, чтобы сделать большую часть кода вашего графического интерфейса тестируемой. Представления имеют массу зависимостей, которые делают невозможным их модульное тестирование. ViewModels должны быть модульными, но если вы дадите им прямую зависимость от представления, они не будут.
ILMTitan
И чтобы еще больше расширить это, правильно написанный MVVM позволяет легко переносить решение на другую платформу. В частности, вы должны иметь возможность повторно использовать свои модели просмотра без каких-либо изменений. В этом случае, если вы переместите свое решение на Android, оно не сработает, потому что Android не имеет понятия окна. -1 для решения, нарушающего MVVM.
Spook
0

Вы можете рассматривать окно как службу (например, службу пользовательского интерфейса) и передавать себя модели просмотра через интерфейс , как таковой:

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

У этого решения есть большинство недостатков в передаче самого представления в модель просмотра без нарушения работы MVVM, потому что, хотя физически представление передается в модель просмотра, последний все еще не знает о первом, он видит только некоторые IMainWindowAccess. Так, например, если мы захотим перенести это решение на другую платформу, это будет лишь вопрос IMainWindowAccessправильной реализации , скажем, дляActivity .

Я публикую здесь решение, чтобы предложить другой подход, нежели события (хотя на самом деле он очень похож), потому что он кажется немного проще, чем события для реализации (присоединение / отсоединение и т. Д.), Но все же хорошо согласуется с шаблоном MVVM.

Призрак
источник
-1

Вы можете закрыть текущее окно, просто используя следующий код:

Application.Current.Windows[0].Close();
чандудаб
источник
6
Если у вас более одного окна, это может закрыть неправильное окно.
Саша
17
О Боже! вы зарезали MVVM
Хоссейн Шахдуст
-7

System.Environment.Exit (0); в представлении модель будет работать.

рджайн
источник
6
Нет, не будет. Он выйдет из приложения, но не закроет текущее окно.
Tilak
это решило мою проблему, потому что закрытие mainWindow (для меня) == выход из приложения. Все предложенные методы, кроме этого, имели сложные моменты, когда вызывались из разных потоков; но этому подходу все равно, кто звонит :) это все, что мне нужно!
Hamed
BuAHahahAHahahAha, прости, не смог устоять
L.Trabacchin