如何将此简单绑定到转换器参数?

25

我有问题,不知道如何简单地解决,我有很多像这样的点,所以解决方案不应该太复杂。

我有一个带有设置和主XAML的主项目。

我有一个带有绑定转换器和XAML文件的依赖项项目,其外观如下:

<TextBlock Text="{Binding X.Y.Z, 
                 Converter={StaticResource ProbabilityConverter},                 
                 ConverterParameter=??????????????, Mode=OneWay}"
 />

这个XAML文件由主项目的主XAML文件加载。

我必须将一个Setting属性的值传递到ConverterParameter中,而且这个参数可以在运行时更改,因此必须使用Binding,在这种情况下,我只能使用DependencyProperty进行绑定。

我必须为这个Setting属性创建DependencyProperty包装器来解决这个问题吗?

当我尝试在ConverterParameter中设置Binding时,在运行时会出现以下异常:

无法在类型为“Binding”的“ConverterParameter”属性上设置“Binding”。“Binding”仅能设置在DependencyObject的DependencyProperty上。


要在ConverterParameter中创建绑定,您可以使用Thomas Levesque所示的绑定代理 - marbel82
2个回答

28

您可以绑定到任何属性,不一定是依赖属性。但如果您希望 UI 立即反映属性更改,有两个选项:

  1. 将属性转换为依赖属性。
  2. 在持有该属性的类型上实现 INotifyPropertyChanged 并在属性更改时引发 PropertyChanged 事件。

编辑:

正如问题编辑所指出的那样,无法绑定到 ConverterParameter。但是您可以使用 MultiBinding。例如,假设您想要绑定到一个日期,并将转换器区域设置指定为参数,并在区域设置更改时刷新绑定(我不确定这是否是一个好主意,但它很好地作为示例)。您可以像这样完成它:

<TextBlock>
    <TextBlock.Resources>
        <local:DateCultureConverter x:Key="converter" />
    </TextBlock.Resources>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource converter}">
            <Binding Path="Date" />
            <Binding Path="Settings.Culture" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

在这里,DateSettings都是当前DataContext上的属性。DateCultureConverter实现了IMultiValueConverter,在真正的应用程序中,您可能会将其放在资源的层次结构中的较高级别。


如果我可以绑定到普通属性,那就没问题了,因为我有实现inotitypropertychanged的类;-) 但是编译器说,如果我有嵌套绑定,那么只能绑定到dependencyproperty;-( 我会再试一次并更新这个话题。 - Svisstack
谢谢回复,对我也有用,只是在不同的情况下。请注意,您可以通过在绑定内部定义转换器而不是创建资源来简化此语法。Svisstack,我认为这是正确的解决方案,而不是一种变通方法 :-) - MikeKulls
1
很遗憾,当您需要绑定为双向时,MultiBinding并不是一个合适的替代品。 - Drew Noakes

12
您可以使用以下解决方案之一:
  1. BindableParameter(使用普通绑定、附加属性和MarkupExtension)

https://marlongrech.wordpress.com/2008/08/03/my-wish-came-true-i-can-now-use-databinding-in-a-converterparameter/

你需要集成类BindableParameter和BindableParameterExtension(如下所示),然后可以按照以下方式使用它:
在 XAML 中:
    xmlns:local="clr-namespace:BindableParameterExtension"

    <local:SampleConverter x:Key="sampleConverter" />

    <StackPanel Orientation="Vertical">
        <TextBox Name="txtContent" Text="Text from txtContent" />
        <TextBox Name="txtParameter" Text="Text from txtParameter" />
        <TextBox Name="txtBindingSample" 
                 Text="{Binding ElementName=txtContent, Path=Text, Converter={StaticResource sampleConverter}}"
                 local:BindableParameter.BindParameter="{local:BindableParameter TargetProperty=TextBox.Text,
                               Binding={Binding ElementName=txtParameter, Path=Text} }" />
    </StackPanel>

属性 "TargetProperty":

TargetProperty=TextBox.Text

BindableParameter的值必须设置为原始绑定属性(在本例中为"TextBox.Text")。

示例转换器:

using System;
using System.Windows.Data;

namespace BindableParameterExtension
{
     public class SampleConverter : IValueConverter
     {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null && parameter != null)
            {
                return value.ToString() + ", " + parameter.ToString();
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is string && parameter is string)
            {
                string text1 = value as string;
                string textParamter = parameter as string;

                return text1.Replace(textParamter, "");
            }
            return value;
       }
    }
}    

该参数可用于“Convert”和“ConvertBack”方法(对于绑定到视图模型非常有用)。
类BindableParameter和BindableParameterExtension(URL见上面(不是我的代码))。
/*
 * Copyright - Everyone can use this code for any reason yet if you find a bug, I do not hold myself responsable :D
 */

using System.Windows.Data;
using System.Windows.Markup;

namespace BindableParameterExtension
{
    /// <summary>
    /// BindableParameter is the class that changes the ConverterParameter Value
    /// This must inherit from freezable so that it can be in the inheritance context and thus be able to use the DataContext and to specify ElementName binding as a ConverterParameter
    /// http://www.drwpf.com/Blog/Default.aspx?tabid=36&EntryID=36
    /// </summary>
    public class BindableParameter : Freezable
    {
        #region fields
        //this is a hack to trick the WPF platform in thining that the binding is not sealed yet and then change the value of the converter parameter
        private static FieldInfo isSealedFieldInfo;
        #endregion

        #region Properties
        #region Parameter

        /// <summary>
        /// Parameter Dependency Property
        /// </summary>
        public static readonly DependencyProperty ParameterProperty =
            DependencyProperty.Register("Parameter", typeof(object), typeof(BindableParameter),
                new FrameworkPropertyMetadata((object)null,
                    (d, e) =>
                    {
                        BindableParameter param = (BindableParameter)d;
                        //set the ConverterParameterValue before calling invalidate because the invalidate uses that value to sett the converter paramter
                        param.ConverterParameterValue = e.NewValue;
                        //update the converter parameter 
                        InvalidateBinding(param);
                    }
                    ));

        /// <summary>
        /// Gets or sets the Parameter property.  This dependency property 
        /// indicates ....
        /// </summary>
        public object Parameter
        {
            get { return (object)GetValue(ParameterProperty); }
            set { SetValue(ParameterProperty, value); }
        }

        #endregion

        #region BindParameter

        /// <summary>
        /// BindParameter Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty BindParameterProperty =
            DependencyProperty.RegisterAttached("BindParameter", typeof(BindableParameter), typeof(BindableParameter),
                new FrameworkPropertyMetadata((BindableParameter)null,
                    new PropertyChangedCallback(OnBindParameterChanged)));

        /// <summary>
        /// Gets the BindParameter property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static BindableParameter GetBindParameter(DependencyObject d)
        {
            return (BindableParameter)d.GetValue(BindParameterProperty);
        }

        /// <summary>
        /// Sets the BindParameter property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static void SetBindParameter(DependencyObject d, BindableParameter value)
        {
            d.SetValue(BindParameterProperty, value);
        }

        /// <summary>
        /// Handles changes to the BindParameter property.
        /// </summary>
        private static void OnBindParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = d as FrameworkElement;
            if (element == null)
                throw new InvalidOperationException("BindableParameter can be applied to a FrameworkElement only");

            BindableParameter parameter = (BindableParameter)e.NewValue;
            element.Initialized += delegate
            {
                parameter.TargetExpression = BindingOperations.GetBindingExpression(element, parameter.TargetProperty);
                parameter.TargetBinding = BindingOperations.GetBinding(element, parameter.TargetProperty);

                //update the converter parameter 
                InvalidateBinding(parameter);
            };
        }

        #endregion

        public object ConverterParameterValue { get; set; }

        public BindingExpression TargetExpression { get; set; }

        public Binding TargetBinding { get; private set; }

        /// <summary>
        /// Gets the object being bound
        /// </summary>
        public DependencyObject TargetObject { get; private set; }

        /// <summary>
        /// Gets the dependency property being bound
        /// </summary>
        public DependencyProperty TargetProperty { get; internal set; }
        #endregion

        /// <summary>
        /// Static constructor to get the FieldInfo meta data for the _isSealed field of the BindingBase class
        /// </summary>
        static BindableParameter()
        {
            //initialize the field info once
            isSealedFieldInfo =
                typeof(BindingBase).GetField("_isSealed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            if (isSealedFieldInfo == null)
                throw new InvalidOperationException("Oops, we have a problem, it seems like the WPF team decided to change the name of the _isSealed field of the BindingBase class.");

        }

        private static void InvalidateBinding(BindableParameter param)
        {
            if (param.TargetBinding != null && param.TargetExpression != null)
            {
                //this is a hack to trick the WPF platform in thining that the binding is not sealed yet and then change the value of the converter parameter
                bool isSealed = (bool)isSealedFieldInfo.GetValue(param.TargetBinding);

                if (isSealed)//change the is sealed value
                    isSealedFieldInfo.SetValue(param.TargetBinding, false);

                param.TargetBinding.ConverterParameter = param.ConverterParameterValue;

                if (isSealed)//put the is sealed value back as it was...
                    isSealedFieldInfo.SetValue(param.TargetBinding, true);

                //force an update to the binding
                param.TargetExpression.UpdateTarget();
            }
        }

        #region Freezable Stuff
        protected override Freezable CreateInstanceCore()
        {
            //throw new NotImplementedException();
            //return _bindableParam;

            return this;
        }
        #endregion

    }

    /// <summary>
    /// Markup extension so that it is easier to create an instance of the BindableParameter from XAML
    /// </summary>
    [MarkupExtensionReturnType(typeof(BindableParameter))]
    public class BindableParameterExtension : MarkupExtension
    {
        /// <summary>
        /// Gets or sets the Dependency property you want to change the binding's ConverterParameter
        /// </summary>
        public DependencyProperty TargetProperty { get; set; }

        /// <summary>
        /// Gets or sets the Binding that you want to use for the converter parameter
        /// </summary>
        public Binding Binding { get; set; }

        /// <summary>
        /// constructor that accepts a Dependency Property so that you do not need to specify TargetProperty
        /// </summary>
        /// <param name="property">The Dependency property you want to change the binding's ConverterParameter</param>
        public BindableParameterExtension(DependencyProperty property)
        {
            TargetProperty = property;
        }

        public BindableParameterExtension()
        { }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            _bindableParam = new BindableParameter();
            //set the binding of the parameter
            BindingOperations.SetBinding(_bindableParam, BindableParameter.ParameterProperty, Binding);
            _bindableParam.TargetProperty = TargetProperty;
            return _bindableParam;
        }

        private BindableParameter _bindableParam;

    }
}
  1. 对象引用:

http://drwpf.com/blog/2007/09/02/supplying-an-object-reference-in-the-constructorparameters-collection-of-an-objectdataprovider/

你必须集成ObjectReference类:

http://www.drwpf.com/blog/Portals/0/Code/ObjectReference.cs.txt

在XAML中:
xmlns:local="clr-namespace:WpfMarkupExtension"

<local:SampleConverter x:Key="sampleConverter" />

<StackPanel Orientation="Vertical">
    <TextBox Name="txtContent" Text="Text from txtContent" />
    <TextBox Name="txtParameter" Text="Text from txtParameter" local:ObjectReference.Declaration="{local:ObjectReference txtParam}" />
    <TextBox Name="txtBindingSample" 
             Text="{Binding ElementName=txtContent, Path=Text, 
                            Converter={StaticResource sampleConverter},
                            ConverterParameter={local:ObjectReference txtParam}}" />
</StackPanel>

这段代码:

local:ObjectReference.Declaration="{local:ObjectReference txtParam}"

创建一个静态字典中的引用和部分内容:
ConverterParameter={local:ObjectReference txtParam}}" 

从字典中获取此对象引用-->这里没有绑定,字典是在解析时填充的。

示例转换器:

using System;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfMarkupExtension
{
    public class SampleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null && parameter is TextBox)
            {
                return value.ToString() + ", " + ((TextBox)parameter).Text;
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is string && parameter is TextBox)
            {
                string text1 = value as string;
                string textParamter = ((TextBox)parameter).Text;

                return text1.Replace(textParamter, "");

            }

            return value;
        }
    }
}

3. 可绑定的转换器参数(使用自定义绑定语法):

http://www.codeproject.com/Articles/456589/Bindable-Converter-Parameter

在XAML中:
    xmlns:local="clr-namespace:BcpBindingExtension"

    <local:SampleConverter x:Key="sampleConverter" />

    <StackPanel Orientation="Vertical">
        <TextBox Name="txtContent" Text="Text from txtContent" />
        <TextBox Name="txtParameter" Text="Text from txtParameter" />
        <TextBox Name="txtBindingSample">
                 <TextBox.Text>
                    <local:BcpBinding Path="Text" ElementName="txtContent"
                                      Converter="{StaticResource sampleConverter}"
                                      ConverterParameters="Binding Path=Text ElementName=txtParameter"
                                      Mode="TwoWay"/>
                 </TextBox.Text>
        </TextBox>
    </StackPanel>

示例转换器:

using System;
using System.Windows.Data;

namespace BcpBindingExtension
{
    public class SampleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null && parameter is object[] && ((object[])parameter).Length > 0)
            {
                return value.ToString() + ", " + ((object[])parameter)[0].ToString();
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is string && parameter is object[] && ((object[])parameter).Length > 0)
            {
                string text1 = value as string;
                string textParamter = ((object[])parameter)[0] as string;

                return text1.Replace(textParamter, "");

            }

            return value;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接