Styel在英文中解释为”样式“,在Web开发中,css为层叠样式表,自从.net3.0推出WPF以来,WPF也有样式一说,通过设置样式,使其WPF控件外观更加美化同时减少了大量的复杂属性的设置。
在WPF中,设置外观样式我们有很多种方式,比如通过设置控件的属性来控制控件的外观样式;或者通过在每一个控件中分别设置Style;或者通过在整个Window.Resource中设置Style,又或者在App.xaml的Application.Resource设置Style。
在此我们就不讨论第一种方式设置控件的外观了,因为这不涉及到Style的使用。那么后三种设置样式来控制控件的外观有什么区别呢?那么我们来分别讨论吧!
第一,通过在每一个控件中分别设置Style来控制控件的外观,示例代码如下:
<Button Content="Button" Height="23" Name="button3" Width="75">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Black" />
</Style>
</Button.Style>
</Button>
以上样式的设置只正对当前的Button有效,与其他同种类型的控件无关。
第二,通过在Window.Resource中设置Style来控制控件的外观,示例代码如下:
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="LightBlue" />
</Style>
</Window.Resources>
以上样式的设置,针对整个Window的所有Button有效(只要没有单独的对Button设置),这种方法呢,相对于第一种来说减少了代码量。同时修改起来出错的可能性较小!
第三,通过在App.xaml中的Application.Resource中设置Style来控制控件的外观,示例代码如下:
<Application.Resource> <Style TargetType="Button"> <Setter Property="FontFamily" Value="MS Reference Sans Serif" /> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="White" Offset="0"/> <GradientStop Color="SkyBlue" Offset="0.2"/> <GradientStop Color="SkyBlue" Offset="0.8"/> <GradientStop Color="White" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> </Style> </Application.Resource>
以上样式的设置,针对整个Application的所有Button有效(只要在Window.Resource或者独立的Button中没有分别设置),这种方法来设置控件的外观样式呢,相对于前两种来说,代码量有大幅的增加,同时呢,在一个应用程序中,往往同种类型的控件的很多属性都是相同的,我们在Applicaiton.Resource进行全局设置,使其维护更加方便!
好了,以上是我对WPF中Style的理解,希望在我总结的同时,能够给同行们提供帮助,如果发现错误,请积极指正,谢谢!
如果只是单纯的让ListBox可以横向配列,这样很简单,只需要更改ListBox的ItemsPanel模板就可以,例如: <ListBox> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel Orientation=”Horizontal” IsItemsHost=”True”/> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> 但是这样的修改,ListBox只能横向排列,不会根据宽度自动换行,如果想要横向排列的ListBox支持根据宽度自动换行的话,需要这样写: <ListBox.Template> <ControlTemplate TargetType=”{x:Type ListBox}”> <ScrollViewer HorizontalScrollBarVisibility=”Disabled” VerticalScrollBarVisibility=”Auto”> <WrapPanel Orientation=”Horizontal” IsItemsHost=”True” ScrollViewer.CanContentScroll=”True”/> </ScrollViewer> </ControlTemplate> </ListBox.Template> <Style TargetType="{x:Type ListBox}"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <WrapPanel Orientation="Horizontal" Margin="2" Background="LightGray"/> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="ListBoxItem"> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Trigger.Setters> <Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderBrush" Value="Red"/> </Trigger.Setters> </Trigger> </Style.Triggers> </Style> </Setter.Value> </Setter> </Style>
string str="123abc456"; int i=3; 1 取字符串的前i个字符 str=str.Substring(0,i); // or str=str.Remove(i,str.Length-i); 2 去掉字符串的前i个字符: str=str.Remove(0,i); // or str=str.Substring(i); 3 从右边开始取i个字符: str=str.Substring(str.Length-i); // or str=str.Remove(0,str.Length-i); 4 从右边开始去掉i个字符: str=str.Substring(0,str.Length-i); // or str=str.Remove(str.Length-i,i); 5 判断字符串中是否有"abc" 有则去掉之 using System.Text.RegularExpressions; string str = "123abc456"; string a="abc"; Regex r = new Regex(a); Match m = r.Match(str); if (m.Success) { //下面两个取一种即可。 str=str.Replace(a,""); Response.Write(str); string str1,str2; str1=str.Substring(0,m.Index); str2=str.Substring(m.Index+a.Length,str.Length-a.Length-m.Index); Response.Write(str1+str2); } 6 如果字符串中有"abc"则替换成"ABC" str=str.Replace("abc","ABC"); ************************************************ string str="adcdef"; int indexStart = str.IndexOf("d"); int endIndex =str.IndexOf("e"); string toStr = str.SubString(indexStart,endIndex-indexStart); c#截取字符串最后一个字符的问题! str1.Substring(str1.LastIndexOf(",")+1); C# 截取字符串最后一个字符 k = k.Substring(k.Length-1, 1);
<Grid> <Canvas x:Name="LayoutRoot"> <Image Cursor="Hand" MouseLeftButtonDown="imgLogo1_MouseLeftButtonDown" MouseEnter="imgLogo1_MouseEnter" MouseLeave="imgLogo1_MouseLeave" Canvas.ZIndex="1" x:Name="imgLogo1" Canvas.Left="100" Canvas.Top="60" Height="100" Source="Image/Picture.jpg"> <Image.RenderTransform> <ScaleTransform x:Name="LogoScale" CenterX="90" CenterY="96"> </ScaleTransform> </Image.RenderTransform> </Image> </Canvas> </Grid> public partial class Window8 : Window { public Window8() { InitializeComponent(); timer = new System.Windows.Threading.DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(50); timer.Tick += new EventHandler(timer_Tick); } private System.Windows.Threading.DispatcherTimer timer; private ScaleDirection scaleDirection ; void timer_Tick(object sender, EventArgs e) { AdjustScale(scaleDirection, LogoScale); } void AdjustScale(ScaleDirection scaleDirection, ScaleTransform scale) { if (scaleDirection == ScaleDirection.Down) { if (scale.ScaleX < 1.3) { scale.ScaleX += 0.05; scale.ScaleY += 0.05; } else timer.Stop(); } else { if (scale.ScaleX > 1.0) { scale.ScaleX -= 0.05; scale.ScaleY -= 0.05; } else timer.Stop(); } } enum ScaleDirection { Up, Down } private void imgLogo1_MouseEnter(object sender, MouseEventArgs e) { scaleDirection = ScaleDirection.Down; timer.Start(); } private void imgLogo1_MouseLeave(object sender, MouseEventArgs e) { scaleDirection = ScaleDirection.Up; timer.Start(); } private void imgLogo1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { MessageBox.Show("test"); } }
<Window.Triggers> <EventTrigger SourceName="CrazyButton" RoutedEvent="Window.Loaded"> <!--<EventTrigger.Actions>--> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="image2" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:5" AutoReverse="False" RepeatBehavior="Forever"> </DoubleAnimation> </Storyboard> </BeginStoryboard> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="image2" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:5" AutoReverse="False" RepeatBehavior="Forever"/> </Storyboard> </BeginStoryboard> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="image1" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:5" AutoReverse="False" RepeatBehavior="Forever"/> </Storyboard> </BeginStoryboard> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="image1" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:5" AutoReverse="False" RepeatBehavior="Forever"> </DoubleAnimation> </Storyboard> </BeginStoryboard> <!--</EventTrigger.Actions>--> </EventTrigger> </Window.Triggers>
1.建类,必须继承IValueConverter接口,在命名空间System.Windows.Data下 class BoolToContentConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { bool temp = bool.Parse(value.ToString()); if (temp) return "暂 停"; else return "开 始"; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } 2.在资源文件xaml里加入 转换器,converters为命名空间定义 比如xmlns:converters="clr-namespace:Converters" <converters:BoolToContentConverter x:Key="BoolToContentConverter"/> 3.使用转换器 <Button Content="{Binding Path=isDownloading, Converter={StaticResource BoolToContentConverter}}" > </Button> 4,带参数的Converter <TextBox Text="{Binding XXXX,Converter={StaticResource UsrConverter}, ConverterParameter={StaticResource userList }}" />
Converter 里对参数的使用(摘抄)
public class UserNameConverter : IValueConverter { public object IValueConverter.Convert(object value, Type targetType,object parameter, CultureInfo culture) { List<User> usrs = parameter as List<User>; ... } }
如何学好wpf(摘抄)
用了三年多的WPF,开发了很多个WPF的项目,就我自己的经验,谈一谈如何学好WPF,当然,抛砖引玉,如果您有什么建议也希望不吝赐教。
WPF,全名是Windows Presentation Foundation,是微软在.net3.0 WinFX中提出的。WPF是对Direct3D的托管封装,它的图形表现依赖于显卡。当然,作为一种更高层次的封装,对于硬件本身不支持的一些图形特效的硬实现,WPF提供了利用CPU进行计算的软实现,用以简化开发人员的工作。
简单的介绍了一下WPF,这方面的资料也有很多。作于微软力推的技术,整个推行也符合微软一贯的风格。简单,易用,强大,外加几个创新概念的噱头。
噱头一:声明式编程。从理论上讲,这个不算什么创新。Web界面声明式开发早已如火如荼了,这种界面层的声明式开发也是大势所趋。为了适应声明式编程,微软推出了XAML,一种扩展的XML语言,并且在.NET 3.0中加入了XAML的编译器和运行时解析器。XAML加上IDE强大的智能感知,确实大大方便了界面的描述,这点是值得肯定的。
噱头二:紧接着,微软借XAML描绘了一副更为美好的图片,界面设计者和代码开发者可以并行的工作,两者通过XAML进行交互,实现设计和实现的分离。不得不说,这个想法非常打动人心。以往设计人员大多是通过photoshop编辑出来的图片来和开发人员进行交互的,需要开发人员根据图片的样式来进行转换,以生成实际的效果。既然有了这层转换,所以最终出来的效果和设计时总会有偏差,所以很多时候开发人员不得不忍受设计人员的抱怨。WPF的出现给开发人员看到了一线曙光,我只负责逻辑代码,UI你自己去搞,一结合就可以了,不错。可实际开发中,这里又出现了问题,UI的XAML部分能完全丢给设计人员么?
这个话题展开可能有点长,微软提供了Expression Studio套装来支持用工具生成XAML。那么这套工具是否能够满足设计人员的需要呢?经过和很多设计人员和开发人员的配合,最常听到的话类似于这样。“这个没有Photoshop好用,会限制我的灵感”, “他们生成的XAML太糟糕了...”。确实,在同一项目中,设计人员使用Blend进行设计,开发人员用VS来开发代码逻辑,这个想法稍有理想化:
· 有些UI效果是很难或者不可以用XAML来描述的,需要手动编写效果。
· 大多数设计人员很难接受面向对象思维,包括对资源(Resource)的复用也不理想
· 用Blend生成的XAML代码并不高效,一种很简单的布局也可能被翻译成很冗长的XAML。
在经历过这样不愉快的配合后,很多公司引入了一个integrator的概念。专门抽出一个比较有经验的开发人员,负责把设计人员提供的XAML代码整理成比较符合要求的XAML,并且在设计人员无法实现XAML的情况下,根据设计人员的需要来编写XAML或者手动编写代码。关于这方面,我的经验是,设计人员放弃Blend,使用Expression Design。Design工具还是比较符合设计人员的思维,当然,需要特别注意一些像素对齐方面的小问题。开发人员再通过设计人员提供的design文件转化到项目中。这里一般是用Blend打开工程,Expression系列复制粘贴是格式化到剪切板的,所以可以在design文件中选中某一个图形,点复制,再切到blend对应的父节点下点粘贴,适当修改一下转化过来的效果。
作为一个矢量化图形工具,Expression Studio确实给我们提供了很多帮助,也可以达到设计人员同开发人员进行合作,不过,不像微软描述的那样自然。总的来说,还好,但是不够好。
这里,要步入本篇文章的重点了,也是我很多时候听起来很无奈的事情。微软在宣传WPF时过于宣传XAML和工具的简易性了,造成很多刚接触WPF的朋友们会产生这样一副想法。WPF=XAML? 哦,类似HTML的玩意...
这个是不对的,或者是不能这么说的。作为一款新的图形引擎,以Foundation作为后缀,代表了微软的野心。借助于托管平台的支持,微软寄希望WPF打破长久以来桌面开发和Web开发的壁垒。当然,由于需要.net3.0+版本的支持,XBAP已经逐渐被Silverlight所取替。在整个WPF的设计里,XAML(Markup)确实是他的亮点,也吸取了Web开发的精华。XAML对于帮助UI和实现的分离,有如如虎添翼。但XAML并不是WPF独有的,包括WF等其他技术也在使用它,如果你愿意,所有的UI你也可以完成用后台代码来实现。正是为了说明这个概念,Petzold在Application = codes + markup 一书中一分为二,前半本书完全使用Code来实现的,后面才讲解了XAML以及在XAML中声明UI等。但这本书叫好不叫座,你有一定开发经验回头来看发现条条是路,非常经典,但你抱着这本书入门的话估计你可能就会一头雾水了。
所以很多朋友来抱怨,WPF的学习太曲折了,上手很容易,可是深入一些就比较困难,经常碰到一些诡异困难的问题,最后只能推到不能做,不支持。复杂是由数量级别决定的,这里借LearnWPF的一些数据,来对比一下Asp.net, WinForm和WPF 类型以及类的数量:
ASP.NET 2.0 |
WinForms 2.0 |
WPF |
1098 public types 1551 classes |
777 public types 1500 classes |
1577 public types 3592 classes |
当然,这个数字未必准确,也不能由此说明WPF相比Asp.net、WinForm,有多复杂。但是面对如此庞大的类库,想要做到一览众山小也是很困难的。想要搞定一个大家伙,我们就要把握它的脉络,所谓庖丁解牛,也需要知道在哪下刀。在正式谈如何学好WPF之前,我想和朋友们谈一下如何学好一门新技术。
学习新技术有很多种途经,自学,培训等等。相对于我们来说,听说一门新技术,引起我们的兴趣,查询相关讲解的书籍(资料),边看书边动手写写Sample这种方式应该算最常见的。那么怎么样才算学好了,怎么样才算是学会了呢?在这里,解释下知识树的概念:
这不是什么创造性的概念,也不想就此谈大。我感觉学习主要是两方面的事情,一方面是向内,一方面是向外。这棵所谓树的底层就是一些基础,当然,只是个举例,具体图中是不是这样的逻辑就不要见怪了。学习,就是一个不断丰富自己知识树的过程,我们一方面在努力的学习新东西,为它添枝加叶;另一方面,也会不停的思考,理清脉络。这里谈一下向内的概念,并不是没有学会底层一些的东西,上面的东西就全是空中楼阁了。很少有一门技术是仅仅从一方面发展来的,就是说它肯定不是只有一个根的。比方说没有学过IL,我并不认为.NET就无法学好,你可以从另外一个根,从相对高一些的抽象上来理解它。但是对底层,对这种关键的根,学一学它还是有助于我们理解的。这里我的感觉是,向内的探索是无止境的,向外的扩展是无限可能的。
介绍了这个,接下来细谈一下如何学好一门新技术,也就是如何添砖加瓦。学习一门技术,就像新new了一个对象,你对它有了个大致了解,但它是游离在你的知识树之外的,你要做的很重要的一步就是把它连好。当然这层向内的连接不是一夕之功,可能会连错,可能会少连。我对学好的理解是要从外到内,再从内到外,就读书的例子谈一下这个过程:
市面关于技术的书很多,名字也五花八门的,简单的整理一下,分为三类,就叫V1,V2,V3吧。
· V1类,名字一般比较好认,类似30天学通XXX,一步一步XXX…没错,入门类书。这种书大致上都是以展示为主的,一个一个Sample,一步一步的带你过一下整个技术。大多数我们学习也都是从这开始的,倒杯茶水,打开电子书,再打开VS,敲敲代码,只要注意力集中些,基本不会跟不上。学完这一步,你应该对这门技术有了一定的了解,当然,你脑海中肯定不自觉的为这个向内连了很多线,当然不一定正确,不过这个新东东的创建已经有轮廓了,我们说,已经达到了从外的目的。
· V2类,名字就比较乱了,其实意思差不多,只是用的词语不一样。这类有深入解析XXX,XXX本质论…这种书良莠不齐,有些明明是入门类书非要换个马甲。这类书主要是详细的讲一下书中的各个Feature, 来龙去脉,帮你更好的认识这门技术。如果你是带着问题去的,大多数也会帮你理清,书中也会顺带提一下这个技术的来源,帮你更好的把请脉络。这种书是可以看出作者的功力的,是不是真正达到了深入浅出。这个过程结束,我们说,已经达到了从外到内的目的。
· V3类,如果你认真,踏实的走过了前两个阶段,我觉得在简历上写个精通也不为过。这里提到V3,其实名字上和V2也差不多。往内走的越深,越有种冲动想把这东西搞透,就像被强行注入了内力,虽然和体内脉络已经和谐了,不过总该自己试试怎么流转吧。这里谈到的就是由内向外的过程,第一本给我留下深刻印象的书就是侯捷老师的深入浅出MFC,在这本书中,侯捷老师从零开始,一步一步的构建起了整个类MFC的框架结构。书读两遍,如醍醐灌顶,痛快淋漓。如果朋友们有这种有思想,讲思想,有匠心的书也希望多多推荐,共同进步。
回过头,就这个说一下WPF。WPF现在的书也有不少,入门的书我首推MSDN。其实我觉得作为入门类的书,微软的介绍就已经很好了,面面俱到,用词准确,Sample附带的也不错。再往下走,比如Sams.Windows.Presentation.Foundation.Unleashed或者Apress_Pro_WPF_Windows_Presentation_Foundation_in_NET_3_0也都非常不错。这里没有看到太深入的文章,偶有深入的也都是一笔带过,或者是直接用Reflector展示一下Code。
接下来,谈一下WPF的一些Feature。因为工作关系,经常要给同事们培训讲解WPF,越来越发现,学好学懂未必能讲懂讲透,慢慢才体会到,这是一个插入点的问题。大家的水平参差不齐,也就是所谓的总口难调,那么讲解的插入点就决定了这个讲解能否有一个好的效果,这个插入点一定要尽可能多的插入到大家的知识树上去。最开始的插入点是大家比较熟悉的部分,那么往后的讲解就能一气通贯,反之就是一个接一个的概念,也就是最讨厌的用概念讲概念,搞得人一头雾水。
首先说一下Dependency Property(DP)。这个也有很多人讲过了,包括我也经常和人讲起。讲它的储存,属性的继承,验证和强制值,反射和值储存的优先级等。那么为什么要有DP,它能带来什么好处呢?
抛开DP,先说一下Property,属性,用来封装类的数据的。那么DP,翻译过来是依赖属性,也就是说类的属性要存在依赖,那么这层依赖是怎么来的呢。任意的一个DP,MSDN上的一个实践是这样的:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool)); public bool IsSpinning { get { return (bool)GetValue(IsSpinningProperty); } set { SetValue(IsSpinningProperty, value); } }
单看IsSpinning,和传统的属性没什么区别,类型是bool型,有get,set方法。只不过内部的实现分别调用了GetValue和SetValue,这两个方法是DependecyObject(简称DO,是WPF中所有可视Visual的基类)暴露出来的,传入的参数是IsSpinningProperty。再看IsSpinningProperty,类型就是DependencyProperty,前面用了static readonly,一个单例模式,有DependencyProperty.Register,看起来像是往容器里注册。
粗略一看,也就是如此。那么,它真正的创新、威力在哪里呢。抛开它精巧的设计不说,先看储存。DP中的数据也是存储在对象中的,每个DependencyObject内部维护了一个EffectiveValueEntry的数组,这个EffectiveValueEntry是一个结构,封装了一个DependencyProerty的各个状态值animatedValue(作动画),baseValue(原始值),coercedValue(强制值),expressionValue(表达式值)。我们使用DenpendencyObject.GetValue(IsSpinningProperty)时,就首先取到了该DP对应的EffectiveValueEntry,然后返回当前的Value。
那么,它和传统属性的区别在哪里,为什么要搞出这样一个DP呢?第一,内存使用量。我们设计控件,不可避免的要设计很多控件的属性,高度,宽度等等,这样就会有大量(私有)字段的存在,一个继承树下来,低端的对象会无法避免的膨胀。而外部通过GetValue,SetValue暴露属性,内部维护这样一个EffectiveValueEntry的数组,顾名思义,只是维护了一个有效的、设置过值的列表,可以减少内存的使用量。第二,传统属性的局限性,这个有很多,包括一个属性只能设置一个值,不能得到变化的通知,无法为现有的类添加新的属性等等。
这里谈谈DP的动态性,表现有二:可以为类A设置类B的属性;可以给类A添加新的属性。这都是传统属性所不具备的,那么是什么让DependencyObject具有这样的能力呢,就是这个DenpencyProperty的设计。在DP的设计中,对于单个的DP来说,是单例模式,也就是构造函数私有,我们调用DependencyProperty.Register或者DependencyProperty.RegisterAttached这些静态函数的时候,内部就会调用到私有的DP 的构造函数,构建出新的DP,并把这个DP加入到全局静态的一个HashTable中,键值就是根据传入时的名字和对象类型的hashcode取异或生成的。
既然DependencyProperty是维护在一个全局的HashTable中的,那么具体到每个对象的属性又是怎么通过GetValue和SetValue来和DependencyProperty关联上的,并获得PropertyChangeCallback等等的能力呢。在一个DP的注册方法中,最多传递五个参数 :
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata,
ValidateValueCallback validateValueCallback);
其中第一和第三个参数就是用来确定HashTable中的键值,第二个参数确定了属性的类型,第四个参数是DP中的重点,定义了DP的属性元数据。在元数据中,定义了属性变化和强制值的Callback等。那么在一个SetValue的过程中,会出现哪些步骤呢:
- 取得该DP下对应这个DependencyObject的PropertyMetadata,这句可能听起来有些拗口。Metadata,按微软一般的命名规则,一般是用来描述对象自身数据的,那么一个DP是否只含有一个propertyMetadata呢?答案是不是,一个DP内部维护了一个比较高效的map,里面存储了多个propertyMetadata,也就是说DP和propertyMetadata是一对多的关系。这里是为什么呢,因为同一个DP可能会被用到不同的DependencyObject中去,对于每类DependencyObject,对这个DP的处理都有所不同,这个不同可以表现在默认值不同,properyMetadata里面的功能不同等等,所以在设计DP的时候设计了这样一个DP和propertyMetadata一对多的关系。
- 取得了该DP下对应真正干活的PropertyMetadata,下一步要真正的”SetValue”了。这个“value”就是要设置的值,设置之后要保存到我们前面提到的EffectiveValueEntry上,所以这里还要先取得这个DP对应的EffectiveValueEntry。在DependencyObject内部的EffectiveValueEntry的数组里面查找这个EffectiveValueEntry,有,取得;没有,新建,加入到数组中。
- 那么这个EffectiveValueEntry到底是用来干什么的,为什么需要这样一个结构体?如果你对WPF有一定了解,可能会听说WPF值储存的优先级,local value>style trigger>template trigger>…。在一个EffectiveValueEntry中,定义了一个BaseValueSourceInternal,用来表示设置当前Value的优先级,当你用新的EffectiveValueEntry更新原有的EffectiveValueEntry时,如果新的EffectiveValueEntry中BaseValueSourceInternal高于老的,设置成功,否则,不予设置。
- 剩下的就是proertyMetadata了,当你使用类似如下的参数注册DP,
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register( "CurrentReading", typeof(double), typeof(Gauge), new FrameworkPropertyMetadata( Double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnCurrentReadingChanged), new CoerceValueCallback(CoerceCurrentReading)), new ValidateValueCallback(IsValidReading));
当属性发生变化的时候,就会调用metadata中传入的委托函数。这个过程是这样的, DependencyObject中定义一个虚函数 :
protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e)。
当DP发生变化的时候就会先首先调用到这个OnPropertyChanged函数,然后如果metaData中设置了PropertyChangedCallback的委托,再调用委托函数。这里我们设置了FrameworkPropertyMetadataOptions.AffectsMeasure, 意思是这个DP变化的时候需要重新测量控件和子控件的Size。具体WPF的实现就是FrameworkElement这个类重载了父类DependencyObject的OnPropertyChanged方法,在这个方法中判断参数中的metadata是否是FrameworkPropertyMetadata,是否设置了
FrameworkPropertyMetadataOptions.AffectsMeasure这个标志位,如果有的话调用一下自身的InvalidateMeasure函数。
简要的谈了一下DependencyProperty,除了微软那种自卖自夸,这个DependencyProperty究竟为我们设计实现带来了哪些好处呢?
1. 就是DP本身带有的PropertyChangeCallback等等,方便我们的使用。
2. DP的动态性,也可以叫做灵活性。举个例子,传统的属性,需要在设计类的时候设计好,你在汽车里拿飞机翅膀肯定是不可以的。可是DependencyObject,通过GetValue和SetValue来模仿属性,相对于每个DependencyObject内部有一个百宝囊,你可以随时往里放置数据,需要的时候又可以取出来。当然,前面的例子都是使用一个传统的CLR属性来封装了DP,看起来和传统属性一样需要声明,下面介绍一下WPF中很强大的Attached Property。
再谈Attached Property之前,我打算和朋友们谈一个设计模式,结合项目实际,会更有助于分析DP,这就是MVVM(Mode-View-ViewModel)。关于这个模式,网上也有很多论述,也是我经常使用的一个模式。那么,它有什么特点,又有什么优缺点呢?先来看一个模式应用:
public class NameObject : INotifyPropertyChanged { private string _name = "name1"; public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged("Name"); } } private void NotifyPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public event PropertyChangedEventHandler PropertyChanged; } public class NameObjectViewModel : INotifyPropertyChanged { private readonly NameObject _model; public NameObjectViewModel(NameObject model) { _model = model; _model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged); } void _model_PropertyChanged(object sender, PropertyChangedEventArgs e) { NotifyPropertyChanged(e.PropertyName); } public ICommand ChangeNameCommand { get { return new RelayCommand( new Action<object>((obj) => { Name = "name2"; }), new Predicate<object>((obj) => { return true; })); } } public string Name { get { return _model.Name; } set { _model.Name = value; } } private void NotifyPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public event PropertyChangedEventHandler PropertyChanged; } public class RelayCommand : ICommand { readonly Action<object> _execute; readonly Predicate<object> _canExecute; public RelayCommand(Action<object> execute, Predicate<object> canExecute) { _execute = execute; _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } } public partial class Window1 : Window { public Window1() { InitializeComponent(); this.DataContext = new NameObjectViewModel(new NameObject()); } } <Window x:Class="WpfApplication7.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> <TextBlock Margin="29,45,129,0" Name="textBlock1" Height="21" VerticalAlignment="Top" Text="{Binding Path=Name}"/> <Button Height="23" Margin="76,0,128,46" Name="button1" VerticalAlignment="Bottom" Command="{Binding Path=ChangeNameCommand}">Rename</Button> </Grid> </Window
类的关系如图所示:
这里NameObject -> Model,NameObjectViewModel -> ViewModel,Window1 -> View。我们知道,在通常的Model-View世界中,无论MVC也好,MVP也好,包括我们现在提到的MVVM,它的Model和View的功能都类似,Model是用来封装核心数据,逻辑与功能计算的模型,View是视图,具体可以对应到窗体(控件)等。那么View的功能主要有,把Model的数据显示出来,响应用户的操作,修改Model,剩下Controller或Presenter的功能就是要组织Model和View之间的关系,整理一下Model-View世界中的需求点,大致有:
1. 为View提供数据,如何把Model中的数据提供给View。
2. Model中的数据发生变化后,View如何更新视图。
3. 根据不同的情况为Model选择不同的View。
4. 如何响应用户的操作,鼠标点击或者一些其他的事件,来修改Model。
所谓时势造英雄,那么WPF为MVVM打造了一个什么“时势“呢。
1. FrameworkElement类中定义了属性DataContext(数据上下文),所有继承于FrameworkElement的类都可以使用这个数据上下文,我们在XAML中的使用类似Text=”{Binding Path=Name}”的时候,隐藏的含义就是从这个控件的DataContext(即NameObjectViewModel)中取它的Name属性。相当于通过DataContext,使View和Model中存在了一种松耦合的关系。
2. WPF强大的Binding(绑定)机制,可以在Model发生变化的时候自动更新UI,前提是Model要实现INotifyPropertyChanged接口,在Model数据发生变化的时候,发出ProperyChaned事件,View接收到这个事件后,会自动更新绑定的UI。当然,使用WPF的DenpendencyProperty,发生变化时,View也会更新,而且相对于使用INotifyPropertyChanged,更为高效。
3. DataTemplate和DataTemplateSelector,即数据模板和数据模板选择器。可以根据Model的类型或者自定义选择逻辑来选择不同的View。
4. 使用WPF内置的Command机制,相对来说,我们对事件更为熟悉。比如一个Button被点击,一个Click事件会被唤起,我们可以注册Button的Click事件以处理我们的逻辑。在这个例子里,我使用的是Command="{Binding Path=ChangeNameCommand}",这里的ChangeNameCommand就是DataContext(即NameObjectViewModel)中的属性,这个属性返回的类型是ICommand。在构建这个Command的时候,设置了CanExecute和Execute的逻辑,那么这个ICommand什么时候会调用,Button Click的时候会调用么?是的,WPF内置中提供了ICommandSource接口,实现了这个接口的控件就有了触发Command的可能,当然具体的触发逻辑要自己来控制。Button的基类ButtonBase就实现了这个接口,并且在它的虚函数OnClick中触发了这个Command,当然,这个Command已经被我们绑定到ChangeNameCommand上去了,所以Button被点击的时候我们构建ChangeNameCommand传入的委托得以被调用。
正是借助了WPF强大的支持,MVVM自从提出,就获得了好评。那么总结一下,它真正的亮点在哪里呢?
1. 使代码更加干净,我没使用简洁这个词,因为使用这个模式后,代码量无疑是增加了,但View和Model之间的逻辑更清晰了。MVVM致力打造一种纯净UI的效果,这里的纯净指后台的xaml.cs,如果你编写过WPF的代码,可能会出现过后台xaml.cs代码急剧膨胀的情况。尤其是主window的后台代码,动则上千行的代码,整个window内的控件事件代码以及逻辑代码混在一起,看的让人发恶。
2. 可测试性。更新UI的时候,只要Model更改后发出了propertyChanged事件,绑定的UI就会更新;对于Command,只要我们点击了Button,Command就会调用,其实是借助了WPF内置的绑定和Command机制。如果在这层意思上来说,那么我们就可以直接编写测试代码,在ViewModel上测试。如果修改数据后得到了propertyChanged事件,且值已经更新,说明逻辑正确;手动去触发Command,模拟用户的操作,查看结果等等。就是把UnitTest也看成一个View,这样Model-ViewModel-View和Model-ViewModel-UnitTest就是等价的。
3. 使用Attached Behavior解耦事件,对于前面的例子,Button的点击,我们已经尝试了使用Command而不是传统的Event来修改数据。是的,相对与注册事件并使用,无疑使用Command使我们的代码更“和谐“,如果可以把控件的全部事件都用Command来提供有多好,当然,控件的Command最多一个,Event却很多,MouseMove、MouseLeave等等,指望控件暴露出那么多Command来提供绑定不太现实。这里提供了一个Attached Behavior模式,目的很简单,就是要注册控件的Event,然后在Event触发时时候调用Command。类似的Sample如下:
public static DependencyProperty PreviewMouseLeftButtonDownCommandProperty = DependencyProperty.RegisterAttached( "PreviewMouseLeftButtonDown", typeof(ICommand), typeof(AttachHelper), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(AttachHelper.PreviewMouseLeftButtonDownChanged))); public static void SetPreviewMouseLeftButtonDown(DependencyObject target, ICommand value) { target.SetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty, value); } public static ICommand GetPreviewMouseLeftButtonDown(DependencyObject target) { return (ICommand)target.GetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty); } private static void PreviewMouseLeftButtonDownChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { FrameworkElement element = target as FrameworkElement; if (element != null) { if ((e.NewValue != null) && (e.OldValue == null)) { element.PreviewMouseLeftButtonDown += element_PreviewMouseLeftButtonDown; } else if ((e.NewValue == null) && (e.OldValue != null)) { element.PreviewMouseLeftButtonDown -= element_PreviewMouseLeftButtonDown; } } } private static void element_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { FrameworkElement element = (FrameworkElement)sender; ICommand command = (ICommand)element.GetValue(AttachHelper.PreviewMouseLeftButtonDownCommandProperty); command.Execute(sender);
这里用到了DependencyProperty.RegisterAttached这个AttachedProperty,关于这个模式,留到下面去讲,这段代码的主要意思就是注册控件的PreviewMouseLeftButtonDown事件,在事件唤起时调用AttachedProperty传入的Command。
那么是不是这个模式真的就这么完美呢,当然不是,MVVM配上WPF自然是如鱼得水,不过它也有很多不足,或者不适合使用的场合:
1. 这个模式需要Model-ViewModel,在大量数据的时候为每个Model都生成这样一个ViewModel显然有些过。ViewModel之所以得名,因为它要把Model的属性逐一封装,来给View提供绑定。
2. Command的使用,前面提到过,实现ICommandSource的接口才具备提供Command的能力,那是不是WPF的内置控件都实现了这样的接口呢?答案是不是,很少,只有像Button,MenuItem等少数控件实现了这一接口,像我们比较常用ComboBoxItem就没有实现这一接口。接口没实现,我们想使用Command的绑定更是无从谈起了。这个时候我们要使用Command,就不得不自己写一个ComboxBoxCommandItem继承于ComboBoxItem,然后自己实现ICommandSource,并且在Click的时候触发Command的执行了。看起来这个想法不算太好,那不是要自己写很多控件,目的就是为了用Command,也太为了模式而模式了。但像Expression Blend,它就是定义了很多控件,目的就是为了使用Command,说起来也奇怪,自己设计的控件,用起来自己还需要封装,这么多个版本也不添加,这个有点说不过去了。
3. 纯UI,就是在控件后台的cs代码中除了构造函数最多只有一行,this.DataContext = xx; 设置一下数据上下文。当然,我目前的项目代码大都是这样的,还是那句话,不要为了模式而模式。那么多的控件event,不管是使用Attached模式还是用一些奇技淫巧用反射来构建出Command,都没什么必要。目前我的做法就是定义一个LoadedCommand,在这个Command中引用界面上的UI元素,ViewModel拿到这个UI元素后在ViewModel中注册控件事件并处理。还是第一个优点,这么做只是为了让代码更干净,逻辑更清晰,如果都把各个控件事件代码都写在一个xaml.cs中看起来比较混乱。
谈过了MVVM,接下来重点谈AttachedProperty,这个是很好很强大的feature,也是WPF真正让我有不一样感觉的地方。前面简单谈过了DependencyProperty的原理,很多初接触WPF的朋友们都会觉得DP很绕,主要是被它的名字和我们的第一直觉所欺骗。如果我们定义了一个DP,MyNameProperty,类型是string的。那么在DependencyObject上,我谈过了有个百宝囊,就是EffectiveValueEntry数组,它内部最终储存MyName的值也是string,这个DependencyProperty(即MyNameProperty)是个静态属性,是在你设置读取这个string的时候起作用的,如何起作用是通过它注册时定义的propertyMetadata决定的。
简单来说就是DependencyObject可以使用DependencyProperty,但两者没有从属关系,你在一个DependencyObject中定义了一个DP,在另一个DependencyObject也可以使用这个DP,你在另一个DependencyObject中写一个CLR属性使用GetValue和SetValue封装这个DP是一样的。唯一DependencyProperty和DependencyObject有关联的地方就是你注册的时候,DP保存在全局静态DP的Hashtable里的键值是通过注册时的名字和这个DependencyObject的类型的hashcode取异或生成的。但这个键值也可以不唯一,DP提供了一个AddOwner方法,你可以为这个DP在全局静态DP中提供一个新键值,当然,这两个键值指向同一个DP。
既然如此,那么为什么有DependencyProperty.Register和DependencyProperty.RegisterAttached两种方法注册DP呢。既然DP只是一个引子,通过GetValue和SetValue,传入DependencyObject就可以取得存储在其中EffectiveValueEntry里面的值,这两个不是一样的么?恩,原理上是一个,区别在于,前面提到,一个DependencyProperty里面会有多个propertyMetadata,比如说Button定义了一个DP,我们又写了一个CustomButton,继承于Button。我们在CustomButton的静态函数中调用了前面DP的OverrideMetadata函数,DP的OverrideMetadata会涉及到Merge操作,它要把新旧的propertyMetadata合二为一成一个,作为新的propertyMetadata,而这个overrideMetadata过程需要调用时传入的类型必须是DependencyObject的。DependencyProperty.Register和DependencyProperty.RegisterAttached的区别是前者内部调用了OverrideMetadata而后者没有,也就意味着Rigister方法只能是DependencyObject调用,而后者可以在任何对象中注册。
就这一个区别么?恩,还有的,默认的封装方法,Register是使用CLR属性来封装的,RegisterAttached是用静态的Get,Set来封装的。Designer反射的时候,遇到静态的封装会智能感知成类似Grid.Column=“2”这样的方式。这个就类似于非要说菜刀有两大功能,一是砍菜,二是砍人。你要感到纳闷,不是因为菜刀有刀刃么?它会和你解释,不同不同,砍菜进行了优化,你可以用手握着,砍人犯法,最好飞出去…
那么为什么微软要把这个注册过程分为Register和RegisterAttached两类呢?就是为了强调Attach这个概念,这个过程就是DependencyObject(相当于银行金库,有很多箱子)通过DependencyProperty(相当于开箱子的钥匙)取得自己箱子里的财宝一样,当然这些所有的钥匙有人统一管理(全局的HashTable),你来拿钥匙的时候还要刁难一下你(通过钥匙上的附带的propertyMetadata)检查一下你的身份啦,你存取东西要发出一些通知啦等等。这个Attach,翻译过来叫附加,所谓的AttachedProperty(附加属性),就是说人家可以随时新配一把钥匙来你这新开一个箱子,或者拿一把旧钥匙来你这新开个箱子,谁让你箱子多呢?
强调了这么多,只是为了说明一点,这个Attach的能力不是因为你注册了RegisterAttached才具备的,而是DependencyProperty本身设计就支持的。那么这个设计能为我们开发程序带来哪些好处呢?
从继承和接口实现来说,人们初期阶段有些乱用继承,后来出现了接口,只有明确有IS-A语义的才用继承,能力方面的用接口来支持。比如飞行,那么一般会定义到一个IFlyable的接口,我们实现这个接口以获得飞行的能力。那么这个能力的获得要在类的设计阶段继承接口来获得,那么作为一个已经成熟的人,我是大雄,我要飞,怎么办?
AttachedProperty来救你。代码如下:
public partial class Window1 : Window { public Window1() { InitializeComponent(); this.DataContext = new DragonFlyViewModel(); } } public interface IFlyHandler { void Fly(); } public class DragonFlyViewModel : IFlyHandler { public void Fly() { MessageBox.Show("送你个竹蜻蜓,飞吧!"); } } public class FlyHelper { public static readonly DependencyProperty FlyHandlerProperty = DependencyProperty.RegisterAttached("FlyHandler", typeof(IFlyHandler), typeof(FlyHelper), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnFlyHandlerPropertyChanged))); public static IFlyHandler GetFlyHandler(DependencyObject d) { return (IFlyHandler)d.GetValue(FlyHandlerProperty); } public static void SetFlyHandler(DependencyObject d, IFlyHandler value) { d.SetValue(FlyHandlerProperty, value); } public static void OnFlyHandlerPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { FrameworkElement element = target as FrameworkElement; if (element != null) { IFlyHandler flyHander = e.NewValue as IFlyHandler; element.MouseLeftButtonDown += new MouseButtonEventHandler((sender, ex) => { if (flyHander != null) { flyHander.Fly(); } }); } } } <Window x:Class="WpfApplication7.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication7" Title="Window1" Height="300" Width="300"> <Grid> <Label Margin="72,58,88,113" Name="label1" Background="Yellow" local:FlyHelper.FlyHandler="{Binding}">我叫大雄我不会飞</Label> </Grid> </Window
这是一个最简单的模式应用,当然,还不是很完美,不过已经可以起飞了。我们在FlyHelper中使用DependencyProperty.RegisterAttached注册了一个AttachedProperty,在OnFlyHandlerPropertyChanged中订阅了element的MouseLeftButtonDown事件,事件处理就是”起飞”。这里定义了一个IFlyHandler的接口,使用ViewModel模式,ViewModel去实现这个接口,然后使用local:FlyHelper.FlyHandler="{Binding}"绑定,这里{Binding}没有写path,默认绑定到DataContext本身,也就是DragonFlyViewModel上。
你说什么?你要去追小静?那一定要帮你。你往脑门上点一下,看,是不是会飞了?怎么样,戴着竹蜻蜓的感觉很好吧,^_^。大雄欣喜若狂,连声感谢。不过,这么欺骗一个老实人的感觉不太好,实话实说了吧。你真是有宝物不会用啊,你胸前挂着那是啥?小口袋?百宝囊?那是机器猫的口袋,汽车大炮时空飞船,什么掏不出来啊。哦,你嫌竹蜻蜓太慢了?你等等。
public partial class Window1 : Window { public Window1() { InitializeComponent(); this.DataContext = new DragonFlyViewModel(); } private void button1_Click(object sender, RoutedEventArgs e) { this.DataContext = new FighterViewModel(); } } public interface IFlyHandler { void Fly(); } public class DragonFlyViewModel : IFlyHandler { public void Fly() { MessageBox.Show("送你个竹蜻蜓,飞吧!"); } } public class FighterViewModel : IFlyHandler { public void Fly() { MessageBox.Show("送你驾战斗机,为了小静,冲吧!"); } } public class FlyHelper { private IFlyHandler _flyHandler; public FlyHelper(IFlyHandler handler, FrameworkElement element) { _flyHandler = handler; element.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown); } void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (_flyHandler != null) { _flyHandler.Fly(); } } private void UpdateFlyHandler(IFlyHandler handler) { _flyHandler = handler; } #region FlyHelper public static readonly DependencyProperty FlyHelperProperty = DependencyProperty.RegisterAttached("FlyHelper", typeof(FlyHelper), typeof(FlyHelper), new FrameworkPropertyMetadata(null)); public static FlyHelper GetFlyHelper(DependencyObject d) { return (FlyHelper)d.GetValue(FlyHelperProperty); } public static void SetFlyHelper(DependencyObject d, FlyHelper value) { d.SetValue(FlyHelperProperty, value); } #endregion #region FlyHandler public static readonly DependencyProperty FlyHandlerProperty = DependencyProperty.RegisterAttached("FlyHandler", typeof(IFlyHandler), typeof(FlyHelper), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnFlyHandlerPropertyChanged))); public static IFlyHandler GetFlyHandler(DependencyObject d) { return (IFlyHandler)d.GetValue(FlyHandlerProperty); } public static void SetFlyHandler(DependencyObject d, IFlyHandler value) { d.SetValue(FlyHandlerProperty, value); } public static void OnFlyHandlerPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { FrameworkElement element = target as FrameworkElement; if (element != null) { FlyHelper helper = (FlyHelper)element.GetValue(FlyHelperProperty); if (helper == null) { IFlyHandler handler = e.NewValue as IFlyHandler; if (handler != null) { helper = new FlyHelper(handler, element); element.SetValue(FlyHelperProperty, helper); } } else { IFlyHandler handler2 = e.NewValue as IFlyHandler; //handler2 may be null, this usually happened when this.DataContext = null, release IFlyHandler. helper.UpdateFlyHandler(handler2); } } } #endregion }
这里就是一个完整的Attached模式,这里我添加了一个新的AttachedProperty,类型是FlyHelper,当local:FlyHelper.FlyHandler="{Binding}"绑定值发生变化时,判断传入的这个DependencyObject内是否有FlyHelper对象,没有,构造一个,然后塞入到这个DependencyObject中去;如果有,则更新FlyHelper内持有的IFlyHandler对象。这个Attached模式的好处在于,这个辅助的Helper对象是在运行时构造的,构造之后塞入到UI对象(DependencyObject)中去,仅是UI对象持有这个引用,UI对象被释放后这个Helper对象也被释放。FlyHelper对象用于控制何时”起飞”,至于怎么飞则依赖于IFlyHandler这个接口,这层依赖是在绑定时注入的,而这个绑定最终是运用了DataContext这个数据上下文,和MVVM模式搭配的很完美。这也就是MVVM模式中强调的,也就是唯一的依赖,设置控件的DataContext。
回顾一下,作于例子中的Label,是不具备“飞行“能力的。这种不具备具体说就是不知道什么时候触发动作,也不知道触发了之后该干什么。通过一个Attach模式使它具备了这个能力,而且可以随时更新动作。简直达到了一种让你飞,你就飞的境界,值得为它喝彩。
鉴于这种动态添加控件的能力,这种模式也被称为Attached Behavior。在Blend 3中,也加入了Behaviors的支持,很多通用的能力,都可以用Behavior来把它抽出来,比如缩放,DragDrop等等。我没有具体研究过Blend的Behavior,应该也是这种方法或演变吧。在实际项目中,我也大量使用了MVVM和Attached Behavior,配上CommandBinding,Unit Test,脚本化UIAutomation,以及Prism等框架,对一些比较大型的项目,还是很有帮助的。
顺着DP这条线讲下来,还是蛮有味道的。当然,WPF中还有一些比较新的概念,包括逻辑树和视觉树,路由事件,Style和Template等等。其实纵看WPF,还是有几条主线的,包括刚才讲到的DP,Threading Model与Dispatcher,视觉树和依赖它产生的路由,Template和Style等等。那么就回到开头了,如何学好WPF呢?
其实写这篇文章之前,我是经常带着这疑问的。现在新技术推出的很快,虽说没什么技术是凭空产生,都是逐渐衍变而来的。可是真学下去也要花成本,那怎么样才是学好了呢,怎么能融入到项目呢?后来总结了下,我需要了解这么一些情况:
1. 这门技术是否成熟,前景如何?
2. 摆脱宣传和炒作,这门技术的优缺点在哪里?
3. 希望看到一些对这门技术有整体把握的文章,可以不讲细节,主要是帮我理出一个轮廓,最好和我的知识树连一连。
4. 有没有应用的成功案例。
5. 最重要的,呵呵,有没有可以下载的电子书。
关于WPF,现在讲解的书籍和资料已经蛮多了。随着.NET Framework的升级,包括性能以及辅助工具的支持也越来越好了。但不得不说,WPF学习的时间成本还是很大的。WPF的设计很重,带着很浓的设计痕迹,查看WPF的源码,也许你会有种很熟悉的感觉。这种熟悉不是那种流畅美妙之感,到有种到了项目后期,拿着性能测试去优化,拿着Bug报告乱堵窟窿的感觉。
#region 构造器 public TextView() { InitializeComponent(); txtFont = (this.Content as Canvas).Children[0] as TextBlock; Binding binding = new Binding(); binding.Source = this.DataContext; binding.Path = new PropertyPath("CurrentElement.Src"); BindingOperations.SetBinding(this, TextProperty, binding); } #endregion #region 属性 public string Src { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Src", typeof(string), typeof(TextView), new UIPropertyMetadata(null, CurrentSrcChanged)); private static void CurrentSrcChanged(object element, DependencyPropertyChangedEventArgs e) { TextView textView = (TextView)element; Canvas canvas = (Canvas)textView.Content; TextBlock textBlock = (TextBlock)canvas.Children[0]; XElement xText = XElement.Load(textView.Src); textBlock.Text = xText.Attribute("content").Value.ToString(); canvas.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString(xText.Attribute("bgcolor").Value.ToString())); textBlock.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(xText.Attribute("fgcolor").Value.ToString())); textBlock.FontFamily = (FontFamily)(new FontFamilyConverter().ConvertFromString(xText.Attribute("font").Value.ToString())); textBlock.FontSize = Convert.ToInt32(xText.Attribute("size").Value.ToString()); textBlock.TextWrapping = TextWrapping.Wrap; } #endregion #region 定时器实现 //private void UserControl_Loaded(object sender, RoutedEventArgs e) //{ // System.Threading.Thread thread = new System.Threading.Thread(Scroll); // timer.Interval = 50; // timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); // timer.Start(); // thread.Start(); //} //void timer_Elapsed(object sender, ElapsedEventArgs e) //{ // offset++; // Scroll(); //} //private void Scroll() //{ // Action action; // action = ()=>scrollViewer.ScrollToVerticalOffset(offset); // Dispatcher.BeginInvoke(action); //} //Timer timer = new System.Timers.Timer(); //private double offset = 0; #endregion #region 动画实现 private void CeaterAnimation() { if (txtFont == null || txtFont.ActualHeight < (this.Content as Canvas).ActualHeight) { return; } //创建动画资源 Storyboard storyboard = new Storyboard(); //移动动画 DoubleAnimationUsingKeyFrames HeightMove = new DoubleAnimationUsingKeyFrames(); Storyboard.SetTarget(HeightMove, txtFont); DependencyProperty[] propertyChain = new DependencyProperty[] { TextBlock.RenderTransformProperty, TransformGroup.ChildrenProperty, TranslateTransform.YProperty, }; Storyboard.SetTargetProperty(HeightMove, new PropertyPath("(0).(1)[3].(2)", propertyChain)); HeightMove.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 2)))); HeightMove.KeyFrames.Add(new EasingDoubleKeyFrame(-txtFont.ActualHeight, KeyTime.FromTimeSpan(new TimeSpan(0, 0, 0, (int)(txtFont.ActualHeight / 70))))); storyboard.Children.Add(HeightMove); storyboard.RepeatBehavior = RepeatBehavior.Forever; storyboard.Begin(); } private void TextBlock_Loaded(object sender, RoutedEventArgs e) { CeaterAnimation(); } TextBlock txtFont; #endregion
list删除之后不会通知binding的改变,observableCollection则会通知,比list要厉害一点。
C#属性是域的扩展(即通常说的成员变量或字段等)它配合C#中的域(字段)使用,使之构造一个安全的应用程序,为什么说通过属性来替代域会提高应用程序的安全呢?
原因就在于C#属性通过访问器(Accessors)用进行数据访问.所以C#的属性可以设置为只读或只写. 而字段却没有这样的功能(只可设置只读).我们都知道在程序中有时我们是不允许用户修改一些属性的,比如地球是圆的。原则上我们是不能修改此属性.那么我们就可以通过一个属性来实现这样的功能.让它设置为只读属性.
属性的特点:C#属性是对类中的字段(fields)的保护,像访问字段一样来访问属性。同时也就封装了类的内部数据。每当赋值运算的时候自动调用set访问器,其他时候则调用get访问器。 以 帕斯卡命名 不能冠以Get/Set。静态属性是通过类名调用的!
前面我们说到属性是字段的扩展,我们都知道如果要在类外部访问字段,就要公开(Public)这个成员字段。但是如果我们真的这样做了,那这个成员字段的就可以被任意访问(包括修改,读取).那怎么办呢? 用一个属性就可以解决这个问题.
C#属性是通过Get(读取)、Set(设置)来访问属性的.
public class Test { public Test() { // // TODO: 在此处添加构造函数逻辑 // } //为了说明问题这里我们用中文 public string 地球的形状; } 在上面的例子里"地球的形状"这个字段就可以任意的访问,不受任何的束缚.但是我们都知道地球是圆的,是不允许修改的一个特性,那怎么办呢?用一个属性就可以轻松的解决这个问题. public class Test { public Test() { // // TODO: 在此处添加构造函数逻辑 // } //为了说明问题这里我们用中文 private string 地球的形状="圆";//私有的成员变量,它对于外部是不可见的. public string 地球形状 { get { return 地球的形状;//这里将私有成员变量地球的形状返回给"地球的形状" } } } 这里我们只可以读取属性"地球形状",而不可以写,如果强制写编译器就会提示出错.这样我们就可以通过类来访问属性. Test MyTt=new Test();//实例化类 string MyTemp=MyTt.地球形状;//读取类的属性 下面我们说一下写的应用. public class Test { public Test() { // // TODO: 在此处添加构造函数逻辑 // } //为了说明问题这里我们用中文 private string 你的名字;//私有的成员变量,它对于外部是不可见的. public string 名字 { get { return 你的名字;//这里将私有成员变量"你的名字"的形状返回给"名字" } set { 你的名字=value;//这里的value将等于"名字" 这个属性值 } } }
这样我们就可以对属性读取和写了.
Test MyTt=new Test();//实例化类
MyTt.名字="Simon"//设置属性
String MyTemp=MyTt.名字;读取属性值
通过上面的例子我们可以看出属性只不过是做了一个中介的角色而已,真正工作的还是字段(域),但这样做可以更面向对象,写出更安全的应用程序。
C#提供了一个处理此概念的更清爽的方式。在C#中,get和set方法是内在的,而在Java和C++里则需人为维护。C#的处理方式有诸多优点。它鼓励程序员按照属性的方式去思考—把这个属性标为可读写的和只读的哪个更自然?或者根本不应该为属性?如果你想改变你的属性的名称,你只要检查一处就可以了。
C#中属性这种机制使得在保证封装性的基础上实现了访问私有成员的便捷性。一个支持属性的语言将有助于获得更好的抽象。
来自MSDN中的内容:
属性和属性过程
可以使用属性和字段在对象中存储信息。属性使用属性过程控制如何设置或返回值,而字段只是公共变量。属性过程是在属性定义中声明的代码块,可用于在设置或检索属性值时执行代码。
具有两种类型的属性过程:用于检索属性值的 Get 属性过程和用于为属性分配值的 Set 属性过程。例如,存储银行帐户余额的属性可能会在 Get 属性过程中使用代码以在返回可用余额之前记入利息并检查服务费。然后,您可以使用 Set 属性过程验证余额并防止它以不正确的方式更新。简而言之,属性过程允许对象保护和验证自己的数据。
只读和只写属性
大多数属性都具有 Get 和 Set 属性过程,这两个属性过程可用于读取和修改存储在内部的值。然而,您可以使用 ReadOnly 或 WriteOnly 修饰符来限制对属性进行修改或读取。
只读属性不能具有 Set 属性过程。这种属性用于需要公开但不允许修改的项。例如,可以使用只读属性来提供计算机处理器的速度。
只写属性不能具有 Get 属性过程,它们用于使用不应或不能存储在对象中的数据配置对象。例如,只写属性可用于获取密码并在不存储该密码的情况下更改对象的状态。
<Controls:DataPager x:Name="dataPager" PageSize="25" Grid.Row="1" TotalCount="{Binding Path=SchedualTotalModel.Total}"> <I:Interaction.Triggers> <I:EventTrigger EventName="PageChanged"> <I:InvokeCommandAction Command="{Binding PageChangeCommand}" CommandParameter="{Binding ElementName=dataPager,Path=PageIndex,Converter={StaticResource userConverter}}" /> </I:EventTrigger> <I:EventTrigger EventName="Loaded"> <I:InvokeCommandAction Command="{Binding PageSizeCommand}" CommandParameter="{Binding ElementName=dataPager,Path=PageSize,Converter={StaticResource userConverter}}"/> </I:EventTrigger> </I:Interaction.Triggers> </Controls:DataPager>
public class QueueSearchConEvent : CompositePresentationEvent<SearchCon> { } public class SearchCon { public string _partyId; public string _consultionId; public string _doctorId; public string _stationSelectedIndex; public string _triageSelectedIndex; public string _patientName; public SearchCon(string partyId, string consultionId, string doctorId, string stationSelectedIndex, string triageSelectedIndex, string patientName) { this._partyId = partyId; this._consultionId = consultionId; this._doctorId = doctorId; this._stationSelectedIndex = stationSelectedIndex; this._triageSelectedIndex = triageSelectedIndex; this._patientName = patientName; } }
其次,在构造器之中注册一下事件:
#region 构造器 public QueueListViewModel() { if (!IsInDesignMode) { _commandParameters = this.UnityContainer.Resolve<CommandParameters>(); TrigeService = this.UnityContainer.Resolve<ITrigeService>(); _regionManager = this.UnityContainer.Resolve<IRegionManager>(); _container = this.UnityContainer.Resolve<IUnityContainer>(); PageChange(1, 2); this.EventAggregator.GetEvent<QueueSearchConEvent>().Subscribe(ScreeningResults); } } #endregion
最后,发布事件,也就是用事件(触发某个方法,来引用。可以在项目的任何模块):
private void SelectButtonChanged(string _partyId, string _consultionId, string _doctorId) { this.EventAggregator.GetEvent<QueueSearchConEvent>().Publish(new SearchCon(_partyId, _consultionId, _doctorId, _stationSelectedIndex, _triageSelectedIndex, _patientName)); }
Auto 表示自动适应显示内容的宽度, 如自动适应文本的宽度,文本有多长,控件就显示多长.
* 则表示按比例来分配宽度.
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />
同样,行可以这样定义
<RowDefinition Height="3*" />
<RowDefinition Height="7*" />
这些数字可以是小数.
如果数字缺省,则默认是1.
在这个例子中, 列2的宽度是列1的1.5倍.
<ColumnDefinition Width="1.5*" />
<ColumnDefinition />
Auto和*可以混合使用. 在这个例子中,后两行的宽度在前两行分配完之后,按比例获取剩余的宽度.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <!-- Auto-fit to content, 'Hi' -->
<ColumnDefinition Width="50.5" /> <!-- Fixed 50.5 device units) -->
<ColumnDefinition Width="69*" /> <!-- Take 69% of remainder -->
<ColumnDefinition Width="31*"/> <!-- Take 31% of remainder -->
</Grid.ColumnDefinitions>
<TextBlock Text="Hi" Grid.Column="0" />
auto之后,如果缩小控件显现不出滚动条,而*则会显现出来滚动条。
概述
我们经常会遇到这样的需求:到数据库里查找一些关键字,把带这些关键字的记录返回显示在客户端上。但如果仅仅是单纯地把文本显示出来,那很不直观,用户不能很轻易地看到他们想找的内容,所以通常我们还要做到“高亮显示”。
如果是用BS架构去实现,应该很简单,把相应的关键字加上一些label,然后给label定样式即可,或者直接用js在客户端渲染,减少服务器的负担,但CS架构就相对麻烦一点,我这里用WPF写了一个demo,实现了这个功能的演示:
另外本demo还包括了一些非常有用的wpf的小技巧。
功能介绍
由于这只是一个简单的DEMO,我和以往的风格一样,把它做成了“零配置”,我用一个csv文件和LINQ to Object来取代DBMS,执行一些简单的查询操作。
查询方式分为两种,一种是Full Match,表示全字符匹配,另一种是Any Key,表示用空格断开查询字符串,逐个关键字查询。
这个程序的显示区域使用了ListView控件,之所以使用ListView而不是DataGrid,主要是ListView能很轻易地自适应行高,而DataGrid的行高是固定的,但如果你要换DataGrid去做的话,应该也是同一个道理。
高亮显示功能分析与实现
要实现高亮显示,我们可以这么做:在界面上放置一个TextBlock,叫tbTest,然后执行下面的代码:
tbTest.Inlines.Clear(); tbTest.Inlines.Add( new Run("The"){ Background = Brushes.Yellow }); tbTest.Inlines.Add( " quick brown fox jumps over "); tbTest.Inlines.Add( new Run("the") { Background = Brushes.Yellow }); tbTest.Inlines.Add( new Run(" lazy dog."));
就能看到这样的“高亮”效果:
遗憾的是Inlines这个属性并非“依赖属性”(Dependency Property),你不能轻易把一个字符串或对象“绑定”给它。我的做法是创建一个用户控件,其中只包含一个TextBlock:
<UserControl x:class="HighlightDispDemo.HighlightTextBlock" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <TextBlock Name="innerTextBlock" TextWrapping="Wrap"> </TextBlock> </UserControl>
再给它增加一个叫“HlContent”的依赖属性,其类型为自定义的HighlightContent:
public static readonly DependencyProperty HighlightContentProperty = DependencyProperty .Register( "HlContent", typeof(HighlightContent), typeof( HighlightTextBlock), new FrameworkPropertyMetadata( null, OnHtContentChanged)); [ Description("获取或设置高亮显示的内容")] [ Category("Common Properties")] public HighlightContent HlContent { get { return(HighlightContent)GetValue( HighlightContentProperty); } set { SetValue( HighlightContentProperty, value); } }
HighlightContent的定义如下:
public enum HighlightContentMode { FullMatch, AnyKey }; public class HighlightContent { public string Content { get; set; } public static string ToHighlight { get; set; } public static HighlightContentMode Mode { get; set; } }
其中ToHighlight属性表示要高亮显示的“键”,而Mode属性则用来指明用“Full Match”还是“Any Key”模式,考虑到同一时间只有一种高亮显示,我把这两个属性定义为static。
“HlContent”的内容变更通知回调函数:
private static void OnHtContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg) { if(sender is HighlightTextBlock) { HighlightTextBlock ctrl = sender as HighlightTextBlock ; HighlightContent content = ctrl.HlContent ; ctrl.innerTextBlock.Inlines.Clear(); if(content != null) { ctrl.innerTextBlock.Inlines.AddRange(MakeRunsFromContent( content)); } } } private static IEnumerable<Run> MakeRunsFromContent(HighlightContent content) { //此函数功能是:将要显示的字符串根据key及mode,拆分成不同的Run片段 //代码较多,从略 }
这样一来,我们就可以用自定义的HighlightTextBlock来取代Textblock实现绑定了。
绑定到ListView(摘抄)
ListView的默认的Column是肯定不支持“高亮”显示的了,现在我们来自定义Template:
<ListView ItemContainerStyle="{DynamicResource CustomListViewItemStyle}" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="lvContent" AlternationCount="2"> <ListView.View> <GridView> <GridView.Columns> <GridViewColumn Header="OS Name" Width="100"> <GridViewColumn.CellTemplate> <DataTemplate> <hld:HighlightTextBlock HlContent="{Binding Path=OsName,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="File System" Width="150"> <GridViewColumn.CellTemplate> <DataTemplate> <hld:HighlightTextBlock HlContent="{Binding Path=FileSystem,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="Desktop" Width="200"> <GridViewColumn.CellTemplate> <DataTemplate> <hld:HighlightTextBlock HlContent="{Binding Path=Desktop,Converter={StaticResource converterHlContent}}"></hld:HighlightTextBlock> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView.Columns> </GridView> </ListView.View> </ListView>
可以看到,Template中使用了前面我们自定义的HighlightTextBlock控件,它们绑定的Path分别是OsName,FileSystem和Desktop,其实这都是string,而HlContent需要的是HighlightContent类型,所以我们还得指定一个转换器,转换器代码如下:
[ValueConversion(typeof(string), typeof(HighlightContent))] public class HlContentConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return new HighlightContent {Content = (string)value}; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
使用CsvHelper来读取CSV文件(摘抄)
这次我没有使用DBMS,其实DEMO项目能不用DBMS就不用了,否则部署困难,不利于问题分析。CsvHelper可以从github上获取,地址是:https://github.com/JoshClose/CsvHelper
它的帮助写得稍微有点潦草(个人感觉),我这里稍稍补充说明下:CsvHelper的思路就是把csv文件转为一个可枚举的集合,其中的一行转为集合中的一个对象,那么一列就对应到这个对象的一个属性,那么究竟哪一列转为那个属性呢?我们得告诉它,这就是“Map”,了解了这个之后看一下下面的代码,一切都清楚了。
public class LinuxInfo { public string OsName { get; set; } public string FileSystem { get; set; } public string Desktop { get; set; } } public class LinuxMap : CsvClassMap<LinuxInfo> { public override void CreateMap() { Map(m => m.OsName).Index(0); Map(m => m.FileSystem).Index(1); Map(m => m.Desktop).Index(2); } }
上面代码是对象及Map定义。下面是执行读取和转换的操作。
TextReader tr = new StreamReader("linux.csv", Encoding.UTF8); CsvReader csv = new CsvReader(tr); csv.Configuration.RegisterClassMap<LinuxMap>(); csv.Configuration.HasHeaderRecord = false; //表示csv文件不带header行 _listData = csv.GetRecords<LinuxInfo>().ToList();
ListView的隔行背景样式(摘抄)
把ListView的AlternationCount属性设为2,并指定ItemContainerStyle="{DynamicResource CustomListViewItemStyle}"。Style这样定义:
<Style x:Key="CustomListViewItemStyle" TargetType="{x:Type ListViewItem}"> <Style.Triggers> <Trigger Property="ItemsControl.AlternationIndex" Value="1"> <Setter Property="Background" Value="#DDEEFF"></Setter> </Trigger> </Style.Triggers> </Style>
让TextBox获得焦点时全选文本(摘抄)
这个功能得在App.xml.cs中做一些全局处理:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); //为了让TextBox能够在获得焦点的时候自动选中其中文本,特意添加此全局事件处理 EventManager.RegisterClassHandler(typeof(TextBox), UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyHandleMouseButton), true); EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText), true); } private static void SelectivelyHandleMouseButton(object sender, MouseButtonEventArgs e) { var textbox = (sender as TextBox); if (textbox != null && !textbox.IsKeyboardFocusWithin) { if (e.OriginalSource.GetType().Name == "TextBoxView") { e.Handled = true; textbox.Focus(); } } } private static void SelectAllText(object sender, RoutedEventArgs e) { var textBox = e.OriginalSource as TextBox; if (textBox != null) textBox.SelectAll(); }
完整代码下载
HighlightDispDemo.7z(Visual Studio 2010)
GridViewColumn中的CellTemplateSelector使用起来有一点需要注意:DataTemplateSelector(数据模板选择器)的操作对象是ListView的直接绑定对象,而不是对象在该列的属性。事实上如果设置了DisplayMemberBinding,CellTemplateSelector和CellTemplate都是无效的。
这个问题从一个简单的示例上可以看出来,这样一个简单的对象:
public class Item { public int Int { get; set; } public bool Bool { get; set; } }
用ListView绑定一系列的Item对象,然后分两列显示这个Int和Bool。Bool那列肯定显示True或者False(因为没定义CellTemplateSelector或者CellTemplate的话,WPF默认显示对象的ToString)。现在用一个DataTemplateSelector,来根据Bool是True或者False,显示一个红框或者绿框。
此时DataTemplateSelector你有可能这样定义(错误的):
class BoolSelector : DataTemplateSelector { public DataTemplate TrueTemplate { get; set; } public DataTemplate FalseTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { //错误代码 if ((bool)item) return TrueTemplate; return FalseTemplate; } }
然后在XAML中,定义好这个DataTemplateSelector,设置DisplayMemberBinding和CellTemplateSelector:
<Window.Resources> <loc:BoolSelector x:Key="sel"> <loc:BoolSelector.TrueTemplate> <DataTemplate> <Rectangle Fill="Green" Width="20" Height="20"/> </DataTemplate> </loc:BoolSelector.TrueTemplate> <loc:BoolSelector.FalseTemplate> <DataTemplate> <Rectangle Fill="Red" Width="20" Height="20"/> </DataTemplate> </loc:BoolSelector.FalseTemplate> </loc:BoolSelector> </Window.Resources> <ListView Name="list"> <ListView.View> <GridView> <GridView.Columns> <GridViewColumn Header="int" DisplayMemberBinding="{Binding Int}"/> <GridViewColumn Header="bool" DisplayMemberBinding="{Binding Bool}" CellTemplateSelector="{StaticResource sel}" /> </GridView.Columns> </GridView> </ListView.View> </ListView>
最后ListView仍然只输出True和False。
原因是如果设置了GridViewColumn的DisplayMemberBinding,CellTemplateSelector和CellTemplate都是无效的。同样CellTemplateSelector的对应DataTemplateSelector的针对对象是数据绑定对象(本例中的Item类),而不是对象的属性(本例中的Bool值)。
所以首先修改上面的DataTemplateSelector的SelectTemplate,让其针对Item对象:
public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (((Item)item).Bool) return TrueTemplate; return FalseTemplate; }
接着不使用GridViewColumn的DisplayMemberBinding。直接用CellTemplateSelector:
结果才会显示正确:
本文来自刘圆圆的博客,原文地址:http://www.cnblogs.com/mgen/archive/2011/11/24/2262465.html
重写窗体样式,现在我接触的,主要是下面这个方法,它有如下几个步骤:
1、写一个继承自window的ResourceDictionary,并且要设置如下的style <Setter Property="WindowStyle" Value="None" /> <Setter Property="AllowsTransparency" Value="True" /> 2、grid布局窗体,添加最大化最小化关闭等按钮。并且在继承window的类中实现这些按钮的功能,这些功能也继承自window自带的最大最小化和关闭。 如何让此类能访问ResourceDictionary中的按钮,只需要将其作为app.xaml中的资源文件引用。 引用如下: <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resource Dictionaries/MacStyledWindow.xaml" /> <ResourceDictionary Source="Resources/TabItemResource.xaml"/> <ResourceDictionary Source="Resources/WindowRegionResource.xaml"/> <ResourceDictionary Source="Resources/Styles.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> 调用方法如下: ControlTemplate baseWindowTemplate = (ControlTemplate)App.Current.Resources["MacWindowTemplate"]; //set up background image Button skinBtn = (Button)baseWindowTemplate.FindName("SkinBtn", this); skinBtn.Click += delegate { Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); if (dlg.ShowDialog() == true) { ImageBrush brush = (ImageBrush)baseWindowTemplate.FindName("MyBgImg",this); BitmapImage bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri(dlg.FileName, UriKind.Absolute); bitmap.EndInit(); brush.ImageSource = bitmap; } };
private void myMediaElement_MediaEnded(object sender, RoutedEventArgs e) { MediaElement me = (MediaElement)sender; me.Position = new TimeSpan(0); me.Play(); }
处会报错误。
//打开! private void ventuze_Click(object sender, RoutedEventArgs e) { //获取当前窗口句柄 IntPtr handle = new WindowInteropHelper(this).Handle; string path = @"D: winflag_resVentuzNew.vpr"; string openSoft = @"C:Program FilesVentuzProVentuzPresenter.exe"; app = System.Diagnostics.Process.Start(openSoft,path); prsmwh = app.MainWindowHandle; while (prsmwh == IntPtr.Zero) { prsmwh = app.MainWindowHandle; } //设置父窗口 SetParent(prsmwh, handle); ShowWindowAsync(prsmwh, 3);//子窗口最大化 } //关闭 private void Window_Closed(object sender, System.EventArgs e) { if (app.CloseMainWindow()){ app.Kill(); app.Close(); }
一直以来用WPF做一个项目,但是开发中途发现内存开销太大,用ANTS Memory Profiler分析时,发现在来回点几次载入页面的操作中,使得非托管内存部分开销从起始的43.59M一直到150M,而托管部分的开销也一直持高不下,即每次申请的内存在结束后不能完全释放。在网上找了不少资料,甚受益,现在修改后,再也不会出现这种现象了(或者说,即使有也不吓人),写下几个小心得:
1. 慎用WPF样式模板合并
我发现不采用合并时,非托管内存占用率较小,只是代码的理解能力较差了,不过我们还有文档大纲可以维护。
2. WPF样式模板请共享
共享的方式最简单不过的就是建立一个类库项目,把样式、图片、笔刷什么的,都扔进去,样式引用最好使用StaticResource,开销最小,但这样就导致了一些写作时的麻烦,即未定义样式,就不能引用样式,哪怕定义在后,引用在前都不行。
3. 慎用隐式类型var的弱引用
这个本来应该感觉没什么问题的,可是不明的是,在实践中,发现大量采用var与老老实实的使用类型声明的弱引用对比,总是产生一些不能正确回收的WeakRefrense(这点有待探讨,因为开销不是很大,可能存在一些手工编程的问题)
4. 写一个接口约束一下
谁申请谁释放,基本上这点能保证的话,内存基本上就能释放干净了。我是这么做的:
interface IUIElement : IDisposable
{
/// <summary>
/// 注册事件
/// </summary>
void EventsRegistion();
/// <summary>
/// 解除事件注册
/// </summary>
void EventDeregistration();
}
在实现上可以这样:
1 #region IUIElement 成员
2 public void EventsRegistion()
3 {
4 this.traineeReport.SelectionChanged += new SelectionChangedEventHandler(traineeReport_SelectionChanged);
5 }
6
7 public void EventDeregistration()
8 {
9 this.traineeReport.SelectionChanged -= new SelectionChangedEventHandler(traineeReport_SelectionChanged);
10 }
11
12 private bool disposed;
13
14 ~TraineePaymentMgr()
15 {
16 ConsoleEx.Log("{0}被销毁", this);
17 Dispose(false);
18 }
19
20 public void Dispose()
21 {
22 ConsoleEx.Log("{0}被手动销毁", this);
23 Dispose(true);
24 GC.SuppressFinalize(this);
25 }
26
27 protected void Dispose(bool disposing)
28 {
29 ConsoleEx.Log("{0}被自动销毁", this);
30 if(!disposed)
31 {
32 if(disposing)
33 {
34 //托管资源释放
35 ((IDisposable)traineeReport).Dispose();
36 ((IDisposable)traineePayment).Dispose();
37 }
38 //非托管资源释放
39 }
40 disposed = true;
41 }
42 #endregion
5. 定时回收垃圾
DispatcherTimer GCTimer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾释放定时器 我定为每十分钟释放一次,大家可根据需要修改
this.GCTimer.start();
this.EventsRegistion(); // 注册事件
}
public void EventsRegistion()
{
this.GCTimer.Tick += new EventHandler(OnGarbageCollection);
}
public void EventDeregistration()
{
this.GCTimer.Tick -= new EventHandler(OnGarbageCollection);
}
void OnGarbageCollection(object sender, EventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
一个图片跟几行代码相比,哪个开销更少肯定不用多说了,而且这几行代码还可以BaseOn进行重用。
<DrawingGroup x:Key="Diagonal_50px">
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF2A2A2A" Geometry="F1 M 0,0L 50,0L 50,50L 0,50 Z"/>
<GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,0L 0,50L 0,25L 25,0L 50,0 Z"/>
<GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,25L 50,50L 25,50L 50,25 Z"/>
</DrawingGroup.Children>
</DrawingGroup>
<DrawingBrush x:Key="FrameListMenuArea_Brush" Stretch="Fill" TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute"
Drawing="{StaticResource Diagonal_50px}"/>
上面几行代码相当于这个:
7. 使用Blend做样式的时候,一定要检查完成的代码
众所周知,Blend定义样式时,产生的垃圾代码还是比较多的,如果使用Blend,一定要检查生成的代码。
8. 静态方法返回诸如List<>等变量的,请使用out
比如
public static List<String> myMothod()
{...}
请改成
public static myMothod(out List<String> result)
{...}
9. 打针对此问题的微软补丁
3.5的应该都有了吧,这里附上NET4的内存泄露补丁地址,下载点这里 (QFE: Hotfix request to implement hotfix KB981107 in .NET 4.0 )
这是官方给的说明,看来在样式和数据绑定部分下了点工夫啊:
- 运行一个包含样式或模板,请参阅通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展应用程序资源的 WPF 应用程序。 创建使用这些样式或模板的多个控件。 但是,这些控件不使用引用的资源。 在这种情况的一些内存WeakReference对象和空间泄漏的控股数组后,垃圾回收释放该控件。
- 运行一个包含的控件的属性是数据绑定到的 WPF 应用程序DependencyObject对象。 该对象的生存期是超过控件的生存期。 许多控件时创建,一些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
- 运行使用树视图控件或控件派生于的 WPF 应用程序,选择器类。 将控件注册为控制中的键盘焦点的内部通知在KeyboardNavigation类。 该应用程序创建这些控件的很多。 例如对于您添加并删除这些控件。 在本例中为某些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
继续更新有关的三个8月补丁,详细的请百度:KB2487367 KB2539634 KB2539636,都是NET4的补丁,在发布程序的时候,把这些补丁全给客户安装了会好的多。
10. 对string怎么使用的建议
这个要解释话就长了,下面仅给个例子说明一下,具体的大家去找找MSDN
string ConcatString(params string[] items)
{
string result = "";
foreach (string item in items)
{
result += item;
}
return result;
}
string ConcatString2(params string[] items)
{
StringBuilder result = new StringBuilder();
for(int i=0, count = items.Count(); i<count; i++)
{
result.Append(items[i]);
}
return result.ToString();
}
11. 其它用上的技术暂时还没想到,再补充...
如果严格按以上操作进行的话,可以得到一个满意的结果:
运行了三十分钟,不断的切换功能,然后休息5分钟,回头一看,结果才17M左右内存开销,效果显著吧。
然后对于调试信息的输出,我的做法是在窗体应用程序中附带一个控制台窗口,输出调试信息,给一个类,方便大家:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace Trainee.UI.UIHelper { public struct COORD { public ushort X; public ushort Y; }; public struct CONSOLE_FONT { public uint index; public COORD dim; }; public static class ConsoleEx { [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)] internal static extern bool AllocConsole(); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)] internal static extern bool SetConsoleFont(IntPtr consoleFont, uint index); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)] internal static extern bool GetConsoleFontInfo(IntPtr hOutput, byte bMaximize, uint count, [In, Out] CONSOLE_FONT[] consoleFont); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)] internal static extern uint GetNumberOfConsoleFonts(); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32", CharSet = CharSet.Auto)] internal static extern COORD GetConsoleFontSize(IntPtr HANDLE, uint DWORD); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32.dll ")] internal static extern IntPtr GetStdHandle(int nStdHandle); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern int GetConsoleTitle(String sb, int capacity); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll", EntryPoint = "UpdateWindow")] internal static extern int UpdateWindow(IntPtr hwnd); [System.Security.SuppressUnmanagedCodeSecurity] [DllImport("user32.dll")] internal static extern IntPtr FindWindow(String sClassName, String sAppName); public static void OpenConsole() { var consoleTitle = "> Debug Console"; AllocConsole(); Console.BackgroundColor = ConsoleColor.Black; Console.ForegroundColor = ConsoleColor.Cyan; Console.WindowWidth = 80; Console.CursorVisible = false; Console.Title = consoleTitle; Console.WriteLine("DEBUG CONSOLE WAIT OUTPUTING...{0} {1} ", DateTime.Now.ToLongTimeString()); try { //这里是改控制台字体大小的,可能会导致异常,在我这个项目中我懒得弄了,如果需要的的话把注释去掉就行了 //IntPtr hwnd = FindWindow(null, consoleTitle); //IntPtr hOut = GetStdHandle(-11); //const uint MAX_FONTS = 40; //uint num_fonts = GetNumberOfConsoleFonts(); //if (num_fonts > MAX_FONTS) num_fonts = MAX_FONTS; //CONSOLE_FONT[] fonts = new CONSOLE_FONT[MAX_FONTS]; //GetConsoleFontInfo(hOut, 0, num_fonts, fonts); //for (var n = 7; n < num_fonts; ++n) //{ // //fonts[n].dim = GetConsoleFontSize(hOut, fonts[n].index); // //if (fonts[n].dim.X == 106 && fonts[n].dim.Y == 33) // //{ // SetConsoleFont(hOut, fonts[n].index); // UpdateWindow(hwnd); // return; // //} //} } catch { } } public static void Log(String format, params object[] args) { Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + format, args); } public static void Log(Object arg) { Console.WriteLine(arg); } } }
在程序启动时,可以用ConsoleEx.OpenConsole()打开控制台,用ConsoleEx.Log(.....)或者干脆用Console.WriteLine进行输出就可以了。
后台操作代码: /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DispatcherTimer timer = new DispatcherTimer(); timer.Interval = new TimeSpan(10000); timer.Tick += new EventHandler(timer_Tick); timer.Start(); } void timer_Tick(object sender, EventArgs e) { main(); inv.Strokes = strokeCollection; } double i = 0, j = 0; StrokeCollection strokeCollection = new StrokeCollection(); private void main() { StylusPoint stylusPoint = new StylusPoint(i,j); StylusPoint stylusPoint1 = new StylusPoint(i++, j++); StylusPointCollection stylusPointCollection = new System.Windows.Input.StylusPointCollection(); stylusPointCollection.Add(stylusPoint); stylusPointCollection.Add(stylusPoint1); DrawingAttributes drawingAttributes = new DrawingAttributes(); drawingAttributes.Color = Color.FromRgb(33,111,0); Stroke stroke = new Stroke(stylusPointCollection, drawingAttributes); strokeCollection.Add(stroke); } }
前台xaml代码:
<Window x:Class="WpfApplication7.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Canvas x:Name="canvas"> <InkCanvas x:Name="inv" Margin="0"/> </Canvas> </Window>
public class ImageViewModel : BaseRegionViewModel { #region 构造器 public ImageViewModel() { } #endregion #region 属性 private BitmapImage _myBitmapImage; public BitmapImage MyBitmapImage { get { return _myBitmapImage; } set { _myBitmapImage = value; this.RaisePropertyChanged("MyBitmapImage"); } } #endregion public override void CurrentElementChanged() { base.CurrentElementChanged(); BitmapImage bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = new FileStream(CurrentElement.Src, FileMode.Open, FileAccess.Read); bitmap.DecodePixelHeight = Convert.ToInt32(CurrentRegion.Height); bitmap.DecodePixelWidth = Convert.ToInt32(CurrentRegion.Width); bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.EndInit(); bitmap.StreamSource.Dispose(); MyBitmapImage = bitmap; } public override void ClearResource() { base.ClearResource(); } }
<Style TargetType="Button" x:Key="btnHotelInfoStyle" > <Setter Property="Focusable" Value="false"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="border" BorderThickness="0" Padding="4,2" BorderBrush="DarkGray" CornerRadius="0" Background="{TemplateBinding Background}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
Step 1 在WPF的C#代码文件中给定义复杂类型的变量,并给其赋值; Sample code: List<User>lsUser=。。。。 Setp 2 在 C#代码对应的XAML 中将此复杂参数定义为资源; Sample code: <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:SDKSample" x:Class="SDKSample.Window1" Width="400" Height="280" Title="MultiBinding Sample"> <Window.Resources> <c:lsUser x:Key="userList"/> ... </Window.Resources> 这里的命名空间 C 是你的复杂参数所在的命名空间; Step 3 <UserControl.Resources> <app:UserManager x:Key="StaticUsers"/> <app:UserNameConverter x:Key="UsrConverter"/> </UserControl.Resources> <TextBox Text="{Binding XXXX,Converter={StaticResource UsrConverter}, ConverterParameter={StaticResource userList }}" /> Step 4 Converter 里对参数的使用 public class UserNameConverter : IValueConverter { public object IValueConverter.Convert(object value, Type targetType,object parameter, CultureInfo culture) { List<User> usrs = parameter as List<User>; ... } }
一、 如果 ObservableCollection<User> 是基础数据,可以将它们作为全局的变量,在 UserNameConverter 中直接使用 ObservableCollection<User> ,用不着使用 ConverterParameter。
如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class UserNameConverter : IValueConverter { public object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture) { // UserManager.Instance.Users 存储了全局的 ObservableCollection<User> // 也可以实现一些方法,用于将“12,34,56”返回“张三,李四,王五” return UserManager.Instance.GetMultiNamesFromIds( value.ToString() ); } public object IValueConverter.ConvertBack( object value, Type targetType, object parameter, CultureInfo culture) { return null ; } } |
二、 如果 ObservableCollection<User> 不能作为全局的,个人推荐的方式是避开使用将 ObservableCollection<User> 作为 ConverterParameter 的方法,改为使用字符串或 enum
参数,如
1
2
3
4
5
6
7
8
9
|
public class UserNameConverter : IValueConverter { public object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture) { // UserManager.Instance 中存储了多个 ObservableCollection<User> ,根据参数返回不同的 ObservableCollection<User> // parameter 是一个字符串或者是一个 enum return UserManager.Instance.GetMultiNamesFromIds( parameter.ToString(), value.ToString() ); } } |
三、 如果 ObservableCollection<User> 不能作为全局的,而又非要把它通过ConverterParameter 来传递, 由于在 XAML 中只能使用 StaticResources ,那么就只能在代码中来给 StaticResources 赋值了:
1
2
3
4
5
6
|
< UserControl.Resources > < app:UserManager x:Key = "StaticUsers" /> < app:UserNameConverter x:Key = "UsrConverter" /> </ UserControl.Resources > < TextBox Text="{Binding XXXX,Converter={StaticResource UsrConverter}, ConverterParameter={StaticResource StaticUsers}}" /> |
1
2
3
4
5
6
7
8
|
public class UserNameConverter : IValueConverter { public object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture) { ObservableCollection<User> usrs = parameter as ObservableCollection<User>; ... } } |
1
2
3
4
|
public class UserManager : ObservableCollection<User> { } |
在 UserControl 的 .cs 代码中(一般在是构造函数中):
1
2
|
UserManager = this .Resources[ "StaticUsers" ] as UserManager ; UserManager.Add(User 实例); |
四、 个人喜好是不会使用上述 “三”的 UserManager 定义,
不使用继承,而是使用组合
1
2
3
4
|
public class UserManager { public ObservableCollection<User> UserList{ get ; set ;} } |
1
2
3
4
5
6
|
< UserControl.Resources > < app:UserManager x:Key = "StaticUsers" /> < app:UserNameConverter x:Key = "UsrConverter" /> </ UserControl.Resources > < TextBox Text="{Binding XXXX,Converter={StaticResource UsrConverter}, ConverterParameter={StaticResource StaticUsers.UserList}}" /> |
不过好像又记得在 StaticResource 中是不能使用 属性路径 的,即 StaticUsers.UserList 是不能用的,
忘了啦。
PS:以上代码全手写,仅作思路参考之用。
Sorry , 发完贴又看了一遍,上述第三步有误,不用在 XAML 中定义 StaticUsers 的,在.cs代码中定义即可:
1
2
3
4
5
|
< UserControl.Resources > < app:UserNameConverter x:Key = "UsrConverter" /> </ UserControl.Resources > < TextBox Text="{Binding XXXX,Converter={StaticResource UsrConverter}, ConverterParameter={StaticResource StaticUsers}}" /> |
1
2
3
4
5
|
//在构造函数的 InitializeComponent(); 之前执行 ObservableCollection<User> usrs = ... 通过各种途径得到 ObservableCollection<User> ; this .Resources.Add( "StaticUsers" , usrs ); InitializeComponent(); |
微软搞了一个ConverterParameter,我想学习用一下这个东东而已,而且觉得静态变量不是很好那种...
public class IUserNameConverter : IValueConverter
{
public static ObservableCollection<User> TempUserList;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
}
}
------------------------------
恩,还是sunpire厉害啊
我上网找了半天,就是没有找到
this.Resources.Add("StaticUsers", usrs );
这一句关键代码啊
不过可惜的是这句代码一定要在构造函数中写,问题是实际代码没看到过这样写的,
InitializeComponent()肯定早运行过了,
实际业务数据肯定是从服务器去取的,我总不能为了搞这个,从服务器取业务数据都写在InitializeComponent前面吧
也就是说ConverterParameter更多的只具有观赏价值,没有太多实用价值。
简单的说,复杂一点的转换器参数传递最好使用全局静态变量,避免使用ConverterParameter这个东西。
恩,明白,结贴。
//color转为brush: Brush br = new SolidColorBrush(Color.FromRgb(0,0,0)); //string转Color (Color)ColorConverter.ConvertFromString((string)str); //Color转string((Color)value).ToString(); string和Brush的转换 Brush color = newSolidColorBrush((Color)ColorConverter.ConvertFromString((string)str)); //Brush转string ((Brush)value).ToString(); //string转byte[] System.Text.UnicodeEncoding converter = newSystem.Text.UnicodeEncoding(); byte[] stringBytes = converter.GetBytes(inputString); //byte[]转string System.Text.UnicodeEncoding converter = newSystem.Text.UnicodeEncoding(); stringoutputString = converter.GetString(stringByte); 1.由string的rgb数值"255,255,0"转换为color { string[] color_params = e.Parameter.ToString().Split(','); byte color_R = Convert.ToByte(color_params[0]); byte color_G = Convert.ToByte(color_params[1]); byte color_B = Convert.ToByte(color_params[2]); } 2.由颜色名称字符串("black") 转化为color { //ColorConverter c = new ColorConverter(); //object obj = c.ConvertFrom(); //Color color = (Color)obj; Color color = Color.FromRgb(color_R, color_G, color_B); } 3.将blend的 8位颜色值转为color /// <summary> /// 将blend的8位颜色值转为color /// </summary> /// <param name="colorName"></param> /// <returns></returns> public Color ToColor(string colorName) { if (colorName.StartsWith("#")) colorName = colorName.Replace("#", string.Empty); int v = int.Parse(colorName, System.Globalization.NumberStyles.HexNumber); return new Color() { A = Convert.ToByte((v >> 24) & 255), R = Convert.ToByte((v >> 16) & 255), G = Convert.ToByte((v >> 8) & 255), B = Convert.ToByte((v >> 0) & 255) }; }
public partial class ImageView : UserControl { ImageViewModel imageViewModel; public ImageView() { InitializeComponent(); imageViewModel = this.DataContext as ImageViewModel; BindingBitmap(); } private void BindingBitmap() { Binding binding = new Binding(); binding.Source = imageViewModel; binding.Path = new PropertyPath("MyBitmapImage"); BindingOperations.SetBinding(this.img, Image.SourceProperty, binding); } }
public class VerticalTextBlock : Control { public VerticalTextBlock() { IsTabStop = false; var templateXaml = @"<ControlTemplate " + #if SILVERLIGHT "xmlns='http://schemas.microsoft.com/client/2007' " + #else "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " + #endif "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" + "<Grid Background="{TemplateBinding Background}">" + "<TextBlock x:Name="TextBlock" TextAlignment="Center"/>" + "</Grid>" + "</ControlTemplate>"; #if SILVERLIGHT Template = (ControlTemplate)XamlReader.Load(templateXaml); #else using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(templateXaml))) { Template = (ControlTemplate)XamlReader.Load(stream); } #endif } public override void OnApplyTemplate() { base.OnApplyTemplate(); _textBlock = GetTemplateChild("TextBlock") as TextBlock; CreateVerticalText(_text); } private string _text { get; set; } private TextBlock _textBlock { get; set; } public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(string), typeof(VerticalTextBlock), new PropertyMetadata(OnTextChanged)); private static void OnTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((VerticalTextBlock)o).OnTextChanged((string)(e.NewValue)); } private void OnTextChanged(string newValue) { CreateVerticalText(newValue); } private void CreateVerticalText(string text) { _text = text; if (null != _textBlock) { _textBlock.Inlines.Clear(); //清楚一遍数据,防止两条消息来临的时候发生错误 bool first = true; foreach (var c in _text) { if (!first) { _textBlock.Inlines.Add(new LineBreak()); } _textBlock.Inlines.Add(new Run { Text = c.ToString() }); first = false; } } } }
1.MouseButtonEventArgs 类定义在 System.Windows.Input命名空间中。含有方法GetPosition方法,此方法返回一个Point类型(这是定义在System.Windows命名空间内的结构体类型)的对象,表示鼠标坐标(相对于GetPosition参数的左上角)。含有ChangedButton属性,此属性“获取以该事件关联的按钮”(也就是是鼠标的左键,右键等)。
2.Button的 IsDefault 属性设置为true表明此按钮和Enter键关联;IsCancel 属性设置为true表明此按钮和Esc键关联。
如果给Button设置快捷键则可以如下:
<Button Name="btnEnter" Content="查询(_Q)" Width="200" Height="100" ></Button> 下划线加字母代表快捷键为Alt+Q。
但是如果Button在ToolBar里那么上面这种写法不行,须写为:
<ToolBar>
<Button Name="btnWantCreate">
<AccessText>新增(_N)</AccessText>
</Button>
</ToolBar>
3.图片属性里德生成操作设置为SplashScreen,这样在显示自己程序前会先显示这个图片。
4.WPF中. 在Toolbar中连续使用Tab键时,其会循环与其中.如何跳出该循环呢, 很简单, 将TabBar的TabNavigation属性设置为Continue就可以了。
<ToolBar KeyboardNavigation.TabNavigation="Continue"></ToolBar>
5.在XAML里限定泛型的类型(http://msdn.microsoft.com/zh-cn/library/ms750476.aspx):
假定声明了以下 XAML 命名空间定义:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib"
List<字符串>
<scg:List x:TypeArguments="sys:String" ...> 实例化新的带有两个 String 类型参数的 List<T>。
Dictionary<字符串,字符串>
<scg:Dictionary x:TypeArguments="sys:String,sys:String" ...> 实例化新的带有两个 String 类型参数的 Dictionary<TKey, TValue>。
6.程序的Icon设置: 右键项目选择属性-->应用程序-->资源-->图标和清单-->图标 选中自己想要设置的Icon
Windows Presentation Foundation (WPF) 独立应用程序具有两种类型的图标:
一个程序集图标,此图标是在应用程序的项目生成文件中使用 <ApplicationIcon> 属性指定的。 此图标用作程序集的桌面图标。
注意:When debugging in Visual Studio, your icon may not appear due to the hosting process. If you run the executable, the icon will appear. 有关更多信息,请参见承载进程 (vshost.exe)。当调试的时候不显示图标,开始运行不调试时才显示.
每个窗口各自具有的一个图标,此图标是通过设置 Icon 指定的。 对于每个窗口,此图标在窗口的标题栏、任务栏按钮和 Alt-Tab 应用程序选择列表项中使用.
WPF 窗口总是显示一个图标。 如果没有通过设置 Icon 提供图标,WPF 将基于下列规则选择要显示的图标:
1. 使用程序集图标(如果已指定)。
2. 如果未指定程序集图标,则使用默认的 Microsoft Windows 图标。
如果使用 Icon 指定自定义窗口图标,可以通过将 Icon 设置为 null 来还原默认的应用程序图标。