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

247

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

У меня есть форма «Логин», написанная с использованием шаблона MVVM.

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

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

  1. Логин был неверный - мы просто показываем MessageBox и все в порядке

  2. Логин был действителен, нам нужно закрыть форму входа в систему и вернуть ее как DialogResult...

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


Обновить

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

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

Орион Эдвардс
источник
2
Я не понизил принятый ответ, но я предполагаю, что причина отрицательных ответов заключается в том, что в целом он бесполезен, даже если он может работать в одном случае. Вы сами сказали это в другом комментарии: «Хотя форма входа в систему представляет собой диалоговое окно« два поля », у меня есть много других, которые намного более сложны (и, следовательно, требуют MVVM), но все же должны быть закрыты ...»
Джо Белый
1
Я понимаю вашу точку зрения, но лично я думаю, что даже для общего случая простой Closeметод все еще является лучшим решением. Все остальное в других более сложных диалогах - это MVVM и привязка к данным, но было бы глупо реализовывать здесь огромные «решения» вместо простого метода ...
Орион Эдвардс,
2
Вы можете проверить следующую ссылку для результата диалога asimsajjad.blogspot.com/2010/10/… , который вернет результат диалога и закроет представление из модели представления
Asim
3
Пожалуйста, измените принятый ответ на этот вопрос. Есть много хороших решений, которые намного лучше, чем те, кто ставит под сомнение использование MVVM для этой функции. Это не ответ, это избегание.
ScottCher
2
@OrionEdwards Я думаю, вы были правы, что нарушили схему здесь. Основная цель шаблона проектирования - ускорить циклы разработки, повысить удобство сопровождения и упростить ваш код, заставляя всю команду следовать одним и тем же правилам. Это не достигается путем добавления зависимости от внешних Librarys и реализовать сотни строк кода , чтобы выполнить задачу, совершенно не обращая внимания , что есть гораздо более простое решение, только потому , что один, чтобы упрямы , чтобы принести в жертву «чистоту» картины. Просто убедитесь , чтобы документ , что your've сделано и ПОЦЕЛУЙ код ( K EEP я т s Хорт и s реали).
M463

Ответы:

324

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

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

Это почти так же чисто, как если бы команда WPF все правильно поняла и сделала DialogResult свойством зависимости в первую очередь. Просто поместите bool? DialogResultсвойство в вашу ViewModel и реализуйте INotifyPropertyChanged, и вуаля, ваша 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);
        }
    }
}

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

Джо Уайт
источник
3
Этот ответ мне нравится больше всего! Хорошая работа по написанию этой прикрепленной собственности.
Хорхе Варгас
2
Хороший вариант, но в этом решении есть небольшая ошибка. Если модель представления для диалогового окна является одноэлементной, значение DialogResult переносится на следующее использование диалогового окна. Это означает, что он будет немедленно отменен или принят, прежде чем показывать себя, чтобы диалоговое окно не отображалось во второй раз.
Ушел кодирование
13
@HiTech Magic, звучит так, будто ошибка заключается в использовании одноэлементной ViewModel. (ухмыляясь) Серьезно, с какой стати вы захотите модель Viewton синглтона? Неправильно хранить изменяемое состояние в глобальных переменных. Тестирование превращает в кошмар, а тестирование - одна из причин, по которой вы бы изначально использовали MVVM.
Джо Уайт
3
Разве MVVM не имеет смысла не привязывать вашу логику к какому-либо конкретному интерфейсу? В этом случае, бул? наверняка не может использоваться другим пользовательским интерфейсом, таким как WinForm, а DialogCloser специфичен для WPF. Так как это вписывается в решение? Кроме того, зачем писать код 2x-10x, просто чтобы закрыть окно через привязку?
Дэвид Андерсон
2
@DavidAnderson, я бы в любом случае не попробовал MVVM с WinForms; его поддержка привязки данных слишком слаба, и MVVM опирается на хорошо продуманную систему привязки. И это далеко не 2x-10x код. Вы пишете этот код один раз , а не один раз для каждого окна. После этого это однострочная привязка плюс свойство уведомления, использующее тот же механизм, который вы уже использовали для всего остального в вашем представлении (так, например, вам не нужно вводить дополнительный интерфейс представления только для обработки закрытия окно). Вы можете сделать другие компромиссы, но для меня это, как правило, хорошая сделка.
Джо Уайт
64

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

Каждый класс ViewModel должен наследовать от того, WorkspaceViewModelчто имеет RequestCloseсобытие и 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's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    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
}

PS Не относитесь ко мне плохо из-за этих источников! Если бы они были у меня вчера, это спасло бы меня несколько часов ...

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

Budda
источник
2
Хм, тот факт, что вы подключили обработчик событий customer.RequestCloseв коде вашего XAML-файла, не нарушает ли он шаблон MVVM? Вы также можете привязать Clickобработчик событий к кнопке закрытия в первую очередь, увидев, что вы все равно коснулись кода и сделали a this.Close()! Правильно?
GONeale
1
У меня не так много проблем с подходом к событию, но мне не нравится слово RequestClose, потому что для меня оно все еще подразумевает много знаний о реализации View. Я предпочитаю выставлять свойства, такие как IsCancelled, которые имеют тенденцию быть более значимыми с учетом контекста и меньше подразумевать, что представление должно делать в ответ.
jpierson
18

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

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

Адам Миллс
источник
Пока это единственный ответ, который не требует никакого кода в окне (и фактически закрывает модальное окно, вместо того, чтобы предлагать другой подход). Жаль, что требуется так много сложности, со стилем и триггером и всем этим дерьмом - кажется, что это действительно должно быть выполнимо с присоединенным поведением в одну строку.
Джо Уайт
4
Теперь это выполнимо с подключенным поведением в одну строку. Смотрите мой ответ: stackoverflow.com/questions/501886/…
Джо Уайт
15

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

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

В большинстве случаев, с которыми я сталкивался, WPF позволяет вам обходиться без нескольких Windowс. Может быть, вы можете попробовать использовать Frames и Pages вместо Windows с DialogResults.

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

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

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

EightyOne Unite
источник
10

Предполагая, что ваш диалог входа в систему является первым окном, которое создается, попробуйте это внутри вашего класса 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;
        }
    }
Джим уоллес
источник
У мужчин это просто и прекрасно работает. В настоящее время я использую этот подход.
Erre Efe
Работает только для ОСНОВНОГО окна. Так что не используйте его для любых других окон.
Алексей
7

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

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

XAML:

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

ViewModel:

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

Примечание. В этом примере используется Prism DelegateCommand(см. Prism: Commanding ), но ICommandдля этого можно использовать любую реализацию.

Вы можете использовать поведения из этого официального пакета.

Шимми Вайцхандлер
источник
2
+1, но вы должны предоставить более подробную информацию в самом ответе, например, что это решение требует ссылки на сборку интерактивности Expression Blend.
заниматься серфингом
6

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


источник
2
Я тоже так обычно делаю. Даже если это кажется немного грязным, учитывая все эти новомодные wpf-командные вещи.
Botz3000
4

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

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

Орион Эдвардс
источник
3

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

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

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

Полагаю, это что-то вроде обходного пути, но оно работает хорошо, не нарушая шаблон MVVM.

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


источник
Я не уверен, что полностью это понял, но разве это не означает, что ваше MainWindow должно быть создано перед вашим LoginWindow? Этого я бы хотел избежать, если это возможно
Орион Эдвардс,
3

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

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

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

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

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

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

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

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

(помните, что MVVM - это разделение View и ViewModel ... Код View здесь остается 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
                           };
}

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


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

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

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

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

mFeinstein
источник
Хороший способ, но разрешение и назначение ViewModels не всегда могут быть такими простыми. Что если одна и та же viewmodel является DataContext для многих Windows?
Kylo Ren
Тогда, я думаю, вам придется закрыть все окна одновременно, помните, что действие может инициировать сразу несколько делегатов, просто используйте +=для добавления делегата и вызова действия, оно запустит их все ... Или вы будете Я должен создать специальную логику для вашей виртуальной машины, чтобы она знала, какое окно закрывать (возможно, иметь набор закрывающих действий) .... Но я думаю, что несколько представлений, привязанных к одной виртуальной машине, не лучшая практика, это лучше иметь один экземпляр View и один экземпляр виртуальной машины, связанные друг с другом и, возможно, родительскую виртуальную машину, которая управляет всеми дочерними виртуальными машинами, которые связаны со всеми представлениями.
mFeinstein
3
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();
  }
}
Амир Туиту
источник
2

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

Абдулла Аль-Кавасмех
источник
Я согласен с этим - простота ценна. Я должен подумать о том, что произойдет, когда следующий младший разработчик будет нанят для управления этим проектом. Я думаю, у него будет гораздо больше шансов сделать все правильно, как вы описываете. Разве вы не думаете, что собираетесь сохранить этот код навсегда самостоятельно? +1
декан
2

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

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

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

Erick

Эрик Т
источник
Я стал очень счастлив, когда увидел твой простой ответ! но это тоже не работает! Мне нужно открыть и закрыть с Visual Basic. Знаете ли вы эквивалентность (windows [i] .DataContext == this) в VB?
Ehsan
Наконец-то я понял! :) Спасибо. Если windows (i) .DataContext это я
Эхсан
Вы знаете такой же простой способ открыть окно? Мне нужно отправлять и получать некоторые данные также в дочерней viewmodel и наоборот.
Ehsan
1

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

Я держал ViewModel после закрытия View и иногда позже открывал новый View, используя ту же виртуальную машину. Похоже, что закрытие нового View до того, как старый 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?;
}

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

Джим Хансен
источник
Спасибо, сэр. У меня была такая же проблема
DJ Burb
1

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

Поведение - самый удобный способ здесь.

  • С одной стороны, он может быть привязан к заданной модели представления (которая может сигнализировать «закрыть форму!»)

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

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

Юрий Сккатула
источник
0

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

C #:

 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}" />
chrislarson
источник
Я не думаю, что это хорошая идея, чтобы ограничить виртуальную машину типом окна.
Шимми Вайцхандлер
2
Я не думаю, что это хорошая идея ограничивать виртуальную машину Windowтипом, который не совсем «чистый» MVVM. Посмотрите этот ответ, где виртуальная машина не ограничена Windowобъектом.
Шимми Вайцхандлер
таким образом, зависимость ставится на кнопку, которая, конечно, не всегда может быть ситуацией. Также передача типа пользовательского интерфейса во ViewModel является плохой практикой.
Kylo Ren
0

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

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

sliwinski.lukas
источник
0

Я бы пошел по этому пути:

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
{
}
romanoza
источник
0

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

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

Liero
источник
0

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

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

Wes
источник
0

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

  1. Получите вашу ViewModel из INotifyPropertyChanged

  2. Создайте наблюдаемое свойство 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));
        }
    }

    }

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

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

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
Anil8753
источник
0

Создайте Dependency Propertyв своем View/ любом 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. :)

Кило Рен
источник
-2

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

та-да

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }
Кэтэлин Рэдой
источник
ViewModel никоим образом не должен содержать UIElement , потому что это может создать ошибку
WiiMaxx
Что если DataContext наследуется несколькими окнами?
Кайло Рен
Та-да, это совершенно не MVVM.
Александру Дику
-10
Application.Current.MainWindow.Close() 

Достаточно!

Алексей
источник
3
-1 Только в том случае, если окно, которое вы хотите закрыть, является основным окном ... Очень маловероятно предположение для диалога входа в систему ...
surfen