Открытие второго окна в паттерне MVVM

195
10 ноября 2017, 06:42

Как ПРАВИЛЬНО реализовать открытие второго окна из кнопки на первом окне с использованием паттерна MVVM? На сети конкретного примера я не нашел, а во многих проектах открытие окна идет нарушая принципы данного паттерна...

Answer 1

Если разберетесь в коде, то попробуйте DialogService. Простите, нет много времени подробно ответить. На тот случай, если разберетесь и ответ актуален, то спасибо говорить человеку по имени Вадим.

Пользоваться можно так:

                //просто какие-то входные данные
                var settingsWindowRequest = new SettingsLocalEvent() {};
                //вид диалога, это UserControl
                var view = new ChangeSettingsView(settingsWindowRequest, this);
                //вызов диалога
                _dialogService.ShowDialog<NewSettingsLocalEvent>(
                    view, 
                    newSettings => SetNewSettings(newSettings), 
                    throwIfNullResult: false,
                    //imageUri - это моя отсебятина от лени, можно удалять из исходников
                    imageUri: new Uri("pack://application:,,,/Red.ico", UriKind.RelativeOrAbsolute)
                    );

<UserControl x:Class="Xyz.View.ChangeSettingsView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:MS4DMonitor.View"
         xmlns:lctrl="clr-namespace:MS4DMonitor.LocalControls"
         xmlns:mvvm="Sborka_S_Ishodnikami_I_Sootvetstvuyushij_Namespace"
         mc:Ignorable="d" 
         d:DesignHeight="390" d:DesignWidth="530">
    <mvvm:DialogService.ContainerStyle>
        <Style TargetType="mvvm:DialogContainer" BasedOn="{StaticResource OkCancelDialogStyle}">
            <Setter Property="Title" Value="{Binding Title}" />
            <Setter Property="Width" Value="530"/>
            <Setter Property="Height" Value="500"/>
            <Setter Property="Title" Value="Настройки"/>
        </Style>
    </mvvm:DialogService.ContainerStyle>
</UserControl>

В приведенном выше коде вбейте правильный Sborka_S_Ishodnikami_I_Sootvetstvuyushij_Namespace. Вот пример стиля OkCancelDialogStyle:

        <Style x:Key="OkCancelDialogStyle" TargetType="mvvm:DialogContainer">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="mvvm:DialogContainer">
                        <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}">
                            <controls:BusyIndicator IsBusy="{Binding IsBusy}">
                                <DockPanel>
                                    <Border Background="WhiteSmoke" MinHeight="50" DockPanel.Dock="Bottom"
                                        BorderBrush="LightGray"
                                        BorderThickness="0,1,0,0"
                                        >
                                        <StackPanel 
                                        HorizontalAlignment="Right" 
                                        VerticalAlignment="Center"
                                        Orientation="Horizontal"
                                        Height="24"
                                        Margin="0,0,10,0"
                                        >
                                            <Button Content="OK" IsDefault="True" Command="{Binding OkCommand}" MinWidth="60" Margin="0,0,4,0" />
                                            <Button Content="Отмена" Command="{Binding CancelCommand}" IsCancel="True" MinWidth="60" />
                                        </StackPanel>
                                    </Border>
                                    <ContentPresenter />
                                </DockPanel>
                            </controls:BusyIndicator>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

На всякий случай и это:

public partial class ChangeSettingsView : UserControl
{
    public ChangeSettingsView(SettingsLocalEvent request, object viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }

}

Так же во ViewModel можно реализрвать интерфейс IDialogCloseable, тогда будет event:

public event DialogCloseEvent<TResponse> DialogClose = delegate { };

Можно во ViewModel прописать такой метод:

    protected void RaiseDialogClose(object sender, bool? dialogResult, TResponse result)
    {
        DialogClose(sender, new DialogCloseEventArgs<TResponse>(dialogResult, result));
    }

Тогда, например, в какой-то команде (ICommand) по клику кнопки OK можно будет вызвать этот event:

    private void ExecuteOk(object input)
    {
        RaiseDialogClose(this, true, new MyResponse
            {
            }
            );
    }

Кстати, в самом начале кода в данном ответе, где написано "пользоваться можно так", там при вызове диалога есть такой параметр: newSettings => SetNewSettings(newSettings). Если не ошибаюсь, то этот Func как раз и вызывается при нажатии OK в диалоге. Здесь SetNewSettings это просто какой-то метод, суть в том, что он вызовется при нажатии ОК (опять же, если не ошибаюсь).

исходники

[StyleTypedProperty(Property="ContainerStyleProperty", StyleTargetType=typeof(DialogContainer))]
public class DialogService : IDialogService
{
    public static Style GetContainerStyle(DependencyObject obj)
    {
        return (Style)obj.GetValue(ContainerStyleProperty);
    }
    public static void SetContainerStyle(DependencyObject obj, Style value)
    {
        obj.SetValue(ContainerStyleProperty, value);
    }
    public static readonly DependencyProperty ContainerStyleProperty =
        DependencyProperty.RegisterAttached(
            "ContainerStyle", 
            typeof(Style), 
            typeof(DialogService),
            new PropertyMetadata(null)
            {
                CoerceValueCallback = ContainerStylePropertyCoerce
            }
        );
    private static object ContainerStylePropertyCoerce(DependencyObject d, object baseValue)
    {
        var style = baseValue as Style;
        if (style != null)
        {
            if (style.TargetType != typeof(DialogContainer))
            {
                return DependencyProperty.UnsetValue;
            }
            return style;
        }
        return null;
    }
    public void ShowDialog(FrameworkElement view)
    {
        this.ShowDialog<object>(view, (DialogCallback<object>)null);
    }
    public void ShowDialog<T>(FrameworkElement view, Action<T> onSuccess, bool throwIfNullResult = false, Uri imageUri = null)
    {
        this.ShowDialog<T>(
            view,
            (dr, result) =>
            {
                if (dr == true && onSuccess != null) onSuccess(result);
            },
            throwIfNullResult,
            imageUri
        );
    }
    public void ShowDialog<T>(FrameworkElement view, DialogCallback<T> callback, bool throwIfNullResult = false, Uri imageUri = null)
    {
        var container = new DialogContainer(view);
        if (imageUri != null)
            container.Icon = BitmapFrame.Create(imageUri);
        var dialogResult = container.ShowDialog();
        if (callback != null)
        {
            T result = default(T);
            if (dialogResult == true)
            {
                if (throwIfNullResult && container.DialogContainerResult == null)
                {
                    throw new NullReferenceException("DialogContainerResult");
                }
                if (container.DialogContainerResult != null)
                {
                    if (container.DialogContainerResult is T)
                    {
                        result = (T)container.DialogContainerResult;
                    }
                }
            }
            callback(dialogResult, result);
        }
    }
    public MessageBoxResult ShowMessage(
        string message, 
        string caption = null, 
        MessageBoxButton button = MessageBoxButton.OK, 
        MessageBoxImage icon = MessageBoxImage.None, 
        MessageBoxResult defaultResult = MessageBoxResult.None, 
        MessageBoxOptions options = MessageBoxOptions.None
        )
    {
        return MessageBox.Show(message, caption, button, icon, defaultResult, options);
    }
}
public delegate void DialogCallback<T>(bool? dialogResult, T parameter);
public interface IDialogService
{
    void ShowDialog(FrameworkElement view);
    void ShowDialog<T>(FrameworkElement view, Action<T> onSuccess, bool throwIfNullResult = false, Uri imageUri = null);
    void ShowDialog<T>(FrameworkElement view, DialogCallback<T> callback, bool throwIfNullResult = false, Uri imageUri = null);
    MessageBoxResult ShowMessage(
        string message,
        string caption = null,
        MessageBoxButton button = MessageBoxButton.OK,
        MessageBoxImage icon = MessageBoxImage.None,
        MessageBoxResult defaultResult = MessageBoxResult.None,
        MessageBoxOptions options = MessageBoxOptions.None
        );
}

public class DialogContainer : Window
{
    static DialogContainer()
    {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(DialogContainer),
            new FrameworkPropertyMetadata(typeof(DialogContainer))
            );
    }
    //private IDialogCloseable _closeable = null;
    private static Style _GetBaseStyle;
    private static Style GetBaseStyle
    {
        get
        {
            return _GetBaseStyle ?? (_GetBaseStyle = Application.Current.FindResource(typeof(DialogContainer)) as Style);
        }
    }
    internal DialogContainer(FrameworkElement view, Window owner = null)
    {
        Content = view;
        this.Owner = owner
            ?? Application.Current.Windows.OfType<Window>().Where(x => x.IsActive).FirstOrDefault()
            ?? Application.Current.MainWindow;
        WindowStartupLocation = owner == null                  
            ? System.Windows.WindowStartupLocation.CenterOwner
            : System.Windows.WindowStartupLocation.CenterScreen;
        if (view != null)
        {
            var containerStyle = DialogService.GetContainerStyle(view);
            if (containerStyle != null)
            {
                if (containerStyle.BasedOn == null)
                {
                    containerStyle.BasedOn = GetBaseStyle;
                }
                Style = containerStyle;
            }
            CloseableDescriptor = GetFirstIDialogCloseable(view, view.DataContext);
            var dataContextBinding = new Binding()
            {
                Source = view,
                Path = new PropertyPath(FrameworkElement.DataContextProperty)
            };
            SetBinding(FrameworkElement.DataContextProperty, dataContextBinding);
        }
    }
    private DynamicCloseableDescriptor CloseableDescriptor = null;
    private DynamicCloseableDescriptor GetFirstIDialogCloseable(params object[] items)
    {
        var descriptors = from x in items
                          where x != null 
                          let icloseable = x.GetType()
                              .GetInterfaces()
                              .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDialogCloseable<>))
                              .FirstOrDefault()
                          where icloseable != null
                          select new DynamicCloseableDescriptor(this, x, icloseable);
        var target = descriptors.FirstOrDefault();
        return target;
    }
    private sealed class DynamicCloseableDescriptor : IDisposable
    {
        private static readonly MethodInfo OpenGenericDelegateMethod;
        static DynamicCloseableDescriptor()
        {
            OpenGenericDelegateMethod = typeof(DialogContainer)
                .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(x => x.Name == "DynamicCloseDialog")
                .FirstOrDefault();
        }
        readonly object Target;
        readonly Type ICloseableType = null;
        readonly Delegate DynamicDelegate = null;
        readonly EventInfo DialogCloseEvent = null;
        readonly Type DialogResultType = null;
        public DynamicCloseableDescriptor(DialogContainer container, object target, Type iCloseableType)
        {
            if (container == null) throw new ArgumentNullException("container");
            if (target == null) throw new ArgumentNullException("target");
            if (iCloseableType == null) throw new ArgumentNullException("iCloseableType");
            Target = target;
            ICloseableType = iCloseableType;
            DialogResultType = ICloseableType.GetGenericArguments()[0];
            DialogCloseEvent = ICloseableType.GetEvent("DialogClose");
            DynamicDelegate = Delegate.CreateDelegate(
                DialogCloseEvent.EventHandlerType,
                container,
                OpenGenericDelegateMethod.MakeGenericMethod(DialogResultType)
                );
            DialogCloseEvent.AddEventHandler(Target, DynamicDelegate);
        }
        public void ExecuteCancelIfCan()
        {
            if (IsDisposed) throw new ObjectDisposedException("DynamicCloseableDescriptor");
            var cmd = ((ICancelable)Target).CancelCommand;
            if (cmd != null)
            {
                if (cmd.CanExecute(null))
                {
                    cmd.Execute(null);
                }
            }
            else
            {
                DynamicDelegate.DynamicInvoke(new[] { Target, null });
            }
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private bool IsDisposed = false;
        private void Dispose(bool isDisposing)
        {
            if (!IsDisposed && isDisposing)
            {
                DialogCloseEvent.RemoveEventHandler(Target, DynamicDelegate);
                IsDisposed = true;
            }
        }
    }
    internal object DialogContainerResult = null;

    /// <summary>
    /// Вызывается когда IDialogCloseable запускает DialogCloseEvent.
    /// Вызов этого метода строится динамически в DynamicCloseableDescriptor
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sender">Event Sender</param>
    /// <param name="e">Event args</param>
    private void DynamicCloseDialog<T>(object sender, DialogCloseEventArgs<T> e)
    {
        if (CloseableDescriptor != null)
        {
            CloseableDescriptor.Dispose();
            CloseableDescriptor = null;
        }
        if (e != null)
        {
            if (e.DialogResult == true)
            {
                DialogContainerResult = e.Result;
            }
            this.DialogResult = e.DialogResult;
        }
        else
        {
            this.DialogResult = false;
        }
    }
    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
        if (CloseableDescriptor == null)
        {
            base.OnClosing(e);
        }
        else
        {
            e.Cancel = true;
            this.Dispatcher.BeginInvoke((Action)CloseableDescriptor.ExecuteCancelIfCan);
        }
    }
}

public static class DialogHelper
{
    public static void SendOk<T>(this DialogCloseEvent<T> handler, object sender, T result)
    {
        if (handler != null)
        {
            handler(sender, DialogCloseEventArgs<T>.Success(result));
        }
    }
    public static void SendCancel<T>(this DialogCloseEvent<T> handler, object sender)
    {
        if (handler != null)
        {
            handler(sender, DialogCloseEventArgs<T>.Cancel());
        }
    }
}

public class DialogCloseEventArgs<T> : EventArgs
{
    public bool? DialogResult { get; private set; }
    public T Result { get; private set; }
    public DialogCloseEventArgs(bool? dialogResult, T result)
    {
        DialogResult = dialogResult;
        Result = result;
    }
    private static readonly DialogCloseEventArgs<T> CancelResult;
    static DialogCloseEventArgs()
    {
        CancelResult = new DialogCloseEventArgs<T>(false, default(T));
    }
    public static DialogCloseEventArgs<T> Success(T result)
    {
        return new DialogCloseEventArgs<T>(true, result);
    }
    public static DialogCloseEventArgs<T> Cancel()
    {
        return CancelResult;
    }
}

//public delegate void DialogCloseEvent<in T>(bool dialogResult, T result);
/// <summary>
/// Запрос на закрытие диалога
/// </summary>
/// <typeparam name="T">Тип возвращаемого результата</typeparam>
/// <param name="sender">Отправитель</param>
/// <param name="e">Результат диалога, по умолчанию - null = Cancel</param>
public delegate void DialogCloseEvent<T>(object sender, DialogCloseEventArgs<T> e = null);
public interface ICancelable
{
    ICommand CancelCommand { get; }
}
public interface IDialogCloseable<T> : ICancelable
{
    event DialogCloseEvent<T> DialogClose;
}
READ ALSO
Передача переменных в паттерне MVVM

Передача переменных в паттерне MVVM

Не могу разобраться со связью VM<>MТочнее как грамотно передавать переменные

205
Остановка/Запуск цикла

Остановка/Запуск цикла

В поле ввода человек вводит слова, по событию keyup запускается функиця поиска в которой циклом ищется совпадения ввода и слова в массиве

321
Как получить выбранное значение?

Как получить выбранное значение?

Помогите пожалуйста получить из material autocomplete после нажатия на кнопку submit выбранное значениеМой код выглядит приблизительно так: html:

286
Конвертация чисел в 1K, 1M, 1B, 1T, 1aa, 1ab?

Конвертация чисел в 1K, 1M, 1B, 1T, 1aa, 1ab?

Как и написано в вопросе, требуется реализация такой конвертации Конвертация чисел в 1K, 1M, 1B, 1T, 1aa, 1ab?

1270