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;
精彩评论