Как ViewModel должен закрыть форму? - PullRequest
235 голосов
/ 02 февраля 2009

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

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

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

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

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

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

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


Обновление

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

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

Ответы [ 26 ]

3 голосов
/ 30 августа 2016
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 голосов
/ 28 мая 2009

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 24 февраля 2011

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

0 голосов
/ 08 февраля 2016

Создайте 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 голосов
/ 30 мая 2015

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

  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 голосов
/ 02 апреля 2015

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

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

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

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

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 голосов
/ 11 октября 2010

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

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 голосов
/ 31 января 2014

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

та-да

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...