WPF学习:4.类型转换和标记扩展
在上一章,主要介绍了Border和Brush,这一章主要介绍下类型转换和标记扩展。相关代码链接如下:
https://files.cnblogs.com/keylei203/4.WPFSampleDemo.zip
Type Converter
在XAML中,所有的对象都是字符串,XAML解析器通过类型转换器跨越字符和非字符的鸿沟,类型转换器就是将这些字符串转换为相应的CLR对象。
所有类型转换器都是派生自TypeConverter,派生的类有100多个,比较常用的比如BrushConverter,就可以实现从字符串转换成相应的画刷,又如Margin,Margin=10,20,0,30,解析器会自动按照左,上,右,下的顺序将数据转换为宽度数据。
TypeConverter都4个重要方法是CanConvertTo,CanConvertFrom,ConvertTo和ConvertFrom。
CanConvertTo:检测CLR对象是否能转换成相应的字符串。
CanConvertFrom:检测能否从字符串转换成相应的CLR对象。
ConvertTo:将CLR对象转换成相应的字符串。
ConvertFrom:从字符串转换成相应的CLR对象。
XAML解析器解析任何属性值时都包含两部分信息:Value Type-决定了字符串转化后的类型。Actual Value-属性值大小。解析器通过两个步骤来查找类型转换器:
(1)检查属性声明查找TypeConvert特性,如窗口的Width和Height属性(派生自FrameworkElement)前面都会声明一个类型转换器。下面代码通过Reflector查看到的FrameworkElement的源代码片段:
1 public class FrameworkElement.... 2 { 3 [......TypeConverter(typeof(LengthConverter))] 4 public double Height{get;set;} 5 [......TypeConverter(typeof(LengthConverter))] 6 public double Width{get;set;} 7 }
(2)如果属性声明中没有TypeConverter特征,XAML解析器会检查对应的数据类型的类的声明。如按钮Background属性声明是Brush,并在Brush类头部就声明了一个BrushConverter转换器,这样在设置Background属性时XAML解析器会自动应用该转换器。下面代码为用过Reflector查看到的Brush片段。
1 [......TypeConverter(typeof(BrushConverter))...... 2 public abstract class Brush:Animatable
Custom TypeConverter
建立一个CustomTypeConverter必须建立一个新类,在我的例子中,我建立了一个具有Latitude和Longitude属性的类,这个类实现地理位置。
1 [global::System.ComponentModel.TypeConverter(typeof(GeoPointConverter))] 2 public class GeoPointItem 3 { 4 public double Latitude { get; set; } 5 public double Longitude { get; set; } 6 7 public GeoPointItem() 8 { 9 } 10 11 public GeoPointItem(double lat, double lon) 12 { 13 this.Latitude = lat; 14 this.Longitude = lon; 15 } 16 17 public static GeoPointItem Parse(string data) 18 { 19 if (string.IsNullOrEmpty(data)) return new GeoPointItem(); 20 21 string[] items = data.Split(','); 22 if (items.Count() != 2) 23 throw new FormatException("GeoPoint should have both latitude 24 and longitude"); 25 26 double lat, lon; 27 try 28 { 29 lat = Convert.ToDouble(items[0]); 30 } 31 catch (Exception ex) { 32 throw new FormatException("Latitude value cannot be converted", ex); 33 } 34 35 try 36 { 37 lon = Convert.ToDouble(items[1]); 38 } 39 catch (Exception ex) { 40 throw new FormatException("Longitude value cannot be converted", ex); 41 } 42 43 return new GeoPointItem(lat, lon); 44 } 45 46 public override string ToString() 47 { 48 return string.Format("{0},{1}", this.Latitude, this.Longitude); 49 } 50 }
上面的类具有Latitude和Longitude两个double型属性,我重写了ToString(),这对于得到一个完整的字符串来说是非常重要的,Parse方法能够解析字符串格式到地理坐标。
接下俩就是为这个类写一个TypeConverter,将实现4个方法,关键的是ConvertFrom方法,通过GeoPointItem静态方法Parse将字符串转化为正确的类型。
1 public class GeoPointConverter : global::System.ComponentModel.TypeConverter 2 { 3 4 //should return true if sourcetype is string 5 public override bool CanConvertFrom( 6 System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 7 { 8 if (sourceType is string) 9 return true; 10 return base.CanConvertFrom(context, sourceType); 11 } 12 //should return true when destinationtype if GeopointItem 13 public override bool CanConvertTo( 14 System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 15 { 16 if (destinationType is string) 17 return true; 18 19 return base.CanConvertTo(context, destinationType); 20 } 21 //Actual convertion from string to GeoPointItem 22 public override object ConvertFrom( 23 System.ComponentModel.ITypeDescriptorContext context, 24 System.Globalization.CultureInfo culture, object value) 25 { 26 if (value is string) 27 { 28 try 29 { 30 return GeoPointItem.Parse(value as string); 31 } 32 catch (Exception ex) 33 { 34 throw new Exception(string.Format( 35 "Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), ex.Message), ex); 36 } 37 } 38 39 return base.ConvertFrom(context, culture, value); 40 } 41 42 //Actual convertion from GeoPointItem to string 43 public override object ConvertTo( 44 System.ComponentModel.ITypeDescriptorContext context, 45 System.Globalization.CultureInfo culture, object value, Type destinationType) 46 { 47 if(destinationType == null) 48 throw new ArgumentNullException("destinationType"); 49 50 GeoPointItem gpoint = value as GeoPointItem; 51 52 if(gpoint != null) 53 if (this.CanConvertTo(context, destinationType)) 54 return gpoint.ToString(); 55 56 return base.ConvertTo(context, culture, value, destinationType); 57 } 58 }
接下去是使用这个类型转化器的时候了,建立一个用户控件,添加一个GeoPoint属性:
1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition/> 4 <RowDefinition/> 5 </Grid.RowDefinitions> 6 <Grid.ColumnDefinitions> 7 <ColumnDefinition/> 8 <ColumnDefinition/> 9 </Grid.ColumnDefinitions> 10 <TextBlock Text="Latitude" Grid.Row="0" Grid.Column="0"></TextBlock> 11 <TextBox x:Name="txtlat" MinWidth="40" Grid.Row="0" Grid.Column="1" 12 TextChanged="txtlat_TextChanged"/> 13 <TextBlock Text="Longitude" Grid.Row="1" Grid.Column="0"></TextBlock> 14 <TextBox x:Name="txtlon" MinWidth="40" Grid.Row="1" Grid.Column="1" 15 TextChanged="txtlon_TextChanged"/> 16 </Grid>
控件拥有两个Textboxes显示Latutude和Longitude,当textBox内容发生修改时,GeopointItem值也会相应更改。
1 public partial class GeoPoint : UserControl 2 { 3 public static readonly DependencyProperty GeoPointValueProperty = 4 DependencyProperty.Register("GeoPointValue", typeof(GeoPointItem), 5 typeof(GeoPoint), new PropertyMetadata(new GeoPointItem(0.0, 0.0))); 6 public GeoPoint() 7 { 8 InitializeComponent(); 9 } 10 11 public GeoPointItem GeoPointValue 12 { 13 get 14 { 15 return this.GetValue(GeoPointValueProperty) as GeoPointItem; 16 } 17 set 18 { 19 this.SetValue(GeoPointValueProperty, value); 20 } 21 } 22 23 private void txtlat_TextChanged(object sender, TextChangedEventArgs e) 24 { 25 GeoPointItem item = this.GeoPointValue; 26 27 item.Latitude = Convert.ToDouble(txtlat.Text); 28 this.GeoPointValue = item; 29 } 30 31 private void txtlon_TextChanged(object sender, TextChangedEventArgs e) 32 { 33 GeoPointItem item = this.GeoPointValue; 34 35 item.Longitude = Convert.ToDouble(txtlon.Text); 36 this.GeoPointValue = item; 37 } 38 39 private void UserControl_Loaded(object sender, RoutedEventArgs e) 40 { 41 GeoPointItem item = this.GeoPointValue; 42 this.txtlat.Text = item.Latitude.ToString(); 43 this.txtlon.Text = item.Longitude.ToString(); 44 45 } 46 }
对于窗体,需要添加这个自定义控件,设置它的初值。
1 <converter:GeoPoint x:Name="cGeoPoint" GeoPointValue="60.5,20.5" />
Markup Extension
MarkupExtension提供一个XAML属性的灵活性,XAML中只要属性值被{}括起,XAML解析器就会认为这是一个扩展标记,而不是一个普通的字符串。
Extension
在System.Windows.Markup命名空间内已经有了一些标记扩展:
NullExtention
是XAML提供的一种标记扩展,表示一个空值,这可能是所有标记扩展中最简单的了。
Content = "{x:Null}"
ArrayExtension
用来创建一个数组集合。
Values = {x:Array Type=sys:String}
StaticExtension
返回一个静态字段和引用
Text="{x:Static Member=local:MyClass.StaticProperty}"
当你定义一个MyClass类的静态属性时,你可以使用它来将属性自动的赋给Text。
TypeExtension
TargetType="{x:Type Button}"
获得一个button对象的类型
Reference
Text="{x:Reference Name=Myobject}"
引用一个在XAML中声明的对象,.NET4.0特性
StaticResourceExtension
1 <Grid.Resources> 2 3 <Color x:Key="rKeyBlack">Black</Color> 4 <SolidColorBrush Color="{StaticResource rKeyBlack}" x:Key="rKeyBlackBrush"/> 5 6 7 </Grid.Resources> 8 9 <TextBlock Background="{StaticResource ResourceKey=rKeyBlackBrush}" />
DynamicResourceExtension
<TextBlock Background="{DynamicResource ResourceKey=rKeyBlackBrush}" />
似水无痕:http://www.cnblogs.com/keylei203/