it-swarm.com.ru

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

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

До сих пор я создал метод, который проверяет, правильно ли пользователь ввел правильные учетные данные для username и password в textbox на экране входа в систему, binding two 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 я сделал что-то вроде следующего: сначала загружается страница входа в систему, а затем true, загружается само приложение.

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?

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

66
WPFNoob

Вы можете передать окно вашей 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, но принцип применяется к каждому приложению wpf.

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

MVVM соответствует решению (Бывшая EDIT2)

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

Передача объекта Window в модель представления нарушает шаблон MVVM ИМХО, потому что это заставляет ваш виртуальный компьютер знать, на что он смотрит.

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

Интерфейс:

public interface IClosable
{
    void Close();
}

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

ViewModel

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

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

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

Вы должны ссылаться и реализовывать интерфейс IClosable по вашему мнению

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

public partial class MainWindow : Window, IClosable
{
    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();
     }
 }
117
Joel

Оставаясь в 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("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 = новый MainWindowViewModel ())

26
Steve Van Treeck

Я обычно помещаю событие в модель представления, когда мне нужно сделать это, а затем подключаю его к 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(); 
24
ChrisO

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

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 с VM и установить его значение свойства. 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
}
12
DHN

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

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);
8
RassK

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

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

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

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
Anon

Это способ, которым я сделал это довольно просто:

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

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

3
THE Stephen Stanton

Вот простой пример использования 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();
    }
2
Hamish Gunn

Вы можете создать новый обработчик событий в 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(); };
    }
}
1
Jyotirmaya Prusty

Вы можете использовать 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 нет правила «вообще нет выделенного кода», и вы можете выполнить регистрацию сообщений в выделенном фрагменте представления.

1
Amir Oveisi

Мой предложенный способ - объявить событие во ViewModel и использовать blend 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 Закрываемый интерфейс, как показано ниже, но не требует выполнения этого действия. 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
RAJ

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

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

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

Блог

0
Serlok

Это просто .. Вы можете создать свой собственный класс ViewModel для Login - LoginViewModel ... Вы можете создать представление var dialog = new UserView (); внутри вашего LoginViewModel. И вы можете настроить команду 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;
    }
}
0
misak