Закрити вікно з ViewModel


95

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

До сих пір я створив метод , який перевіряє , чи набрав користувач в правильних облікових даних для usernameі passwordв 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?

Заздалегідь спасибі.


Відповіді:


149

Ви можете передати вікно у ваш 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 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

вибачте, я не розумію, ви могли б трохи пояснити своє питання?
Джоел

14
Якщо вам не подобається називати вікна, ви також можете прив'язати параметр таким чином:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
Передача Windowоб’єкта в модель подання порушує шаблон IMVO MVVM, оскільки він змушує вашу віртуальну машину знати, в чому її переглядають. Що робити, якщо представлення було закріпленою вкладкою в інтерфейсі MDI? Правильний спосіб зробити це IMHO - передати якийсь інтерфейс IUIHost, який реалізує метод Close, і мати будь-який вигляд, який ви хочете показати, щоб його vm реалізував.
Кроно

2
Це нормально, оскільки інтерфейс приховує конкретну реалізацію ViewModel. ViewModel не знає нічого про представлення, крім того, що він реалізує метод Close (). Таким чином, подання може бути будь-яким: вікно WPF, форма WinForms, програма UWP або навіть сітка WPF. Він відокремлює вид від моделі перегляду.
Джоел

34

Залишаючись MVVM, я думаю, що використання або поведінки з 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

Вибачте, я повинен був бути більш явним - я мав би властивість у моїй viewmodel (у наведеному вище прикладі, що називається CloseTrigger), яка б отримувала значення true, що в підсумку спричиняло б поведінку. Я
оновив

Це спрацювало, але мені довелося змінити спосіб завантаження мого додатка. Оскільки я використовував Window для мого основного додатка, він також вбив усі дочірні вікна. Дякую.
WPFNoob

Призначення властивості true для виконання дії - смердючий IMO.
Джош Ное

33

Зазвичай я розміщую подію на моделі подання, коли мені потрібно це зробити, а потім підключаю її до, 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(); 

11
Анонімний делегат швидко пишеться, але варто зазначити, що подію не можна зареєструвати (що може бути проблемою). Зазвичай краще з повноцінним обробником подій.
Mathieu Guindon

Мені це подобається найбільше. У будь-якому випадку важко уникнути спеціальної обробки під час відображення вікна (наприклад Loaded, ContentRenderedдля головного вікна, діалогових служб тощо), додавання до нього трохи через подію ViewModel є досить чистим, як для мене. 3 рядки коду насправді не потребують жодного рішення для повторного використання. PS: чистий MVVM у будь-якому випадку для ботаніків.
Сінатр

Хлопчик, це мені допомогло.
Димитрій

Це набагато краще, ніж прийнята відповідь, оскільки вона не порушує шаблон MVVM.
Spook

22

може бути пізно, але ось моя відповідь

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

1
чому це не справжня відповідь?
user2529011

1
@ user2529011 дехто, принаймні, скаржиться на те, що модель перегляду не повинна знати нічого про Application.Current.Windows
грубо підтримує Моніку

-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виглядає наступним чином . Це не те саме, що я розмістив учора, але IMHO це не повинно мати ніякого ефекту.

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
}

Ні, це не безглузде питання. Просто помістіть декларацію про прив'язку в <Window />елемент, як я проілюстрував у своєму фрагменті. Мені було просто лінь писати решту (декларації простору імен тощо), що зазвичай там також оголошується.
DHN

1
Будь ласка, зверніться до мого редагування. Я розмістив виробничий код, тому впевнений, що він працює. Це виглядає дещо інакше, але код, який я опублікував учора, також повинен працювати.
DHN

Дякуємо за роз'яснення. Виявилося, що я називав неправильний простір імен: S. Мені просто потрібно створити datatriggerі призначити його кнопці, щоб вона працювала? Знову вибачте за нубі-питання.
WPFNoob

Дякую - ну, я просто усвідомлюю, що задаю занадто багато питань, які можуть здатися безглуздими та дурними, і марно витрачаю час людей! Але повертаючись до мого запитання. Після всього, що ви згадали, як мені закрити вікно? Використовувати DataTrigger¬ and setting value true`?
WPFNoob

1
Ну це та частина, яку я залишаю тобі. ; О) Подумайте про 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, щоб встановити діалоговий результат при закритті 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 (); }}} назвіть це як: 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?
Роман О

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

3

Я знаю, що це стара публікація, мабуть, ніхто б не прокрутив так далеко, я знаю, що ні. Тож, після кількох годин випробувань різних речей, я знайшов цей блог і чувак його вбив. Найпростіший спосіб зробити це, спробував, і це працює як шарм.

Блог

У 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 ти впевнений? Я відкрив його звичайно, спробуйте ще раз 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.
Hamish Gunn,

1

Моїм запропонованим способом є Оголосити подію у 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 допоможе у створенні загальної служби перегляду, тому якщо ви побудуєте view і 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, навіть якщо ви не реалізуєте інтерфейс, йому знадобиться лише Ваша модель перегляду, щоб підняти CloseRanted.

<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 - намагаючись реалізувати своє рішення (як і інші відповіді), він Object reference not set to an instance of an object.видає a для методу CloseLoginView. Будь-які пропозиції щодо вирішення цього питання?
WPFNoob

@WPFNoob - Я знову відкладаю це рішення. Приклад працює правильно. Хочете надіслати повне рішення візуальної студії електронною поштою?
misak

@WPFNoob - Я бачу проблему. Ви створюєте примірник як var dialog = new UserView () ;. Очистити ключове слово 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

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

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

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

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


-1

Ви можете закрити поточне вікно, просто використовуючи такий код:

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

6
Якщо у вас більше одного вікна, це може закрити неправильне вікно.
Саша

17
О, Боже! ви вбили MVVM
Хоссейн Шахдуст

-7

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


6
Ні Це не буде. Він вийде з програми, а не закриє поточне вікно.
Тілак

це вирішило мою проблему, тому що закриття mainWindow (для мене) == вихід із програми. Усі запропоновані методи, крім цього, мали хитрі моменти, коли їх викликали з різних потоків; але такий підхід насправді не хвилює, хто є ниткою, що телефонує :) це було все, що мені потрібно!
Хамед

BuAHahahAHahahAha вибачте, не втримався
L.Trabacchin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.