Реализация MVVM в WPF без использования системы.Окна.Вход.Метод ICommand

Я пытаюсь реализовать приложение WPF с помощью шаблона MVVM (Model-View-ViewModel), и я хотел бы иметь часть View в отдельной сборке (EXE) из частей Model и ViewModel (DLL).

поворот здесь состоит в том, чтобы очистить сборку Model/ViewModel от любой зависимости WPF. Причина этого в том, что я хотел бы повторно использовать его из исполняемых файлов с разными (не-WPF) UI-техниками, например WinForms или GTK# под Mono.

по умолчанию это невозможно сделать, поскольку ViewModel предоставляет одну или несколько ICommands. Но тип ICommand определен в системе.Окна.Входное пространство имен, которое принадлежит WPF!

Итак, есть ли способ удовлетворить механизм привязки WPF без использования ICommand?

спасибо!

8 ответов


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

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

самым простым примером будет оболочка для делегата void с Execute метод.

все вы разные слои GUI просто нужно перевести из своих собственных типов команд в пользовательские типы команд в одном месте.


WinForms не имеет богатой инфраструктуры привязки данных и команд, необходимой для использования модели представления стиля MVVM.

Так же, как вы не можете повторно использовать контроллеры MVC веб-приложения в клиентском приложении (по крайней мере, не создавая горы оболочек и адаптеров, которые в конце концов просто затрудняют запись и отладку кода без предоставления какого-либо значения клиенту), вы не можете повторно использовать WPF MVVM в приложении WinForms.

Я не использовал GTK# на реальном проект, поэтому я понятия не имею, что он может или не может сделать, но я подозреваю, что MVVM не является оптимальным подходом для GTK#.

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

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

повторите для GTK# или напишите контроллеры и представления MVC, чтобы дать модели веб-интерфейс.

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


мне нужен был пример этого, поэтому я написал один, используя различные методы.

Я имел в виду несколько целей дизайна

1 - Держите его простым

2-абсолютно нет кода в представлении (класс окна)

3-продемонстрировать зависимость только системной ссылки в библиотеке классов ViewModel.

4-Держите бизнес-логику в ViewModel и маршрутизируйте непосредственно к соответствующим методам, не записывая кучу " заглушки" методы.

вот код...

App.xaml (нет StartupUri-единственное, что стоит отметить)

<Application 
    x:Class="WpfApplicationCleanSeparation.App" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>

App.код XAML.cs (загрузить основной вид)

using System.Windows;
using WpfApplicationCleanSeparation.ViewModels;

namespace WpfApplicationCleanSeparation
{
    public partial class App
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            var view = new MainView();
            var viewModel = new MainViewModel();

            view.InitializeComponent();
            view.DataContext = viewModel;
            CommandRouter.WireMainView(view, viewModel);
            view.Show();
        }
    }
}

CommandRouter.cs (магия)

using System.Windows.Input;
using WpfApplicationCleanSeparation.ViewModels;

namespace WpfApplicationCleanSeparation
{
    public static class CommandRouter
    {
        static CommandRouter()
        {
            IncrementCounter = new RoutedCommand();
            DecrementCounter = new RoutedCommand();
        }

        public static RoutedCommand IncrementCounter { get; private set; }
        public static RoutedCommand DecrementCounter { get; private set; }

        public static void WireMainView(MainView view, MainViewModel viewModel)
        {
            if (view == null || viewModel == null) return;

            view.CommandBindings.Add(
                new CommandBinding(
                    IncrementCounter,
                    (λ1, λ2) => viewModel.IncrementCounter(),
                    (λ1, λ2) =>
                        {
                            λ2.CanExecute = true;
                            λ2.Handled = true;
                        }));
            view.CommandBindings.Add(
                new CommandBinding(
                    DecrementCounter,
                    (λ1, λ2) => viewModel.DecrementCounter(),
                    (λ1, λ2) =>
                        {
                            λ2.CanExecute = true;
                            λ2.Handled = true;
                        }));
        }
    }
}

представлении MainView.xaml (код отсутствует, буквально удален!)

<Window 
    x:Class="WpfApplicationCleanSeparation.MainView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WpfApplicationCleanSeparation="clr-namespace:WpfApplicationCleanSeparation" 
    Title="MainWindow" 
    Height="100" 
    Width="100">
    <StackPanel>
        <TextBlock Text="{Binding Counter}"></TextBlock>
        <Button Content="Decrement" Command="WpfApplicationCleanSeparation:CommandRouter.DecrementCounter"></Button>
        <Button Content="Increment" Command="WpfApplicationCleanSeparation:CommandRouter.IncrementCounter"></Button>
    </StackPanel>
</Window>

MainViewModel.cs (включает в себя фактическую модель, так как этот пример настолько упрощен, пожалуйста, извините сход с рельсов MVVM узор.

using System.ComponentModel;

namespace WpfApplicationCleanSeparation.ViewModels
{
    public class CounterModel
    {
        public int Data { get; private set; }

        public void IncrementCounter()
        {
            Data++;
        }

        public void DecrementCounter()
        {
            Data--;
        }
    }

    public class MainViewModel : INotifyPropertyChanged
    {
        private CounterModel Model { get; set; }
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        public MainViewModel()
        {
            Model = new CounterModel();
        }

        public int Counter
        {
            get { return Model.Data; }
        }

        public void IncrementCounter()
        {
            Model.IncrementCounter();

            PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
        }

        public void DecrementCounter()
        {
            Model.DecrementCounter();

            PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
        }
    }
}

Proof

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

Удачи В Кодировании :)

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

    private static void Wire(this UIElement element, RoutedCommand command, Action action)
    {
        element.CommandBindings.Add(new CommandBinding(command, (sender, e) => action(), (sender, e) => { e.CanExecute = true; }));
    }

вместо команд VM exposing, просто выставьте методы. Затем используйте прикрепленные поведения для привязки событий к методам или, если вам нужна команда, используйте ICommand, который может делегировать эти методы и создавать команду через прикрепленные поведения.


конечно это возможно. Вы можете создать еще один уровень абстракции. Добавьте собственный интерфейс IMyCommand, аналогичный или такой же, как ICommand, и используйте его.

взгляните на мое текущее решение MVVM, которое решает большинство проблем, о которых вы упомянули, но полностью абстрагировано от конкретных вещей платформы и может быть повторно использовано. Также я не использовал код-только привязку с DelegateCommands, которые реализуют ICommand. Dialog-это в основном представление-отдельный элемент управления, который имеет свой собственный ViewModel и он отображается из ViewModel главного экрана, но запускается из пользовательского интерфейса через привязку DelagateCommand.

см. полное решение Silverlight 4 здесь модальные диалоги с MVVM и Silverlight 4


извините, Дэйв, но мне не очень понравилось ваше решение. Во-первых, вы должны закодировать сантехнику для каждой команды вручную в коде, затем вы должны настроить CommandRouter, чтобы знать о каждом представлении/viewmodel ассоциации в приложении.

Я выбрал другой подход.

у меня есть служебная сборка Mvvm (которая не имеет зависимостей WPF) и которую я использую в своей viewmodel. В этой сборке я объявляю пользовательский интерфейс ICommand и класс DelegateCommand, который реализует этот интерфейс.

namespace CommonUtil.Mvvm
{
    using System;


    public interface ICommand
    {
        void Execute(object parameter);
        bool CanExecute(object parameter);

        event EventHandler CanExecuteChanged;
    }

    public class DelegateCommand : ICommand
    {
        public DelegateCommand(Action<object> execute) : this(execute, null)
        {

        }

        public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }


        public event EventHandler CanExecuteChanged;

        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
    }
}

у меня также есть сборка библиотеки Wpf (которая ссылается на системные библиотеки WPF), на которую я ссылаюсь из своего проекта WPF UI. В этой сборке я объявляю класс CommandWrapper, который имеет стандартную систему.Окна.Вход.Интерфейс ICommand. CommandWrapper создается с использованием экземпляра моей пользовательской ICommand и просто делегирует Execute, CanExecute и CanExecuteChanged непосредственно моему пользовательскому типу ICommand.

namespace WpfUtil
{
    using System;
    using System.Windows.Input;


    public class CommandWrapper : ICommand
    {
        // Public.

        public CommandWrapper(CommonUtil.Mvvm.ICommand source)
        {
            _source = source;
            _source.CanExecuteChanged += OnSource_CanExecuteChanged;
            CommandManager.RequerySuggested += OnCommandManager_RequerySuggested;
        }

        public void Execute(object parameter)
        {
            _source.Execute(parameter);
        }

        public bool CanExecute(object parameter)
        {
            return _source.CanExecute(parameter);
        }

        public event System.EventHandler CanExecuteChanged = delegate { };


        // Implementation.

        private void OnSource_CanExecuteChanged(object sender, EventArgs args)
        {
            CanExecuteChanged(sender, args);
        }

        private void OnCommandManager_RequerySuggested(object sender, EventArgs args)
        {
            CanExecuteChanged(sender, args);
        }

        private readonly CommonUtil.Mvvm.ICommand _source;
    }
}

в моем Сборка Wpf я также создаю ValueConverter, который при передаче экземпляра моей пользовательской ICommand выплевывает экземпляр Windows.Вход.ICommand совместимый CommandWrapper.

namespace WpfUtil
{
    using System;
    using System.Globalization;
    using System.Windows.Data;


    public class CommandConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return new CommandWrapper((CommonUtil.Mvvm.ICommand)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }
    }
}

теперь мои viewmodels могут предоставлять команды как экземпляры моего пользовательского типа команды без необходимости иметь какую-либо зависимость от WPF, и мой пользовательский интерфейс может связывать окна.Вход.Команды ICommand для этих viewmodels, используя мой ValueConverter так. (Пространство имен XAML спам опущено).

<Window x:Class="Project1.MainWindow">

    <Window.Resources>
        <wpf:CommandConverter x:Key="_commandConv"/>
    </Window.Resources>

    <Grid>
        <Button Content="Button1" Command="{Binding CustomCommandOnViewModel,
                                        Converter={StaticResource _commandConv}}"/>
    </Grid>

</Window>

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

VM-это адаптация модели в соответствии с представлениями WPF. Я бы оставил VM простым и сделал именно это.

Я не могу представить, как заставить MVVM на Winforms. OTOH имея только логику модели и бизнеса, вы можете ввести их непосредственно в форму, если это необходимо.


"вы не можете повторно использовать WPF MVVM в приложении WinForms"

для этого см. url http://waf.codeplex.com/, я использовал MVVM в форме Win, теперь, когда я хотел бы обновить презентацию приложения из формы Win в WPF, она будет изменена без изменения логики приложения,

но у меня есть одна проблема с повторным использованием ViewModel в Asp.net MVC, поэтому я могу сделать такое же настольное приложение win в интернете без или меньше изменений в приложении логика..

спасибо...