0

有人可以解释一下这个错误吗?

起初我以为 SelectedIndex 可能只是不是 DependencyProperty 并且不能绑定,但我错了。

如果我使用普通绑定而不是标记扩展名src:ValidatedBinding,或者如果我保留标记扩展名但绑定SelectedItem而不是SelectedIndex,那么它可以工作。

这是一个演示问题的小应用程序。

主窗口:

<Window       x:Class="WpfApplication2.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:src="clr-namespace:WpfApplication2"
                Title="MainWindow" 
               Height="350" 
                Width="525"
                     >
    <ComboBox SelectedIndex="{src:ValidatedBinding SelectedIndex}"
              VerticalAlignment="Center" HorizontalAlignment="Center" Width="100">
        <ComboBoxItem>Not Specified</ComboBoxItem>
        <ComboBoxItem>First</ComboBoxItem>
        <ComboBoxItem>Second</ComboBoxItem>
    </ComboBox>
</Window>

主窗口后面的代码:

using System.Windows;

namespace WpfApplication2
{
    /// <summary>
    /// The main window.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new Item { Description = "Item 1", SelectedIndex = 0 };
        }
    }

    /// <summary>
    /// An object with a string and an int property.
    /// </summary>
    public sealed class Item
    {
        int _selectedIndex;
        string _description;

        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set { _selectedIndex = value; }
        }
    }
}

标记扩展的代码:

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

namespace WpfApplication2
{
    /// <summary>
    /// Creates a normal Binding but defaults NotifyOnValidationError and 
    /// ValidatesOnExceptions to True, Mode to TwoWay and UpdateSourceTrigger
    /// to LostFocus.
    /// </summary>
    [MarkupExtensionReturnType(typeof(Binding))]
    public sealed class ValidatedBinding : MarkupExtension
    {
        public ValidatedBinding(string path)
        {
            Mode = BindingMode.TwoWay;

            UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;

            Path = path;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var Target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

            /* on combo boxes, use an immediate update and validation */
            DependencyProperty DP = Target.TargetProperty as DependencyProperty;
            if (DP != null && DP.OwnerType == typeof(System.Windows.Controls.Primitives.Selector)
                && UpdateSourceTrigger == UpdateSourceTrigger.LostFocus) {
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            }

            return new Binding(Path) {
                Converter = this.Converter,
                ConverterParameter = this.ConverterParameter,
                ElementName = this.ElementName,
                FallbackValue = this.FallbackValue,
                Mode = this.Mode,
                NotifyOnValidationError = true,
                StringFormat = this.StringFormat,
                ValidatesOnExceptions = true,
                UpdateSourceTrigger = this.UpdateSourceTrigger
            };
        }

        public IValueConverter Converter { get; set; }

        public object ConverterParameter { get; set; }

        public string ElementName { get; set; }

        public object FallbackValue { get; set; }

        public BindingMode Mode { get; set; }

        [ConstructorArgument("path")]
        public string Path { get; set; }

        public string StringFormat { get; set; }

        public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
    }
}

我运行此应用程序时的异常:

System.Windows.Markup.XamlParseException 发生
HResult=-2146233087 Message= 'Set property 'System.Windows.Controls.Primitives.Selector.SelectedIndex' 引发异常。' 行号 '9' 和行位置 '19'。
来源=PresentationFramework LineNumber=9 LinePosition=19
StackTrace:在 System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader,布尔 skipJournaledProperties,对象 rootObject,XamlObjectWriterSettings 设置,Uri baseUri)处的 System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader,布尔型 skipJournaledProperties,对象 rootObject , XamlAccessLevel accessLevel, Uri baseUri) 在 System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream) 在 System.Windows.Application.LoadComponent(Object component, Uri resourceLocator) 在 WpfApplication2.MainWindow。 InitializeComponent() in c:\Users\Administrator\Documents\Visual Studio 2012\Projects\WpfApplication2\MainWindow.xaml:line 1 at WpfApplication2.MainWindow..ctor() in c:\Users\Administrator\Documents\Visual Studio 2012\Projects\WpfApplication2\MainWindow.xaml.cs:第 12 行
InnerException:System.ArgumentException HResult=-2147024809 消息= “System.Windows.Data.Binding”不是属性“SelectedIndex”的有效值. Source=WindowsBase StackTrace:在 System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp,对象值,PropertyMetadata 元数据,布尔 coerceWithDeferredReference,布尔 coerceWithCurrentValue,OperationType operationType,布尔 isInternal)在 System.Windows.DependencyObject.SetValue(DependencyProperty dp,对象值)在 System.Windows.Baml2006.WpfMemberInvoker.SetValue(Object instance, Object value) 在 MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(XamlMember member, Object obj, Object value) 在 MS.Internal.Xaml.Runtime.ClrObjectRuntime。 SetValue(Object inst, XamlMember property, Object value) InnerException:

4

1 回答 1

1

Ok, here is a proxy binding that works if anyone is interested.

Thank you @HighCore for pointing me in the right direction.

I use this binding proxy to set non-standard default values on the binding so I don't have to set them everywhere. This makes my xaml more compact, and allows me to have a central place where I 'style' my bindings.

These are the defaults:

  • NotifyOnValidationError = True,
  • ValidatesOnExceptions = True,
  • Mode = TwoWay,
  • UpdateSourceTrigger = LostFocus for 'Text' properties, otherwise PropertyChanged.

The UpdateSourceTrigger will change to PropertyChanged if the target property is not a Text property. (e.g. Combos or CheckBoxes)

If I don't need validation, I use the normal binding:

<TextBlock Text="{Binding FirstName}" />

If I need a normal two way binding, I know I might need validation so I use this binding proxy:

<TextBox Text="{i:ValidatedBinding FirstName}" />

That means I don't have to write out:

<TextBox Text="{Binding FirstName
    , Mode=TwoWay
    , UpdateSourceTrigger=LostFocus
    , NotifyOnValidationError=True
    , ValidatesOnExceptions=True" />

It works on both SelectedItem (reference types) and SelectedIndex (value types).

It will monitor the DataContext and maintain the binding.

If you find holes in the code, fix bugs or have any suggestions, please let me know.

using ITIS.Reflection /* you can replace this with System.Reflection */;
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace ITIS
{
    /// <summary>
    /// Creates a Binding with the following defaults:
    /// <para>- NotifyOnValidationError = True, </para>
    /// <para>- ValidatesOnExceptions = True, </para>
    /// <para>- Mode = TwoWay, </para> 
    /// <para>- UpdateSourceTrigger = LostFocus for 'Text' properties, otherwise PropertyChanged.</para>
    /// </summary>
#if !SILVERLIGHT
    [MarkupExtensionReturnType(typeof(Binding))]
#endif
    public sealed class ValidatedBinding : MarkupExtension
    {
        #region CONSTRUCTOR

        public ValidatedBinding(string path)
        {
            Mode = BindingMode.TwoWay;

            Path = path;

            /* possibly changed again in ProvideValue() */
            UpdateSourceTrigger = UpdateSourceTrigger.Default;
        }

        #endregion

        #region PROPERTIES

        public IValueConverter Converter { get; set; }

        public object ConverterParameter { get; set; }

        public string ElementName { get; set; }

        public object FallbackValue { get; set; }

        public BindingMode Mode { get; set; }

#if !SILVERLIGHT
        [ConstructorArgument("path")]
#endif
        public string Path { get; set; }

        public string StringFormat { get; set; }

        public UpdateSourceTrigger UpdateSourceTrigger { get; set; }

        #endregion

        #region FIELDS

        bool _bound;
        DependencyProperty _property;
        FrameworkElement _element;

        #endregion

        #region OPERATIONS

        void ClearBinding()
        {
            _element.ClearValue(_property);
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget Target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

            if (Target == null) {
                throw new InvalidOperationException(
                    "Cannot resolve the IProvideValueTarget. Are you binding to a property?");
            }

#if !SILVERLIGHT
            /* on text boxes, use a LostFocus update trigger */
            _property = Target.TargetProperty as DependencyProperty;

            if (_property != null) {
                if (_property.Name.StartsWith("Text")) {
                    UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
                }
                else {
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                }
            }
#endif

            _element = Target.TargetObject as FrameworkElement;

            if (_element != null) {

                _element.DataContextChanged += Element_DataContextChanged_SetBinding;

                if (_element.DataContext != null || !string.IsNullOrWhiteSpace(ElementName)) {

                    SetBinding();

                    /* can be replaced with normal reflection PropertyInfo.GetValue() */
                    return FastReflector.GetPropertyValue(/* object = */ _element.DataContext, /* property name = */ Path);
                }

                /* don't return null for value types */
                if (_property.PropertyType.IsValueType) {
                    return Activator.CreateInstance(_property.PropertyType);
                }

                return null;
            }

            return this;
        }

        void SetBinding()
        {
            _bound = true;

            Binding Binding = new Binding() {
                Path = new PropertyPath(this.Path),
                Converter = this.Converter,
                ConverterParameter = this.ConverterParameter,
                FallbackValue = this.FallbackValue,
                Mode = this.Mode,
                NotifyOnValidationError = true,
                StringFormat = this.StringFormat,
                ValidatesOnExceptions = true,
                UpdateSourceTrigger = this.UpdateSourceTrigger
            };

            /* only set when necessary to avoid a validation exception from the binding */
            if (_element.DataContext != null) { Binding.Source = _element.DataContext; }
            if (!string.IsNullOrWhiteSpace(ElementName)) { Binding.ElementName = ElementName; }

            _element.SetBinding(_property, Binding);
        }

        #endregion

        #region EVENT HANDLERS

        void Element_DataContextChanged_SetBinding(object sender, DependencyPropertyChangedEventArgs e)
        {
            /* cleanup the old binding */
            if (_bound) { ClearBinding(); }

            SetBinding();
        }

        #endregion
    }
}
于 2013-03-21T10:33:21.093 回答