1

最近、ビュー モデルでコマンドにバインドできるようにするソリューションをコードに実装しました。私が使用した方法へのリンクは次のとおりです: https://code.msdn.microsoft.com/Event-to-Command-24d903c8。リンクの2番目の方法を使用しました。私のコードがこのコードと非常によく似ていると、すべての意図と目的で想定できます。これはうまくいきます。ただし、このダブルクリックにもコマンド パラメーターをバインドする必要があります。どうすればそれを設定できますか?

これが私のプロジェクトの背景です。このプロジェクトの背後にあるセットアップのいくつかは奇妙に思えるかもしれませんが、ここでは詳細を説明しないため、この方法で行う必要があります。最初に注意すべきことは、このバインドのセットアップが多値コンバーターの内部で行われているということです。新しい要素を生成するコードは次のとおりです。

DataTemplate dt = new DataTemplate();
dt.DataType = typeof(Button);

FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
btn.SetValue(Attached.DoubleClickCommandProperty, ((CardManagementViewModel)values[1]).ChangeImageCommand);

dt.VisualTree = btn;

values[1] は DataContext で、ここではビューモデルです。ビュー モデルには次のものが含まれます。

private RelayCommand _ChangeImageCommand;

public ICommand ChangeImageCommand
{
    get
    {
        if (_ChangeImageCommand == null)
        {
            _ChangeImageCommand = new RelayCommand(
                param => this.ChangeImage(param)
                );
        }
        return _ChangeImageCommand;
    }
}

private void ChangeImage(object cardParam)
{
}

そのコマンドパラメーターを渡すにはどうすればよいですか? 私はこれまで何度も XAML を使用してこれらすべてをバインドしてきましたが、C# からバインドする必要はありませんでした。助けてくれてありがとう!

編集

これが私の問題の完全なサンプルです。このサンプルには、実際にこのように実行する目的がないことはわかっていますが、この問題のために、そのまま実行します。

表示したい文字列の ObservableCollection があるとしましょう。これらはビューモデルに含まれています。

private ObservableCollection<string> _MyList;
public ObservableCollection<string> MyList { get { return _MyList; } set { if (_MyList != value) { _MyList = value; RaisePropertyChanged("MyList"); } } }
public ViewModel()
{
    MyList = new ObservableCollection<string>();
    MyList.Add("str1");
    MyList.Add("str2");
    MyList.Add("str3");
}

私のチームのUI担当者が私にこれを渡してくれました

<ContentControl>
    <ContentControl.Content>
        <MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
            <Binding Path="MyList"/>
            <Binding />
        </MultiBinding>
    </ContentControl.Content>
</ContentControl>

ここで、UI 担当者とプロジェクト マネージャーが共謀して私の人生を生き地獄にしようと決心したとします。そのため、これらの項目を XAML ではなく、 ContentControl のコンテンツがバインドされているコンバーター。だから私はこれを行います:

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    ListBox lb = new ListBox();
    lb.ItemsSource = (ObservableCollection<string>)values[0];
    DataTemplate dt = new DataTemplate();
    dt.DataType = typeof(Button);

    FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
    btn.SetValue(Button.WidthProperty, 100D);
    btn.SetValue(Button.HeightProperty, 50D);
    btn.SetBinding(Button.ContentProperty, new Binding());

    dt.VisualTree = btn;
    lb.ItemTemplate = dt;

    return lb;
}

これが成功すると、リストボックスが表示され、すべての項目がボタンとして表示されます。翌日、ばかげたプロジェクト マネージャーがビュー モデルで新しいコマンドを作成します。その目的は、ボタンの 1 つがダブルクリックされた場合に、選択されたアイテムをリストボックスに追加することです。シングルクリックではなく、ダブルクリック!これは、CommandProperty を使用できないこと、さらに重要なことに CommandParameterProperty を使用できないことを意味します。ビューモデルでの彼のコマンドは次のようになります。

private RelayCommand _MyCommand;

public ICommand MyCommand
{
    get
    {
        if (_MyCommand == null)
        {
            _MyCommand = new RelayCommand(
                param => this.MyMethod(param)
                );
        }
        return _MyCommand;
    }
}

private void MyMethod(object myParam)
{
    MyList.Add(myParam.ToString());
}

グーグルで検索した後、DoubleClick イベントを添付プロパティに変換するクラスを見つけました。そのクラスは次のとおりです。

public class Attached
{
    static ICommand command;

    public static ICommand GetDoubleClickCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(DoubleClickCommandProperty);
    }

    public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(DoubleClickCommandProperty, value);
    }

    // Using a DependencyProperty as the backing store for DoubleClickCommand.  This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty DoubleClickCommandProperty =
        DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));

    static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var fe = obj as FrameworkElement;
        command = e.NewValue as ICommand;
        fe.AddHandler(Button.MouseDoubleClickEvent, new RoutedEventHandler(ExecuteCommand));
    }

    static void ExecuteCommand(object sender, RoutedEventArgs e)
    {
        var ele = sender as Button;
        command.Execute(null);
    }
}

次に、コンバーターに戻り、この行をdt.VisualTree = btn;のすぐ上に置きます。:

btn.SetValue(Attached.DoubleClickCommandProperty, ((ViewModel)values[1]).MyCommand);

これでプロジェクト マネージャーのコマンドは正常に実行されましたが、それでもリスト ボックスの選択項目を渡す必要があります。その後、プロジェクト マネージャーから、ビューモデルに触れることはもう許可されていないと言われました。これは私が立ち往生しているところです。ビュー モデルでプロジェクト マネージャーのコマンドにリストボックスの選択項目を送信するにはどうすればよいですか?

この例の完全なコード ファイルは次のとおりです。

ViewModel.cs

using System.Collections.ObjectModel;
using System.Windows.Input;
using WpfApplication2.Helpers;

namespace WpfApplication2
{
    public class ViewModel : ObservableObject
    {
        private ObservableCollection<string> _MyList;
        private RelayCommand _MyCommand;

        public ObservableCollection<string> MyList { get { return _MyList; } set { if (_MyList != value) { _MyList = value; RaisePropertyChanged("MyList"); } } }

        public ViewModel()
        {
            MyList = new ObservableCollection<string>();
            MyList.Add("str1");
            MyList.Add("str2");
            MyList.Add("str3");
        }

        public ICommand MyCommand
        {
            get
            {
                if (_MyCommand == null)
                {
                    _MyCommand = new RelayCommand(
                        param => this.MyMethod(param)
                        );
                }
                return _MyCommand;
            }
        }

        private void MyMethod(object myParam)
        {
            MyList.Add(myParam.ToString());
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:helpers="clr-namespace:WpfApplication2.Helpers"
        xmlns:Converters="clr-namespace:WpfApplication2.Helpers.Converters"
        xmlns:local="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <Converters:MyConverter x:Key="MyConverter"/>
    </Window.Resources>
    <ContentControl>
        <ContentControl.Content>
            <MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
                <Binding Path="MyList"/>
                <Binding />
            </MultiBinding>
        </ContentControl.Content>
    </ContentControl>
</Window>

MyConverter.cs

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication2.Helpers.Converters
{
    public class MyConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            ListBox lb = new ListBox();
            lb.ItemsSource = (ObservableCollection<string>)values[0];

            DataTemplate dt = new DataTemplate();
            dt.DataType = typeof(Button);

            FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
            btn.SetValue(Button.WidthProperty, 100D);
            btn.SetValue(Button.HeightProperty, 50D);
            btn.SetBinding(Button.ContentProperty, new Binding());

            btn.SetValue(Attached.DoubleClickCommandProperty, ((ViewModel)values[1]).MyCommand);
            // Somehow create binding so that I can pass the selected item of the listbox to the 
            // above command when the button is double clicked.  

            dt.VisualTree = btn;
            lb.ItemTemplate = dt;

            return lb;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

添付.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication2.Helpers
{
    public class Attached
    {
        static ICommand command;

        public static ICommand GetDoubleClickCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(DoubleClickCommandProperty);
        }

        public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(DoubleClickCommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for DoubleClickCommand.  This enables animation, styling, binding, etc... 
        public static readonly DependencyProperty DoubleClickCommandProperty =
            DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));

        static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var fe = obj as FrameworkElement;
            command = e.NewValue as ICommand;
            fe.AddHandler(Button.MouseDoubleClickEvent, new RoutedEventHandler(ExecuteCommand));
        }

        static void ExecuteCommand(object sender, RoutedEventArgs e)
        {
            var ele = sender as Button;
            command.Execute(null);
        }
    }
}

ObservableObject.cs

using System;
using System.ComponentModel;
using System.Diagnostics;

namespace WpfApplication2.Helpers
{
    public class ObservableObject : INotifyPropertyChanged
    {
        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This 
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public virtual void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new Exception(msg);
                else
                    Debug.Fail(msg);
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raises the PropertyChange event for the property specified
        /// </summary>
        /// <param name="propertyName">Property name to update. Is case-sensitive.</param>
        public virtual void RaisePropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);
            OnPropertyChanged(propertyName);
        }

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members
    }
}

RelayCommand.cs

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace WpfApplication2.Helpers
{
    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameters)
        {
            return _canExecute == null ? true : _canExecute(parameters);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

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

        #endregion // ICommand Members
    }
}

繰り返しますが、助けてくれてありがとう!!!

4

1 に答える 1

2

投稿したコードは、実際には最小限または完全な例ではありません。少なくとも、CardManagementViewModel型が欠落しており、もちろん、例は元のコードに基づいているように見えますが、最小限の例に減らす試みはありません。

そのため、すべてのコードを調べるのに多くの時間を費やすことはありませんでした。わざわざコンパイルして実行することは気にしませんでした。ただし、元の編集で欠けていた主なものは、添付プロパティの実装です。Attachedそれを踏まえて、クラスを次のように変更することをお勧めします。

public class Attached
{
    public static ICommand GetDoubleClickCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(DoubleClickCommandProperty);
    }

    public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(DoubleClickCommandProperty, value);
    }

    public static object GetDoubleClickCommandParameter(DependencyObject obj)
    {
        return obj.GetValue(DoubleClickCommandParameterProperty);
    }

    public static void SetDoubleClickCommandParameter(DependencyObject obj, object value)
    {
        obj.SetValue(DoubleClickCommandParameterProperty, value);
    }

    // Using a DependencyProperty as the backing store for DoubleClickCommand.  This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty DoubleClickCommandProperty =
        DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));
    public static readonly DependencyProperty DoubleClickCommandParameterProperty =
        DependencyProperty.RegisterAttached("DoubleClickCommandParameter", typeof(object), typeof(Attached));

    static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var fe = obj as FrameworkElement;

        if (e.OldValue == null && e.NewValue != null)
        {
            fe.AddHandler(Button.MouseDoubleClickEvent, ExecuteCommand);
        }
        else if (e.OldValue != null && e.NewValue == null)
        {
            fe.RemoveHandler(Button.MouseDoubleClickEvent, ExecuteCommand);
        }
    }

    static void ExecuteCommand(object sender, RoutedEventArgs e)
    {
        var ele = sender as Button;
        ICommand command = GetDoubleClickCommand(ele);
        object parameter = GetDoubleClickCommandParameter(ele);

        command.Execute(parameter);
    }
}

注意: 上記は Web ブラウザに入力したものです。Minimal, Complete, and Verifiable code exampleが不足しているため、上記のコードをコンパイルすることも、実行することも気にしませんでした。誤植や論理エラーがあったとしても、それらは最小限であり、目標に基づいてコードが実際にどうあるべきかを簡単に理解できると信じています.

ここでの主なことは、Attached.DoubleClickCommandParameter添付プロパティを追加したことです。これにより、コマンド自体と同時にコマンド パラメータを設定できます。

また、他のいくつかの実装の詳細を変更しました。

  1. コマンドとそのパラメーターは、実装のようにフィールドに保存するのICommandではなく、イベントが発生したときに特定のオブジェクトに対して取得されます。staticコードのやり方では、一度に 1 つのコマンドしか使用できませんでした。複数の要素に添付プロパティを設定しようとして、複数のICommand値を使用した場合でも、最新の recent-set しか取得できませんICommand。私の変更により、設定したコマンドを常に取得できます。
  2. プロパティの変更を処理するコードを変更して、前の値が null で新しい値が null でない場合にのみハンドラーを追加するようにしました。 null 以外の値から null に戻ったことはありません。

次に、次のようにコード ビハインドで添付プロパティを使用できます。

Attached.SetDoubleClickCommand(btn, ((CardManagementViewModel)values[1]).ChangeImageCommand);
Attached.SetDoubleClickCommandParameter(btn, ((CardManagementViewModel)values[1]).ChangeImageCommandParameter);

ChangeImageCommandParameter送信するパラメーターを格納するプロパティがあると想定していることに注意してください。もちろん、選択した項目を参照する値など、任意のプロパティ値を設定できます。

Attachedまた、クラスのプロパティ セッター メソッドを呼び出すように設定を変更しました。これは、WPF の添付プロパティの抽象化をより適切に使用するためです。確かに、ほとんどの実装では、メソッドを直接呼び出すのとまったく同じSetValue()ですが、何らかの方法で動作をカスタマイズしている場合は、添付プロパティのメソッドを使用することをお勧めします。


とはいえ、あなたの広範な設計はいくつかの異なる点で非常に間違っていることを繰り返します。MVVM などの従来のアプローチを無視し、UI の構成と動作をビュー モデルに結び付け、特にオブジェクトの状態を実際に変更する場所としてコンバーターを使用することで、数が多い可能性が高いシステムを作成しています。微妙で、見つけるのが難しく、修正がほぼ不可能なバグが含まれています。

しかし、それは添付プロパティの使用方法の問題とはほとんど無関係です。適切に設計された WPF プログラムであっても、添付プロパティにはその役割があります。上記の説明から、既存の添付プロパティを拡張して追加の値 (値など) をサポートする方法について、より良いアイデアが得られることを願っていますCommandProperty

于 2016-10-10T23:42:52.080 に答える