Как должна ViewModel закрыть форму?

Я пытаюсь изучить WPF и проблему MVVM, но попал в ловушку. Этот вопрос аналогичен, но не совсем такой как этот (обработка-dialogs-in-wpf-with-mvvm)...

У меня есть форма "Вход", написанная с использованием шаблона MVVM.

В этой форме есть ViewModel, который содержит имя пользователя и пароль, которые привязаны к представлению в XAML, используя обычные привязки данных. Он также имеет команду "Вход", которая привязана к кнопке "Войти" в форме, агане, используя обычную привязку данных.

Когда запускается команда "Вход", она вызывает функцию в ViewModel, которая отключается и отправляет данные по сети для входа в систему. Когда эта функция завершается, есть 2 действия:

  • Вход был неверным - мы просто показываем MessageBox, и все в порядке

  • Вход был действителен, нам нужно закрыть форму "Вход" и вернуть значение true в качестве DialogResult...

Проблема в том, что ViewModel ничего не знает о фактическом представлении, поэтому как он может закрыть представление и сказать ему, чтобы он возвращал конкретный DialogResult? Я мог бы вставить код в CodeBehind и/или передать View в ViewModel, но похоже, что он полностью уничтожит всю точку MVVM...


Update

В конце концов я просто нарушил "чистоту" шаблона MVVM и опубликовал событие "Публиковать" Closed и вывел метод Close. Тогда ViewModel просто вызовет view.Close. Вид известен только через интерфейс и подключался через контейнер IOC, поэтому никакая тестируемость или ремонтопригодность не теряются.

Кажется довольно глупым, что принятый ответ на -5 голосов! Хотя я хорошо знаю хорошие чувства, возникающие при решении проблемы, будучи "чистыми", конечно, я не единственный, кто думает, что 200 строк событий, команд и поведения просто избегают метода одной строки в имя "узоры" и "чистота" немного смешно....

Ответ 1

Я был вдохновлен ответом Тежуана, чтобы написать более простое прикрепленное свойство. Нет стилей, триггеров; вместо этого вы можете просто сделать это:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

Это почти так же чисто, как если бы команда WPF правильно поняла и сделала DialogResult свойством зависимости в первую очередь. Просто поместите свойство bool? DialogResult на свой ViewModel и реализуйте INotifyPropertyChanged и voilà, ваш ViewModel может закрыть окно (и установить его DialogResult), просто установив свойство. MVVM, как и должно быть.

Вот код для DialogCloser:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

Я также разместил этот в своем блоге.

Ответ 2

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

Каждый класс ViewModel должен наследоваться от WorkspaceViewModel, у которого есть RequestClose envent, и CloseCommand свойство типа ICommand. По умолчанию реализация свойства CloseCommand приведет к событию RequestClose.

И для того, чтобы закрыть окно, метод OnLoaded вашего окна должен быть переопределен:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

или OnStartup метод вашего приложения:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

Я предполагаю, что реализация свойства RequestClose и CloseCommand в WorkspaceViewModel довольно ясна, но я покажу им, что они будут последовательными:

public abstract class WorkspaceViewModel : ViewModelBase // There are nothing interest in ViewModelBase, it only implements INotifyPropertyChanged interface only
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose!=null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

И исходный код RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

P.S. Не обращайся со мной плохо за эти источники. Если бы у меня было это вчера, это спасло бы меня несколько часов...

P.P.S Любые комментарии или предложения приветствуются.

Ответ 3

Я использовал прикрепленные действия, чтобы закрыть окно. Привяжите свойство "signal" к вашему ViewModel к приложенному поведению (я фактически использую триггер) Когда он установлен в true, поведение закрывает окно.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

Ответ 4

Есть много комментариев, аргументирующих плюсы и минусы MVVM здесь. Для меня я согласен с Ниром; это вопрос использования шаблона соответствующим образом, и MVVM не всегда подходит. Люди, похоже, стали жертвовать всеми важными принципами разработки программного обеспечения JUST, чтобы он соответствовал MVVM.

Тем не менее, я думаю, что ваш случай может быть хорошим вариантом с небольшим количеством рефакторинга.

В большинстве случаев я сталкивался, WPF позволяет вам получить БЕЗ нескольких Window s. Возможно, вы можете попробовать использовать Frame и Page вместо Windows с помощью DialogResult s.

В вашем случае мое предложение будет иметь LoginFormViewModel обрабатывать LoginCommand, и если вход недействителен, установите свойство на LoginFormViewModel на соответствующее значение (false или некоторое значение перечисления, например UserAuthenticationStates.FailedAuthentication)), Вы бы сделали то же самое для успешного входа в систему (true или другого значения перечисления). Затем вы использовали DataTrigger, который отвечает на различные состояния аутентификации пользователя и может использовать простой Setter для изменения свойства Source для Frame.

Наличие вашего окна входа в систему a DialogResult Я думаю, что вы путаетесь; что DialogResult действительно является свойством вашего ViewModel. В моем, по общему признанию, ограниченном опыте работы с WPF, когда что-то не кажется правильным, обычно, потому что я думаю о том, как бы я сделал то же самое в WinForms.

Надеюсь, что это поможет.

Ответ 5

Предполагая, что ваше диалоговое окно входа в систему - это первое созданное окно, попробуйте это внутри класса LoginViewModel:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

Ответ 6

То, как я бы справился с этим, - добавить обработчик событий в мой ViewModel. Когда пользователь был успешно зарегистрирован, я должен запустить событие. В моем представлении я приложил бы к этому событию, и когда он выстрелил, я закрою окно.

Ответ 7

Это простое и чистое решение. Вы добавляете событие в ViewModel и инструктируете Окно закрываться при этом событии.

Подробнее см. в моем сообщении в блоге Закрыть окно из ViewModel.

Ответ 8

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

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

Затем я удалил весь этот код и просто попросил LoginFormViewModel вызвать метод Close на нем. Это оказалось намного лучше и легче следовать. ИМХО, точка шаблонов - дать людям более простой способ понять, что делает ваше приложение, и в этом случае MVVM значительно усложнял понимание, чем если бы я не использовал его, и теперь стал анти-шаблоном.

Ответ 9

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

В моем случае ViewModel, который создает экземпляр окна, которое будет отображаться (может вызвать его ViewModelMain), также знает о LoginFormViewModel (используя приведенную выше ситуацию в качестве примера).

Так что я сделал, чтобы создать свойство на LoginFormViewModel, которое было типа ICommand (назовем его CloseWindowCommand). Затем, прежде чем я вызову .ShowDialog() в окне, я установил свойство CloseWindowCommand в LoginFormViewModel в window.Close() метод экземпляра Window I. Затем внутри LoginFormViewModel все, что мне нужно сделать, это вызвать CloseWindowCommand.Execute(), чтобы закрыть окно.

Это немного обходное решение/взлом, я полагаю, но он работает хорошо, не разрушая шаблон MVVM.

Не стесняйтесь критиковать этот процесс столько, сколько хотите, я могу его принять!:)

Ответ 10

Вероятно, это очень поздно, но я столкнулся с той же проблемой, и нашел решение, которое работает для меня.

Я не могу понять, как создать приложение без диалогов (возможно, это просто блок разума). Поэтому я был в тупике с MVVM и показывал диалог. Поэтому я столкнулся с этой статьей CodeProject:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Это UserControl, который в основном позволяет окну находиться внутри визуального дерева другого окна (не допускается в xaml). Он также предоставляет логический DependencyProperty, называемый IsShowing.

Вы можете установить стиль, как правило, в resourcedictionary, который в основном отображает диалог всякий раз, когда свойство Content элемента управления!= null через триггеры:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

В представлении, в котором вы хотите отобразить диалог, просто выполните следующее:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

И в вашем ViewModel все, что вам нужно сделать, - установить свойство в значение (Примечание: класс ViewModel должен поддерживать INotifyPropertyChanged для представления, чтобы знать, что что-то произошло).

так:

DialogViewModel = new DisplayViewModel();

Чтобы сопоставить ViewModel с представлением, вы должны иметь что-то вроде этого в resourcedictionary:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

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

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

Затем вы можете обработать результат диалога с помощью обратного вызова.

Это может показаться немного сложным, но как только фундамент заложен, он довольно прост. Опять же, это моя реализация, я уверен, что есть и другие:)

Надеюсь, это поможет, это спасло меня.

Ответ 11

Хорошо, поэтому этот вопрос почти 6 лет, и я до сих пор не могу найти здесь то, что считаю правильным ответом, поэтому позвольте мне поделиться своими "2 центами"...

У меня на самом деле есть два способа сделать это, сначала один - простой... второй на правой, поэтому , если вы ищете правильный, просто пропустите # 1 и перейдите к # 2

1. Быстрый и легкий (но не полный)

Если у меня есть только небольшой проект, я иногда просто создаю CloseWindowAction в ViewModel:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

И кто бы ни входил в ящик View или в коде View, я просто установил метод, который Action вызовет:

(помните, что MVVM посвящен разделению View и ViewModel... поведение кода просмотра по-прежнему является View и до тех пор, пока существует надлежащее разделение, вы не нарушаете шаблон)

Если какой-либо ViewModel создает новое окно:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

Или, если вы хотите его в главном окне, просто поместите его под свой конструктор вида:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

когда вы хотите закрыть окно, просто вызовите действие на вашем ViewModel.


2. Правильный путь

Теперь правильный способ сделать это с помощью Призма (IMHO), и все это может быть найдено здесь.

Вы можете сделать Запрос на взаимодействие, заполнить его любыми данными, которые вам понадобятся в новом окне, обедать, закрыть и даже получить данные назад. Все это инкапсулировано и одобрено MVVM. Вы даже получили статус закрытия окна, например, если пользователь Canceled или Accepted (кнопка ОК) окно и данные обратно, если вам это нужно, Это немного сложнее и ответ №1, но он намного более полный и рекомендуемый шаблон от Microsoft.

Ссылка, которую я дал, содержит все фрагменты кода и примеры, поэтому я не буду помещать здесь какой-либо код, просто прочитайте статью о загрузке Prism Quick Start и запустите ее, это очень просто, более подробный, чтобы заставить его работать, но преимущества больше, чем просто закрытие окна.

Ответ 12

Вы можете заставить ViewModel выставить событие, которое регистрирует View. Затем, когда ViewModel принимает решение о закрытии представления, он запускает это событие, которое заставляет закрыть окно. Если вы хотите, чтобы какое-то конкретное значение результата передавалось обратно, тогда у вас будет свойство в ViewModel для этого.

Ответ 13

public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}

Ответ 14

Почему бы просто не передать окно в качестве параметра команды?

С#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />

Ответ 15

Чтобы добавить к массивному числу ответов, я хочу добавить следующее. Предполагая, что у вас есть ICommand на вашем ViewModel, и вы хотите, чтобы эта команда закрывала свое окно (или любое другое действие, если на то пошло), вы можете использовать что-то вроде следующего.

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

Это не идеально, и может быть трудно проверить (так как сложно издеваться/заглушить статику), но он чище (IMHO), чем другие решения.

Эрик

Ответ 16

Другим решением является создание свойства с помощью INotifyPropertyChanged в View Model, например DialogResult, а затем в Code Behind:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

Самый важный фрагмент _someViewModel_PropertyChanged. DialogResultPropertyName может быть некоторой открытой строкой const в SomeViewModel.

Я использую этот трюк, чтобы внести некоторые изменения в элементы управления View в случае, когда это трудно сделать в ViewModel. OnPropertyChanged в ViewModel вы можете делать все, что хотите в представлении. ViewModel по-прежнему является "единым тестируемым", а некоторые небольшие строки кода в коде не имеют никакого значения.

Ответ 17

Я реализовал решение Joe White, но столкнулся с проблемами со случайными "DialogResult" можно установить только после создания окна и отображения в виде диалогового окна.

Я сохранял ViewModel вокруг после закрытия представления, и иногда я позже открывал новый вид с использованием той же виртуальной машины. Похоже, что закрытие нового представления перед тем, как старый View был собран с помощью мусора, вызвал DialogResultChanged, пытающийся установить свойство DialogResult в закрытом окне, что вызвало ошибку.

Моим решением было изменить DialogResultChanged, чтобы проверить свойство IsLoaded:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

После этого изменения любые вложения в закрытые диалоги игнорируются.

Ответ 18

Если вам нужно закрыть окно, просто поместите это в режим просмотра:

та-да

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

Ответ 19

Я закончил смешивать ответ Джо Уайта и некоторый код из ответ Адама Миллса, так как мне нужно было показать пользовательский элемент управления в программно создаваемом окне. Таким образом, DialogCloser не обязательно должен находиться в окне, он может находиться в самом пользовательском элементе управления

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

И DialogCloser найдет окно пользовательского элемента управления, если оно не было прикреплено к самому окну.

namespace Wpf
{
  public static class DialogCloser
  {
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      var window = d.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}

Ответ 20

Я бы пошел следующим образом:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

Ответ 21

Я прочитал все ответы, но должен сказать, что большинство из них просто недостаточно или даже хуже.

Вы могли бы справиться с этим с помощью класса DialogService, ответственным за отображение диалогового окна и возврата результата диалога. Я создал образец проекта, демонстрирующий его реализацию и использование.

здесь наиболее важные части:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

Разве это не проще? более простенький, более читаемый и, что не менее важно, легче отлаживать, чем EventAggregator или другие подобные решения?

как вы можете видеть. На мой взгляд, модели я использовал первый подход ViewModel, описанный в моем сообщении здесь: Лучшая практика для вызова View из ViewModel в WPF

Конечно, в реальном мире DialogService.ShowDialog должен иметь больше возможностей для настройки диалога, например. кнопок и команд, которые они должны выполнить. Есть другой способ сделать это, но его вне видимости:)

Ответ 22

Хотя это не отвечает на вопрос о том, как это сделать с помощью viewmodel, это показывает, как это сделать, используя только XAML + blend SDK.

Я решил загрузить и использовать два файла из Blend SDK, оба из которых можно как пакет от Microsoft через NuGet. Файлы:

System.Windows.Interactivity.dll и Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll дает вам хорошие возможности, такие как возможность устанавливать свойство или вызывать метод на вашем режиме просмотра или другой цели, а также другие виджеты внутри.

Некоторые XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Обратите внимание, что если вы просто собираетесь использовать поведение OK/Cancel, вы можете уйти с использованием свойств IsDefault и IsCancel, пока отображается окно w/Window.ShowDialog().
У меня лично были проблемы с кнопкой, для которой свойство IsDefault установлено в true, но оно было скрыто при загрузке страницы. Похоже, что он не играл хорошо после того, как он был показан, поэтому я просто устанавливаю свойство Window.DialogResult, как показано выше, и он работает для меня.

Ответ 23

Вот простое решение без ошибок (с исходным кодом), оно работает для меня.

  • Вывести ViewModel из INotifyPropertyChanged

  • Создать видимое свойство CloseDialog в ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

    }

  • Прикрепите обработчик в представлении для этого изменения свойства

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  • Теперь вы почти закончили. В обработчике события DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    

Ответ 24

Создайте Dependency Property в View/any UserControl (или Window, который вы хотите закрыть). Как ниже:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

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

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

И привяжите его к свойству ViewModel:

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Свойство В VeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Теперь запустите операцию закрытия, изменив значение CloseWindow в ViewModel.:)

Ответ 25

Application.Current.MainWindow.Close() 

Достаточно!