Реализация 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"));
}
}
}
просто быстрый и грязный, и я надеюсь, что это полезно кому-то. Я видел несколько разных подходов через различные 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 в интернете без или меньше изменений в приложении логика..
спасибо...