在部分构造的对象上绑定错误

时间:2016-07-08 15:42:35

标签: wpf data-binding

我创建了一些附加属性以允许间接绑定(我指的是绑定到名称由附加属性赋予的值,而不是在XAML中指定为文字)。

某些AP是可选的(例如,一个覆盖原本会生效的DataContext)并且这意味着我在尝试创建绑定时,并非所有AP都已生效设置(因为PropertyChangedCallback我不知道是否会设置其他人。)

结果是绑定可以多次创建,有时不成功,这会导致绑定错误,因为缺少更好的单词,所以会出现不愉快的情况。

是否有一种方法可以在分配元素的所有AP之前抑制绑定错误,或者在PropertyChangedCallback内解决是否会包含任何更多的包含AP的内容在这个元素上设置?

修改

我被要求提供代码。我希望没有这样做(因为它让问题变得相当长!),但这是我要问的课程:

public static class BindingIndirector
{
    public static string GetBindingSource(DependencyObject dob)
    {
        return (string)dob.GetValue(BindingSourceProperty);
    }

    public static void SetBindingSource(DependencyObject dob, string value)
    {
        dob.SetValue(BindingSourceProperty, value);
    }

    /// <summary>
    /// The "source" to be set on the binding.
    /// Must be specified.
    /// </summary>
    public static readonly DependencyProperty BindingSourceProperty =
        DependencyProperty.RegisterAttached(
            "BindingSource",
            typeof(String),
            typeof(BindingIndirector),
            new PropertyMetadata(null, BindingChanged));


    public static object GetBindingSourceContext(DependencyObject dob)
    {
        return dob.GetValue(BindingSourceContextProperty);
    }

    public static void SetBindingSourceContext(DependencyObject dob, object value)
    {
        dob.SetValue(BindingSourceContextProperty, value);
    }

    /// <summary>
    /// A DataContext type property. This overrides the inherited DataContext that would otherwise be
    /// used for the binding.
    /// Optional.
    /// </summary>
    public static readonly DependencyProperty BindingSourceContextProperty =
        DependencyProperty.RegisterAttached(
            "BindingSourceContext",
            typeof(object),
            typeof(BindingIndirector),
            new PropertyMetadata(null, BindingChanged));


    public static string GetBindingTarget(DependencyObject dob)
    {
        return (string)dob.GetValue(BindingTargetProperty);
    }

    public static void SetBindingTarget(DependencyObject dob, string value)
    {
        dob.SetValue(BindingTargetProperty, value);
    }

    /// <summary>
    /// The binding target property.
    /// Optional (defaults to "Content" if not specified
    /// </summary>
    public static readonly DependencyProperty BindingTargetProperty =
        DependencyProperty.RegisterAttached(
            "BindingTarget",
            typeof(String),
            typeof(BindingIndirector),
            new PropertyMetadata("Content", BindingChanged));

    private static void BindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(e.Property == BindingSourceContextProperty || e.NewValue is string))
            throw new ArgumentException("Property can only be set to string values", e.Property.ToString());

        // Check rules for attempting to set the binding are met
        string source = GetBindingSource(d) as string;
        string target = GetBindingTarget(d) as string;
        object context = GetBindingSourceContext(d);
        if (source == null)     // Source needs to be set - don't interfere with binding if it isn't
            return;

        // Clear any existing binding
        var originalName = e.Property ==
            BindingSourceProperty ?
                target :
                e.OldValue as string;

        if (originalName != null)
        {
            var existingDescriptor =
                DependencyPropertyDescriptor.FromName(
                    originalName,
                    d.GetType(),
                    d.GetType());

            if (existingDescriptor != null)
                d.ClearValue(existingDescriptor.DependencyProperty);
        }

        // Create and assign new binding
        var targetDescriptor = 
                DependencyPropertyDescriptor.FromName(
                    target,
                    d.GetType(),
                    d.GetType());

        if (targetDescriptor != null)   // don't interfere with binding if target invalid
        {
            Binding newBinding = new Binding(source) { Mode = BindingMode.TwoWay };
            if (context != null)        // Will fall back to DataContext of element in this case
                newBinding.Source = context;

            BindingOperations.SetBinding(d, targetDescriptor.DependencyProperty, newBinding);
        }
    }
}

这个静态类创建了3个附加属性,并且还包含一个方法,&#34; BindingChanged()&#34;这是所有三个AP的propertyChangedCallback。如果已经给出了足够的信息来尝试创建绑定,则会这样做,丢弃先前用于创建AP的任何先前绑定。

它没有做什么(可能是一个解决方案)是找出绑定是先成功还是捕获绑定引擎产生的任何错误(你能这样做吗?)。在不抑制应该显示的绑定错误方面可能存在挑战(例如,因为最终用户提供了duff信息)。

以下是一个用例的示例:

<UserControl x:Class="UtilityControls.ListEditor"
             ...>

    <Grid x:Name="ControlContainer">
        <Grid.DataContext>
            <local:LeViewModel x:Name="vm" />
        </Grid.DataContext>

        <ListBox
        x:Name="EditingArea"
        ItemsSource="{Binding ColumnCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:ListEditor}}}"
        >
            <ListBox.Resources>

                <DataTemplate x:Key="TextTemplate">
                    <StackPanel>
                        <TextBlock Text="{Binding DisplayName}" />
                        <TextBox 
                            local:BindingIndirector.BindingSourceContext="{Binding DataContext.CurrentEditing, ElementName=ControlContainer}"
                            local:BindingIndirector.BindingSource="{Binding PathName}"
                            local:BindingIndirector.BindingTarget="Text"
                            />
                    </StackPanel>
                </DataTemplate>

                <DataTemplate x:Key="PickListTemplate" .. />
                <DataTemplate x:Key="BooleanTemplate" ... />

            </ListBox.Resources>

            <ListBox.ItemTemplateSelector>
                <local:DataTypeSelector
                    TextTemplate="{StaticResource TextTemplate}"
                    PickListTemplate="{StaticResource PickListTemplate}"
                    BooleanTemplate="{StaticResource BooleanTemplate}"
                    />
            </ListBox.ItemTemplateSelector>

        </ListBox>
    </Grid>
</UserControl>

&#34; CurrentEditing&#34;是各种ListBox项正在编辑的ViewModel对象(来自ListBox的每个ColumnCollection项生成对象的不同属性的编辑器。)

希望AP的目的(在#34; TextTemplate&#34;中使用)是不言自明的(它们为Text的{​​{1}}属性创建了一个绑定),但请注意虽然这里有三个都是必需的,但我希望至少TextBox是可选的...这会产生问题:BindingSourceContext不知道将设置多少个AP,所以它不知道何时创建绑定。因此,如果有足够的信息,每次更改属性时都会有一个go。如果还有更多,那么就会产生绑定错误。

1 个答案:

答案 0 :(得分:2)

您可以在Binding上使用FallbackValue来抑制这些异常。例如:

<Grid Visibility="{Binding SomeProperty, FallbackValue=Collapsed}"/>