8

MVVM (Model-View-ViewModel) パターンを使用して WPF アプリケーションを実装しようとしていますが、View パーツを Model および ViewModel パーツ (DLL) とは別のアセンブリ (EXE) に配置したいと考えています。

ここでのひねりは、Model/ViewModel アセンブリを WPF の依存関係から切り離すことです。この理由は、Mono での WinForms や GTK# など、異なる (WPF 以外の) UI 技術を持つ実行可能ファイルから再利用したいからです。

ViewModel は 1 つ以上の ICommand を公開するため、既定では、これは実行できません。しかし、ICommand 型は、WPF に属する System.Windows.Input 名前空間で定義されています。

では、ICommand を使用せずに WPF バインディング メカニズムを満たす方法はありますか?

ありがとう!

4

8 に答える 8

7

wpf レイヤーで 1 つの WPF カスタム ルーティング コマンドと 1 つのコマンド ハンドラー クラスを定義できる必要があります。すべての WPF クラスは、適切なパラメーターを使用してこの 1 つのコマンドにバインドできます。

その後、ハンドラー クラスは、ViewModel レイヤーで独自に定義し、WPF に依存しない独自のカスタム コマンド インターフェイスにコマンドを変換できます。

最も単純な例は、Execute メソッドを持つ void デリゲートへのラッパーです。

さまざまな GUI レイヤーはすべて、ネイティブ コマンド タイプからカスタム コマンド タイプに 1 か所で変換するだけで済みます。

于 2009-02-02T14:34:05.463 に答える
5

WinForms には、MVVM スタイルのビュー モデルを使用するために必要な豊富なデータ バインディングとコマンド インフラストラクチャがありません。

クライアント アプリケーションで Web アプリケーション MVC コントローラーを再利用できないのと同じように (少なくとも、顧客に価値を提供せずにコードの記述とデバッグを困難にするラッパーとアダプターの山を作成しない限り)、次のことができます。 WinForms アプリケーションで WPF MVVM を再利用しないでください。

私は実際のプロジェクトで GTK# を使用したことがないので、できることとできないことはわかりませんが、とにかく MVVM は GTK# の最適なアプローチではないと思います。

アプリケーションの動作の多くをモデルに移動するようにしてください。モデルからのデータのみを公開し、ビュー モデルにロジックを持たないコマンドに基づいてモデルを呼び出すビュー モデルを用意してください。

次に、WinForms の場合は、ビュー モデルを削除して UI からモデルを直接呼び出すか、WinForms のより制限されたデータ バインディング サポートに基づく別の中間層を作成します。

GTK# について繰り返すか、MVC コントローラーとビューを記述して、モデルに Web フロントエンドを提供します。

あるテクノロジーを別のテクノロジーに最適化された使用パターンに強制しようとしないでください。独自のコマンド インフラストラクチャをゼロから作成しないでください (以前に作成したことがありますが、最も生産的な選択ではありません)。各テクノロジーに最適なツールを使用してください。 .

于 2009-02-03T14:52:16.720 に答える
4

この例が必要だったので、さまざまな手法を使用して書きました。

いくつかの設計目標を念頭に置いていました

1 - シンプルに保つ

2 - ビューにコード ビハインドがまったくない (ウィンドウ クラス)

3 - ViewModel クラス ライブラリの System 参照のみの依存関係を示します。

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:私自身のコードを簡素化するために、これは Adds をワンライナーにするのに役立つかもしれません。

    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; }));
    }
于 2011-02-10T14:10:09.180 に答える
4

申し訳ありませんが、私はあなたのソリューションがあまり好きではありませんでした。まず、各コマンドの配管をコードで手動でコーディングする必要があります。次に、CommandRouter を構成して、アプリケーション内の各ビュー/ビューモデルの関連付けを認識する必要があります。

私は別のアプローチを取りました。

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 UI プロジェクトから参照する Wpf ライブラリ アセンブリ (システム WPF ライブラリを参照する) もあります。そのアセンブリで、標準の System.Windows.Input.ICommand インターフェイスを持つ CommandWrapper クラスを宣言します。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 アセンブリでは、カスタム ICommand のインスタンスが渡されると、Windows.Input.ICommand 互換の CommandWrapper のインスタンスを吐き出す ValueConverter も作成します。

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();
        }
    }
}

これでビューモデルは、WPF に依存することなく、カスタム コマンド タイプのインスタンスとしてコマンドを公開できます。また、UI は、ValueConverter を使用して Windows.Input.ICommand コマンドをこれらのビューモデルにバインドできます。(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>

私が本当に怠け者で (私はそうです)、毎回 CommandConverter を手動で適用する必要がない場合は、Wpf アセンブリで次のような独自の Binding サブクラスを作成できます。

namespace WpfUtil
{
    using System.Windows.Data;


    public class CommandBindingExtension : Binding
    {
        public CommandBindingExtension(string path) : base(path)
        {
            Converter = new CommandConverter();
        }
    }
}

したがって、次のように、さらに簡単にカスタム コマンド タイプにバインドできます。

<Window x:Class="Project1.MainWindow"
                xmlns:wpf="clr-namespace:WpfUtil;assembly=WpfUtil">

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

    <Grid>
        <Button Content="Button1" Command="{wpf:CommandBinding CustomCommandOnViewModel}"/>
    </Grid>

</Window>
于 2013-11-05T17:42:50.747 に答える
2

もちろん、これは可能です。別のレベルの抽象化を作成できます。ICommandと同様または同じ独自のIMyCommandインターフェイスを追加し、それを使用します。

あなたが言及した問題のほとんどを解決するが、プラットフォーム固有のものから完全に抽象化され、再利用できる私の現在のMVVMソリューションを見てください。また、ICommandを実装するDelegateCommandsとのバインディングのみのコードビハインドは使用しませんでした。ダイアログは基本的にビューです。独自のViewModelを持つ別個のコントロールであり、メイン画面のViewModelから表示されますが、DelagateCommandバインディングを介してUIからトリガーされます。

ここで完全なSilverlight4ソリューションを参照してくださいMVVMおよびSilverlight4を使用したモーダルダイアログ

于 2010-01-21T21:19:01.537 に答える
2

コマンドを公開する VM の代わりに、メソッドを公開するだけです。次に、添付された動作を使用してイベントをメソッドにバインドするか、コマンドが必要な場合は、これらのメソッドに委任できる ICommand を使用し、添付された動作を通じてコマンドを作成します。

于 2009-03-27T21:38:58.770 に答える
1

プロジェクトを間違った時点で分離していると思います。モデルとビジネス ロジック クラスのみを共有する必要があると思います。

VM は、WPF ビューに合わせてモデルを適応させたものです。私は VM をシンプルに保ち、まさにそれを行います。

WinformにMVVMを強制することは想像できません。OTOHにはモデルとビジネスロジックだけがあり、必要に応じてそれらをフォームに直接挿入できます。

于 2011-05-06T16:15:30.790 に答える
0

「WinFormsアプリケーションでWPFMVVMを再利用することはできません」

これについては、URL http://waf.codeplex.com/を参照してください。WinフォームでMVVMを使用しましたが、アプリケーションのプレゼンテーションをWinフォームからWPFにアップグレードする場合は常に、アプリケーションロジックを変更せずに変更されます。

しかし、Asp.net MVCでViewModelを再利用する際に問題が1つあるため、アプリケーションロジックを変更することなく、またはほとんど変更せずに、同じデスクトップをWebでアプリケーションに勝つことができます。

ありがとう...

于 2011-07-25T13:08:13.047 に答える