it-swarm.com.ru

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

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

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

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

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

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

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

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


Обновление

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

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

231
Orion Edwards

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

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

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

308
Joe White

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

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

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

65
Budda

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

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

18
Adam Mills

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

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

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

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

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

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

15
Stimul8d

Предполагая, что ваш диалог входа в систему является первым окном, которое создается, попробуйте это внутри вашего класса 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;
        }
    }
9
Jim Wallace

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

6
Billy Jacobs

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

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

5
Shimmy

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

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

4
Orion Edwards

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

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

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

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

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

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

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

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

(помните, что 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
                           };
}

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


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

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

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

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

3
mFeinstein
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();
  }
}
3
Amir Twito

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

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

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

Полагаю, это немного обходной путь/хак, но он работает хорошо, не нарушая паттерн MVVM.

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

3
Chris Walker

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

2
Abdulla Al-Qawasmeh

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

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

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

Erick

0
Erick T

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

0
sliwinski.lukas

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

Вы можете прекрасно справиться с этим с помощью класса 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
Liero

Создайте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}"

Свойство InVeiwModel:

private bool closeWindow;

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

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

0
Kylo Ren

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

0
Wes

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

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

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
romanoza

Поведение является наиболее удобным способом здесь. 

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

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

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

0
Yury Schkatula

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

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}" />
0
chrislarson

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

Я держал ViewModel после закрытия 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?;
}

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

0
Jim Hansen

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

  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;
        }
    }
    
0
Anil8753