漫谈Silverlight(3)扩展DependencyObject像Html那样使用Attribute
您是否会觉得Html中可以自由添加的属性很方便,对于每个Html元素我们可以方便的将自己的属性加在元素上,而对于Silverlight我们只能中规中矩的使用对象本身的属性,下面我们来尝试为DependencyObject添加类似的SetAttribute和GetAttribute方法。
为了完成这一目标,我们利用两个知识点:附加属性和扩展方法。大致的思路是这样的,为DependencyObject添加一个Attributes的附加集合属性,然后使用扩展方法控制集合的添加删除等操作,原理比较简单,下面是实现,首先是附加属性,构建AttributeService类为DependencyObject添加附加属性Attributes:
1: public class AttributeService2: {3: public static readonly DependencyProperty AttributesProperty = DependencyProperty.RegisterAttached(4: "Attributes",
5: typeof(AttributeDictionary),
6: typeof(DependencyObject),
7: new PropertyMetadata(null));8: public static void SetAttributes(DependencyObject obj, AttributeDictionary attrCollection)9: {10: obj.SetValue(AttributesProperty, attrCollection);11: }12: public static AttributeDictionary GetAttributes(DependencyObject obj)13: {14: return (AttributeDictionary)obj.GetValue(AttributesProperty);
15: }16: }
这里的AttributeDictionary是一个空实现:
1: public class AttributeDictionary : Dictionary<string, object>2: {3: }
为什么我没有直接用Dictionary来实现?其实没什么,只是为了在Xaml中使用更加方便,不过这里要顺便说说扩展属性的默认值,在Silverlight中大多数的扩展属性都是值类型,比如最常见的Grid.Column和Grid.Row等等,Silverlight为我们存储扩展属性的默认值,如果是值类型,对于每个使用扩展属性的对象来说都有各自的值,即使这些值是一样的,但是如果扩展属性是引用类型就不一样了,所有的对象将共享同一个默认值,除非使用SetValue设置成新的值,但是我们在GetValue获得这个扩展属性的值的时候并没有办法知道这个值是默认值还是在跟其他对象共享这个值,这一点尤其需要注意。下面让我们来看看扩展的实现:
1: public static class DependencyObjectExtension2: {3: public static void SetAttribute(this DependencyObject obj, string name, object value)4: {5: var attrCollection = AttributeService.GetAttributes(obj);6: if (attrCollection == null)7: {8: attrCollection = new AttributeDictionary();
9: AttributeService.SetAttributes(obj, attrCollection);10: }11: if (value == null)12: {13: if (attrCollection.ContainsKey(name))
14: attrCollection.Remove(name);15: }16: else
17: {18: if (attrCollection.ContainsKey(name))
19: attrCollection[name] = value;
20: else
21: attrCollection.Add(name, value);
22: }23: }24: public static object GetAttribute(this DependencyObject obj, string name)25: {26: var attrCollection = AttributeService.GetAttributes(obj);27: if (attrCollection == null)28: return null;29: if (attrCollection.ContainsKey(name))
30: return attrCollection[name];
31: return null;32: }33: public static bool HasAttribute(this DependencyObject obj, string name)34: {35: var attrCollection = AttributeService.GetAttributes(obj);36: if (attrCollection == null)37: return false;38: return attrCollection.ContainsKey(name);
39: }40: }
上面的代码扩展了DependencyObject为其添加了三个方法,在这三个方法中利用AttributeService的GetAttributes方法获得AttributeDictionary,注意GetAttribute方法中的实现,当获得的AttributeDictionary为null时用AttributeService的SetAttributes方法为传入的DependencyObject分配一个新的AttributeDictionary,如果我们在注册扩展属性的时候指定的默认值不是null,而是一个空的AttributeDictionary,那么所有被扩展的DependencyObject将使用同一个集合,显然我们不想要这样的结果。下面来看看如何使用,先看C#中如何使用:
1: private void UserControl_Loaded(object sender, RoutedEventArgs e)2: {3: this.LayoutRoot.SetAttribute("attrName1", "In C#");4: }
比较简单,再来看看在Xaml中如何使用:
1: <Grid x:Name="LayoutRoot" Background="White">2: <local:AttributeService.Attributes>3: <local:AttributeDictionary>4: <sys:String x:Key="attrName2">In Xaml</sys:String>5: </local:AttributeDictionary>6: </local:AttributeService.Attributes>7: </Grid>8:
虽然我们无法像Html那样这样写:
1: <Grid x:Name="LayoutRoot" Background="White" attrName2="In Xaml">
但是上面的写法也不算很丑陋了,并且似乎在Silverlight的Xaml中无法直接使用泛型,所以我用了AttributeDictionary的封装,如果高人知道如何在Xaml中定义泛型,请指点。我们还是回到Xaml中的代码,我之所以可以在AttributeDictionary下使用x:Key得益于Silverlight4,在之前的版本中,只有ResourceDictionary中才能使用x:Key,这样看起来是否还算舒服呢,是不是有点像FrameworkElement.Resources属性,最后再奉上一个ValueConverter:
1: public class AttributeValueConverter : IValueConverter2: {3: public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)4: {5: var attrName = System.Convert.ToString(parameter);6: if (value is DependencyObject)7: {8: var obj = value as DependencyObject;9: return obj.GetAttribute(attrName);
10: }11: return null;12: }13: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)14: {15: throw new NotImplementedException();16: }17: }
很遗憾,没办法双向绑定,大家可以根据这个思路继续扩展下去,使用如下:
1: <Grid x:Name="LayoutRoot" Background="White">2: <Grid.Resources>3: <local:AttributeValueConverter x:Key="AttrConverter"></local:AttributeValueConverter>4: </Grid.Resources>5: <local:AttributeService.Attributes>6: <local:AttributeDictionary>7: <sys:String x:Key="attrName2">In Xaml</sys:String>8: <SolidColorBrush Color="Blue" x:Key="Blue"></SolidColorBrush>9: </local:AttributeDictionary>10: </local:AttributeService.Attributes>11: <Button Foreground="{Binding ElementName=LayoutRoot, ConverterParameter=Blue, Converter={StaticResource AttrConverter}}"12: Click="Button_Click" Content="Hello Silverlight"></Button>13: </Grid>
Button的前景色被设置成了LayoutRoot的名为Blue的Attribute所指向的SolidColorBrush。希望本文对您有帮助。
转载请遵循此协议:署名 - 非商业用途 - 保持一致