简体   繁体   中英

WPF: Binding custom dependency property in command parameter MultiBinding

I want to bind dictionary to CommandParameter of button. This my xaml code:

<Button.CommandParameter>
    <MultiBinding Converter="{StaticResource MyMultiValueConverter}">
        <Binding>
            <Binding.Source>
                <system:String>SomeString</system:String>
            </Binding.Source>
        </Binding>
        <Binding>
            <Binding.Source>
                <models:StringObjectPair Key="UserId" Value="{Binding User.Id, UpdateSourceTrigger=PropertyChanged}" />
            </Binding.Source>
        </Binding>
        <Binding>
            <Binding.Source>
                <models:StringObjectPair Key="UserName" Value="{Binding User.Name, UpdateSourceTrigger=PropertyChanged}" />
            </Binding.Source>
        </Binding>
    </MultiBinding>
</Button.CommandParameter>

StringObjectPair class:

public class StringObjectPair : FrameworkElement
{
    public string Key { get; set; }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(StringObjectPair), new PropertyMetadata(defaultValue: null));

    public object Value
    {
        get { return GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
}

In the MyMultiValueConverter into the values[1].Value and values[2].Value properties I see nulls, but User.Id and User.Name not equals null. In the output window no errors. How can I bind this?

I found the solution

Changes in StringObjectPair class:

public class StringObjectPair
{
    public string Key { get; set; }

    public object Value { get; set; }
}

Changes in XAML:

<Button.CommandParameter>
    <MultiBinding Converter="{StaticResource MyMultiValueConverter}">
        <Binding /><!--This is DataContext-->
        <Binding>
            <Binding.Source>
                <system:String>SomeString</system:String>
            </Binding.Source>
        </Binding>
        <Binding>
            <Binding.Source>
                <!--Just string in Value-->
                <models:StringObjectPair Key="UserId" Value="User.Id" />
            </Binding.Source>
        </Binding>
        <Binding>
            <Binding.Source>
                <models:StringObjectPair Key="UserName" Value="User.Name" />
            </Binding.Source>
        </Binding>
    </MultiBinding>
</Button.CommandParameter>

And in MyMultiValueConverter I just get the properties on the basis of a Value:

var dataContext = values[0];
var someString = values[1] as string;
var parameters = new Dictionary<string, object>();
for (var i = 2; i < values.Length; i++)
{
    var pair = values[i] as StringObjectPair;
    if (!string.IsNullOrEmpty(pair?.Key) && !parameters.ContainsKey(pair.Key))
    {
        var props = (pair.Value as string)?.Split('.') ?? Enumerable.Empty<string>();
        var value = props.Aggregate(dataContext, (current, prop) => current?.GetType().GetProperty(prop)?.GetValue(current, null));
        parameters.Add(pair.Key, value);
    }
}

First of all, for this kind of scenarios, it is much easier to use move the logic from converter to viewmodel. All inputs for the command should be accessible from viewmodel. Then you don't need command parameter at all:

public class MainWindowViewModel 
{
    private User _user;
    public MainWindowViewModel()
    {
        MyCommand = new DelegateCommand(() =>
        {
            //Here you can access to User.Id, User.Name or anything from here
        });
    }

    public DelegateCommand MyCommand { get; private set; }

    public User User
    {
        get { return _user; }
        set
        {
            if (value == _user) return;
            _user = value;
            OnPropertyChanged();
        }
    }
}

Second, your original Binding to User.Id didn't worked, because the StringObjectPairs you have created are not in VisualTree and therefore don't have datacontext inherited from parent. However, why not to simplify the multibinding:

<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
    <Binding Source="SomeString" />
    <Binding Path="User.Id" />
    <Binding Path="User.Name" />
</MultiBinding>
var someString = (string)values[0];
var userId = (int)(values[1] ?? 0);
var userName = (string)values[2];

I can imagine even simpler solutution using without multibinding:

<Button Command="..."
        CommandParameter="{Binding User, Converter={StaticResource MyConverter}, 
                                         ConverterParameter=SomeString}" />
public class MyConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var user = (User)value;

        var someString = (string)paramenter;
        int userId = user.Id;
        string userName = user.Name;

        return ...
    }

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

EDIT : if you insist on passing key value pairs to command (although I don't consider it to be a good practice) you can simplify the xaml like this:

<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
    <Binding Source="SomeString" ConverterParameter="SomeString" Converter="{StaticResource KeyValueConverter}" />
    <Binding Path="User.Id" ConverterParameter="Id" Converter="{StaticResource KeyValueConverter}"/>
    <Binding Path="User.Name" ConverterParameter="Name" Converter="{StaticResource KeyValueConverter}"/>
</MultiBinding>
public class KeyValueConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return new KeyValuePair<string, object>((string)parameter, value);
    }
}


public class DictionaryMultiConverter : IMultiValueConverter
{
    public object Convert(object[] keyValues, Type targetType, object parameter, CultureInfo culture)
    {
        return keyValues.Cast<KeyValuePair<string, object>>()
                        .ToDictionary(i => i.Key, i => i.Value);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM