Как должна 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 строк событий, команд и поведения просто избегают метода одной строки в имя "узоры" и "чистота" немного смешно....

+221
02 февр. '09 в 0:17
источник поделиться
25 ответов

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

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

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

+299
25 июл. '10 в 14:06
источник

С моей точки зрения, вопрос довольно хорош, поскольку такой же подход будет использоваться не только для окна "Вход", но и для любого из них. Я прошел через множество предложений, и никто не подходит для меня. Пожалуйста, посмотрите мой вид, который был взят из статьи шаблона проектирования 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 Любые комментарии или предложения приветствуются.

+62
20 янв. '10 в 11:05
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

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

+16
20 июл. '09 в 13:31
источник

Есть много комментариев, аргументирующих плюсы и минусы 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.

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

+14
24 мар. '09 в 12:54
источник

Предполагая, что ваше диалоговое окно входа в систему - это первое созданное окно, попробуйте это внутри класса 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;
        }
    }
+7
28 дек. '09 в 20:09
источник

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

+6
28 апр. '09 в 21:08
источник

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

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

+5
06 окт. '11 в 8:14
источник

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

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 значительно усложнял понимание, чем если бы я не использовал его, и теперь стал анти-шаблоном.

+4
02 февр. '09 в 0:31
источник

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

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

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

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

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

+3
29 апр. '09 в 18:04
источник

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

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

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

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

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

+3
28 мая '09 в 18:08
источник

Хорошо, поэтому этот вопрос почти 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 и запустите ее, это очень просто, более подробный, чтобы заставить его работать, но преимущества больше, чем просто закрытие окна.

+3
13 июн. '15 в 23:17
источник

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

+2
23 февр. '11 в 22:00
источник
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();
  }
}
+1
30 авг. '16 в 7: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}" />
0
11 окт. '10 в 18:49
источник

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

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

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

Эрик

0
05 мар. '11 в 2:11
источник

Другим решением является создание свойства с помощью 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 по-прежнему является "единым тестируемым", а некоторые небольшие строки кода в коде не имеют никакого значения.

0
21 мар. '12 в 12:54
источник

Я реализовал решение 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?;
}

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

0
15 июл. '12 в 19:56
источник

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

та-да

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }
0
31 янв. '14 в 8:02
источник

Я закончил смешивать ответ Джо Уайта и некоторый код из ответ Адама Миллса, так как мне нужно было показать пользовательский элемент управления в программно создаваемом окне. Таким образом, 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_ );
    }
  }
}
0
27 мар. '14 в 16:13
источник

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

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
{
}
0
08 авг. '14 в 21:34
источник

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

Вы могли бы справиться с этим с помощью класса 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 должен иметь больше возможностей для настройки диалога, например. кнопок и команд, которые они должны выполнить. Есть другой способ сделать это, но его вне видимости:)

0
14 мар. '15 в 18:12
источник

Хотя это не отвечает на вопрос о том, как это сделать с помощью 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, как показано выше, и он работает для меня.

0
02 апр. '15 в 0:05
источник

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

  • Вывести 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;
        }
    }
    
0
30 мая '15 в 12:50
источник

Создайте 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.:)

0
08 февр. '16 в 10:39
источник
Application.Current.MainWindow.Close() 

Достаточно!

-10
21 июл. '11 в 9:52
источник

Посмотрите другие вопросы по меткам или Задайте вопрос