ENPT

Tratando eventos de diálogo de janelas WPF da forma elegante

Aqui estão duas formas elegantes que você pode usar para tratar diálogos WPF a partir do ViewModel. Vou demonstrar os dois exemplos fechando um diálogo de janela WPF a partir do ViewModel, dependendo de alguma lógica.

Usando eventos

A primeira solução é usar um evento que notifica o diálogo da janela para fechar. Considere o seguinte exemplo:

public class DialogViewModel
{
    public event EventHandler Saved;

    public DialogViewModel()
    {
        SaveCommand = new DelegateCommand(OnSave);
    }

    public ICommand SaveCommand { get; }

    public void Save()
    {
        //
        // SOME LOGIC TO SAVE GOES HERE
        //
        Saved?.Invoke(this, EventArgs.Empty);
    }
}

A janela de diálogo pode escutar esse evento e se fechar de acordo.

public class SaveDialog : Window
{
    public SaveDialog(DialogViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
        viewModel.Saved += (s, e) => Close();
    }
}

Usando a propriedade DialogResult

Outra forma de fechar um diálogo em WPF é definindo a propriedade DialogResult. Toda vez que definimos o DialogResult como true ou false, o WPF fecha o diálogo automaticamente para nós. Podemos usar isso a nosso favor vinculando essa propriedade à propriedade Result do nosso ViewModel.

public class DialogViewModel : BaseViewModel
{
    private bool? _result;

    public DialogViewModel()
    {
        SaveCommand = new DelegateCommand(OnSave);
    }

    public bool? Result
    {
      get => _result;
      set
      {
          if (_result != value)
          {
              _result = value;
              RaisePropertyChanged();
          }
      }
    }

    public ICommand SaveCommand { get; }

    public void OnSave(object parameter)
    {
        //
        // SOME SAVE LOGIC GOES HERE
        //

        Result = true;
    }
}

Em nosso diálogo, Poderíamos simplesmente vincular a propriedade Result ao DialogResult. Isso fecharia o diálogo automaticamente quando definíssemos o resultado como true ou false. Assim:

<Window
  ...
  DialogResult={Binding Result} />

Infelizmente, NÃO PODEMOS. A propriedade DialogResult não é uma Dependency Property e não podemos vincular nada a ela. Para contornar isso, podemos usar uma Attached Property.

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

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

    public static object GetDialogResult(Window target)
    {
        bool? bReturn = null;

        try
        {
            if (target != null)
            {
                bReturn =
                  target.GetValue(DialogResultProperty) as bool?;
            }
        }
        catch { }

        return bReturn;
    }

    public static void SetDialogResult(Window target, bool? value)
    {
        if (target != null)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

E agora:

<Window
  ...
  local:DialogExtension.DialogResult={Binding Result} />

Conclusão

Provavelmente existem muitas formas de fazer isso; essas duas são as que considero mais elegantes.