开发者

How to keep a local value from being set when a binding fails (so inherited values will propagate)

Consider the following scenario:

I want to bind the TextElement.FontWeight property to an xml attribute. The xml looks somewhat like this and has arbitrary depth.

<text font-weight="bold">
    bold text here
    <inlinetext>more bold text</inlinetext>
    even more bold text
</text>

I use hierarchical templating to display the text, no problem there, but having a Setter in the template style like:

<Setter Property="TextElement.FontWeight" Value="{Binding XPath=@font-weight}"/>

sets the fontweight correctly on the first level, but overwrites the second level with null (as the binding can't find the xpath) which reverts to Fontweight normal.

I tried all sorts of things here but nothing quite seems to work.

e.g. i used a converter to return UnsetValue, which didn't work.

I'm currently trying with:

<Setter Property="custom:AttributeInserter.Wrapper" Value="{custom:AttributeInserter Property=TextElement.FontWeight, Binding={Binding XPath=@font-weight}}"/>

Codebehind:

public static class AttributeInserter
{
    public static AttributeInserterExtension GetWrapper(DependencyObject obj)
    {
        return (AttributeInserterExtension)obj.GetValue(WrapperProperty);
    }
    public static void SetWrapper(DependencyObject obj, AttributeInserterExtension value)
    {
        obj.SetValue(WrapperProperty, value);
    }
    // Using a DependencyProperty as the backing store for Wrapper.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty WrapperProperty =
        DependencyProperty.RegisterAttached("Wrapper", typeof(AttributeInserterExtension), typeof(AttributeInserter), new UIPropertyMetadata(pcc));

    static void pcc(DependencyObject o,DependencyPropertyChangedEventArgs e)
    {
        var n=e.NewValue as AttributeInserterExtension;
        var c = o as FrameworkElement;
        if (n == null || c==null || n.Property==null || n.Binding==null)
            return;

        var bex = c.SetBinding(n.Property, n.Binding);
        bex.UpdateTarget();
        if (bex.Status == BindingStatus.Upd开发者_开发百科ateTargetError)
            c.ClearValue(n.Property);
    }

}

public class AttributeInserterExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public DependencyProperty Property { get; set; }
    public Binding Binding { get; set; }
}

which kinda works, but can't track changes of the property

Any ideas? Any links?

thx for the help


You're on the right track. Using an attached property is the way to go.

Simplest solution

If you're willing to code for each inherited property (there aren't very many of them) it can be much simpler:

<Setter Property="my:OnlyIfSet.FontWeight" Value="{Binding XPath=@font-weight}"/>

with code

public class OnlyIfSet : DependencyObject
{
  public static FontWeight GetFontWeight(DependencyObject obj) { return (FontWeight)obj.GetValue(FontWeightProperty); }
  public static void SetFontWeight(DependencyObject obj, FontWeight value) { obj.SetValue(FontWeightProperty, value); }
  public static readonly DependencyProperty FontWeightProperty = DependencyProperty.RegisterAttached("FontWeight", typeof(FontWeight), typeof(Object), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
      {
        if(e.NewValue!=null)
          obj.SetValue(TextElement.FontWeightProperty, e.NewValue);
        else
          obj.ClearValue(TextElement.FontWeightProperty);
      }
  });
}

Generalizing for multiple properties

Generalizing this can be done in several ways. One is to create a generalized property registering method, but still use multiple attached properties:

public class OnlyIfSet
{
  static DependencyProperty CreateMap(DependencyProperty prop)
  {
    return DependencyProperty.RegisterAttached(
      prop.Name, prop.PropertyType, typeof(OnlyIfSet), new PropertyMetadata
      {
        PropertyChangedCallback = (obj, e) =>
        {
          if(e.NewValue!=null)
            obj.SetValue(prop, e.NewValue);
          else
          obj.ClearValue(prop);
        }
      });
  }

  public static FontWeight GetFontWeight(DependencyObject obj) { return (FontWeight)obj.GetValue(FontWeightProperty); }
  public static void SetFontWeight(DependencyObject obj, FontWeight value) { obj.SetValue(FontWeightProperty, value); }
  public static readonly DependencyProperty FontWeightProperty =
    CreateMap(TextElement.FontWeightProperty);

  public static double GetFontSize(DependencyObject obj) { return (double)obj.GetValue(FontSizeProperty); }
  public static void SetFontSize(DependencyObject obj, double value) { obj.SetValue(FontSizeProperty, value); }
  public static readonly DependencyProperty FontSizeProperty =
    CreateMap(TextElement.FontSizeProperty);

  ...    
}

Allowing arbitrary properties

Another approach is to use two attached properties:

<Setter Property="my:ConditionalSetter.Property" Value="FontWeight" />
<Setter Property="my:ConditionalSetter.Value" Value="{Binding XPath=@font-weight}"/>

with code

public class ConditionalSetter : DependencyObject
{
  public string GetProperty( ... // Attached property
  public void SetProperty( ...
  public static readonly DependencyProperty PropertyProperty = ...
  {
    PropertyChangedCallback = Update,
  });

  public object GetValue( ... // Attached property
  public void SetValue( ...
  public static readonly DependencyProperty ValueProperty = ...
  {
    PropertyChangedCallback = Update,
  });

  void Update(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  {
    var property = GetProperty(obj);
    var value = GetValue(obj);
    if(property==null) return;

    var prop = DependencyPropertyDescriptor.FromName(property, obj.GetType(), typeof(object)).DependencyProperty;
    if(prop==null) return;

    if(value!=null)
      obj.SetValue(prop, value);
    else
      obj.ClearValue(prop);
  }
}


if anyone comes across this, my final solution was this:

<DataTrigger Binding="{Binding XPath=@font-weight,Converter={converters:IsNullConverter}}" Value="false">
    <Setter Property="TextElement.FontWeight" Value="{Binding XPath=@font-weight}" />
</DataTrigger>

with IsNullConverter being a simple Valueconverter with this body:

return value == null;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜