C#中的throw

Throw会抛出/传递异常,通过在catch块里使用throw语句.可以改变产生的异常,比如我们可以抛出一个新的异常,throw语句有各种各样的,并且很有必要. 例子 我们首先看一下三个方法,分别叫做A,B,C,他们使用不同的throw语句。方法A使用了无参的throw语句。这可以被看作是rethrow(继续抛出)—他会抛出已经出现的同样的异常 继续,方法B throw一个命名的异常变量。这就不是一个完全的rethrow了—因为他虽然抛出了同样的异常。但是改变了StackTrace(堆栈轨迹),如果有必要的话,我们可以收集一些异常信息,而方法C则创建了一个新的异常。 提示:你可以通过这种方法实现自定义的的错误处理 使用throw语句的例子 using System; class Program { static void Main() { try { A(); B(); C(null); } catch (Exception ex) { Console.WriteLine(ex); } } static void A() { // Rethrow 语法. try { int value = 1 / int.Parse("0"); } catch { throw; } } static void B() { // 过滤异常类型. try { int value = 1 / int.Parse("0"); } catch (DivideByZeroException ex) { throw ex; } } static void C(string value) { // 创建新的异常. if (value == null) { throw new ArgumentNullException("value"); } } } 程序可能的输出结果 System.DivideByZeroException: Attempted to divide by zero. System.DivideByZeroException: Attempted to divide by zero. System.ArgumentNullException: Value cannot be null. Parameter name: value Rethrow 接着我们看更多的关于rethrows的细节。Rethrow必须是一个无参的throw语句。如果使用throw ex,那么TargetSie(TargetSite 从堆栈跟踪中获取抛出该异常的方法。如果堆栈跟踪为空引用,TargetSite 也返回空引用。-译者注)和StackTrace都被改变了。 在下面的程序里,X()方法使用了rethrow语句。Y()使用了throw ex语句。我们可以看看当rethrow语句使用的使用,引发异常的方法,也就是异常的TargetSite是在StringToNumber—一个int.Parse内部的方法。 但是:当throw ex用的时候。就像在Y()里面,这个异常的TargetSite被修改到了当前的Y()方法里。 测试rethrow的例子 using System; class Program { static void Main() { try { X(); } catch (Exception ex) { Console.WriteLine(ex.TargetSite); } try { Y(); } catch (Exception ex) { Console.WriteLine(ex.TargetSite); } } static void X() { try { int.Parse("?"); } catch (Exception) { throw; // [Rethrow 构造] } } static void Y() { try { int.Parse("?"); } catch (Exception ex) { throw ex; // [Throw 捕获的ex变量] } } } 输出 ...

2012-11-18 · 1 min · bystander

理解并实现模板模式

介绍 本文实现模板模式 背景 有时候我们需要做很多任务,而做这些任务的算法可能不同,这样可以设计成策略模式,这样。执行该任务的基本的一些代码就是一样的。但程序可可以动态的切换来执行任务的不同部分了。 现在,真实的情况是有些算法,从实现层面山看,有可能有一些步骤是不一样的,这种情况下。我们可以使用继承来完成。 当有个算法,而这个算法的一部分却多样的时候。使用模板模式就很好。GoF定义模板模式为: “Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.”. 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 在上面的类图中: AbstractClass:包含两种方法。第一种就是算法的每一步。另一种就是模板方法。模板方法就是那些可以被用在所有独立方法中。并且提供了算法执行的一个骨架 ConcreteClass:这个类重写了抽象类中每一步的方法,包含对这些步骤的个性化实现。 使用代码 看一个简单的例子。假想我们有一个类用来读取数据。并且能够为信息管理系统到处数据。 abstract class DataExporter { // 这个方法都是一致的 public void ReadData() { Console.WriteLine("Reading the data from SqlServer"); } // 当报表格式顶的时候这个也是定的。 public void FormatData() { Console.WriteLine("Formating the data as per requriements."); } // 目标文件类型的不同导致该方法不同 public abstract void ExportData(); // 这是客户端可能使用的模板方法 public void ExportFormatedData() { this.ReadData(); this.FormatData(); this.ExportData(); } } ReadData和FormatData 的实现不会变。唯一可变的部分就是ExportData方法。该方法对于不同的导出类型不同。如果我们要导出excel文件。我们要实现一个ConcreteClass的实现。 class ExcelExporter : DataExporter { public override void ExportData() { Console.WriteLine("Exporting the data to an Excel file."); } } 同样如果要导出PDF文件。重写这部分即可 class PDFExporter : DataExporter { public override void ExportData() { Console.WriteLine("Exporting the data to a PDF file."); } } 好处就是客户端可以使用DataExporter类,而具体的实现是在派生类中的 static void Main(string[] args) { DataExporter exporter = null; //导出 Excel文件 exporter = new ExcelExporter(); exporter.ExportFormatedData(); Console.WriteLine(); // 导出 PDF 文件 exporter = new PDFExporter(); exporter.ExportFormatedData(); } 运行时。对算法的调用将会执行真正请求的派生类的方法。 看一下我们的类图 ...

2012-10-25 · 1 min · bystander

理解并实现外观设计模式

介绍 本文介绍外观模式,并给出简单的实现示例 背景 写软件的时候,有时候需要处理一系列的对象来完成一个确定的任务.比如,我们给一个万能遥控器写代码,我们需要关掉所有的设备,那么,我们有这样几种选择.第一个就是手动选择每一个设备,然后一个接一个的关闭,这好傻.那我们为什么不再遥控器上放一个按钮,我们按一下就关掉了.按钮的命令会与设备控制器通信然后关掉他们. 如果我们又想在晚上12的时候自动关闭设备,那么我们就会有一个基于事件的计时器,与设备通信,然后关闭设备,问题是在两种情况下我们都需要与这些对象通信的函数. 有很多方法解决这个问题,为什么不能有一个对象,该对象的责任就是关闭设备,当我要关闭设备的时候,我调用该对象就行了.这也是外观模式的理念Gof大神定义外观模式 “Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.” 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 看看模式图 注意外观对象仅仅是提供了对函数一起操作,.不能替换子系统的接口.子系统的类仍然可以被系统的其他部分访问.外观为子系统提供了一致的界面. 使用代码 为了模拟外观模式,我们模拟一个小例子.试着实现一个简单的外观对象,该外观对象操作一些WP手机的控制器对象,我们先定义问题 每天早上我跑步的时候,我都得对我的手机做出以下的事情.. 1. 关闭wifi 2. 切换到移动网络 3. 打开GPS 4. 打开音乐 5. 开始跑步追踪器 跑完以后.,我又蛋疼的做出以下几件事 1. 在twitter和facebook上分享我的跑步数据 2. 关闭跑步追踪器 3. 关闭音乐 4. 关闭GPS 5. 关闭移动数据 6. 打开wifi 目前我都是手工做的.,我们来实现这些假想的控制器类吧. class GPSController { bool isSwitchedOn = false; public bool IsSwitchedOn { get { return isSwitchedOn; } set { isSwitchedOn = value; DisplayStatus(); } } private void DisplayStatus() { string status = (isSwitchedOn == true) ? "ON" : "OFF"; Console.WriteLine("GPS Switched {0}", status); } } 其他的像MobileDataController, MusicController, WifiController 代码都是基本的一样的. 然后模拟一下跑步追踪器这个app class SportsTrackerApp { public void Start() { Console.WriteLine("Sports Tracker App STARTED"); } public void Stop() { Console.WriteLine("Sports Tracker App STOPPED"); } public void Share() { Console.WriteLine("Sports Tracker: Stats shared on twitter and facebook."); } } 下面模拟一下我的手工过程 ...

2012-10-23 · 3 min · bystander

理解并实现装饰器模式

背景 本文讨论装饰器模式,这个模式是因为很多情况下需要动态的给对象添加功能.比如我们创建了一个Stream类.后来需要对这个数据流类动态的添加一个加密功能.有人可能说把加密方法写到流类里面啊.然后使用一个bool变量来控制开关就行了.但是这样.这个加密方法只能写一种..如果用派生类来实现.那么..对于不同的加密方法.,都要创建一个子类,举个例子.比如有时候是一些函数的组合.我们最终的派生类的数目基本上就和排列组合的数目一样了. 我们使用装饰器模式来解决这个问题.GoF描述为 “Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.” 首先看一下图.理解一下这个模式中每一个类的作用 • Component:定义了可以动态添加功能的具体类ConcreteComponents的接口. • ConcreteComponent: 可以动态添加功能的具体类 • Decorator: 定义了动态添加到ConcreteComponent类中的功能的接口 • ConcreteDecorator: 可以添加到 ConcreteComponent.中的具体功能类. 使用代码 我们开一个面包店的例子.面包店卖蛋糕和甜点.客户可以买蛋糕和甜点,同时添加一些额外的东西.额外的东西包括奶油(Cream),樱桃(Cherry),香料(Scent)和会员(Name Card) 如果我们用派生类来实现..那么我们会有如下的类 • CakeOnly • CakeWithCreamAndCherry • CakeWithCreamAndCherryAndScent • CakeWithCreamAndCherryAndScentAndNameCard • CakeWithCherryOnly • PastryOnly • PastryWithCreamAndCherry • PastryWithCreamAndCherryAndScent • PastryWithCreamAndCherryAndScentAndNameCard • PastryWithCherryOnly • 等等等等 这简直就是噩梦..我们用装饰器模式来实现把. 首先定义Component 接口 public abstract class BakeryComponent { public abstract string GetName(); public abstract double GetPrice(); } 前面说过了.这个类定义了能够动态添加功能的具体类(ConcreteComponents)的接口,好吧.然后来创建具体类ConcreteComponents class CakeBase : BakeryComponent { // 真实世界里,这个值应该来自数据库等 private string m_Name = "Cake Base"; private double m_Price = 200.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } } class PastryBase : BakeryComponent { //真实世界里,这个值应该来自数据库等 private string m_Name = "Pastry Base"; private double m_Price = 20.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } } 现在基对象准备好了.看看那些可以被动态添加的功能.我们看看Decorator 类 ...

2012-10-22 · 2 min · bystander

理解并实现原型模式-实现ICloneable接口.理解深浅拷贝

本文用C#实现原型模式,也会讨论深浅拷贝,已经如何在.net中高效实现ICloneable 接口 介绍 有时候我们需要从上下文得到一个对象的拷贝,然后通过一些独立的操作来处理他。原型模式在这种情况下很适用 GoF 定义原型模式为用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype." 看一下类图 主要的参与者有 • Prototype: 抽象类或接口,定义了方法来拷贝自己 • ConcretePrototype: 克隆的具体类. • Client: 需要执行拷贝对象的软件对象 然后实现吧 使用代码 为了简化。我以一个著名的偷车游戏作为例子 我们说游戏里有一个注脚。这个主要有着一些定义游戏数据的统计量。保存游戏的时候我们就需要拷贝这个对象,然后序列化到文件中。(仅仅是举个例子,真实的游戏里很少这样做) 下面这个类抽象类就是概念中的Prototype public abstract class AProtagonist { int m_health; int m_felony; double m_money; public int Health { get { return m_health; } set { m_health = value; } } public int Felony { get { return m_felony; } set { m_felony = value; } } public double Money { get { return m_money; } set { m_money = value; } } public abstract AProtagonist Clone(); } 接口定义了玩家重要的信息,然后定义了一个Clone方法。然后我们定义一个具体的玩家类CJ。这样我们可以克隆当前对象,然后异步的进行序列化 class CJ : AProtagonist { public override AProtagonist Clone() { return this.MemberwiseClone() as AProtagonist; } } 这个类就是概念中的ConcretePrototype 。这里为了简化也没有其他一些方法了。 现在看看客户端软件的写法 static void Main(string[] args) { // 演示原型模式 CJ player = new CJ(); player.Health = 1; player.Felony = 10; player.Money = 2.0; Console.WriteLine("Original Player stats:"); Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", player.Health.ToString(), player.Felony.ToString(), player.Money.ToString()); // 这里是拷贝部分. CJ playerToSave = player.Clone() as CJ; Console.WriteLine("\nCopy of player to save on disk:"); Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", playerToSave.Health.ToString(), playerToSave.Felony.ToString(), playerToSave.Money.ToString()); } ...

2012-10-19 · 3 min · bystander

实现IEnumerable接口&理解yield关键字

本文讨论题目的内容。然后讨论IEnumerable接口如何使得foreach语句可以使用。之后会展示如果实现自定义的集合类,该集合类实现了IEnumerable接口。Yield关键字和遍历集合后面也讨论。 背景 一使用集合。就发现遍历集合就跟着来了。遍历集合最好的方式是实现迭代器模式-Understanding and Implementing the Iterator Pattern in C# and C++(这篇文章我过几天翻译一下) ,C#提供foreach来以一种优雅的方式遍历 只要集合实现了IEnumerable 接口就可以用foreach来遍历。 使用代码 首先先看一下内置的集合类如何使用foreach来遍历的。ArrayList实现了IEnumerable 接口。我们看一下 // 看一下实现了IEnumerable 接口的集合如何遍历 ArrayList list = new ArrayList(); list.Add("1"); list.Add(2); list.Add("3"); list.Add('4'); foreach (object s in list) { Console.WriteLine(s); } 遍历泛型集合类 Arraylist 是一个通用集合类,遍历泛型集合类也可以。因为这些泛型集合类实现了IEnumerable<T>接口,看一下吧。 // 遍历实现了IEnumerable<T>接口的泛型类 List<string> listOfStrings = new List<string>(); listOfStrings.Add("one"); listOfStrings.Add("two"); listOfStrings.Add("three"); listOfStrings.Add("four"); foreach (string s in listOfStrings) { Console.WriteLine(s); } 发现了吧。我们自定义的集合类或是泛型集合类应该实现IEnumerable和IEnumerable<T>接口。这样就可以遍历了。 理解yield关键字 在写个实现接口的例子之前,先理解一下yield关键字,yield会记录集合位置。当从一个函数返回一个值的时候,yield可以用。 如下的普通的方法。不论调用多少次,都只会返回一个return static int SimpleReturn() { return 1; return 2; return 3; } static void Main(string[] args) { // 看看 Console.WriteLine(SimpleReturn()); Console.WriteLine(SimpleReturn()); Console.WriteLine(SimpleReturn()); Console.WriteLine(SimpleReturn()); } 原因就是普通的return语句不保留函数的返回状态。每一次都是新的调用。然后返回第一个值。 但是使用下面的语句替换后就不一样。当函数第二次调用的时候。会从上次返回的地方继续调用 static IEnumerable<int> YieldReturn() { yield return 1; yield return 2; yield return 3; } static void Main(string[] args) { // 看看yield return的效果 foreach (int i in YieldReturn()) { Console.WriteLine(i); } } 显然返回1,2,3,唯一要注意的就是函数需要返回IEnumerable。,然后通过foreach调用。 在自定义的集合类里实现Ienumerable接口 现在如果我们在我们的自定义集合里定义一个方法。来迭代所有元素。然后通过使用yield返回。我们就可以成功了。 好。我们定义MyArrayList 类,实现IEnumerable 接口,该接口就会强制我们实现GetEnumerator 函数。这里我们就要使用yield了。 class MyArrayList : IEnumerable { object[] m_Items = null; int freeIndex = 0; public MyArrayList() { // 对了方便我直接用数组了,其实应该用链表 m_Items = new object[100]; } public void Add(object item) { // 考虑添加元素的时候 m_Items[freeIndex] = item; freeIndex++; } // IEnumerable 函数 public IEnumerator GetEnumerator() { foreach (object o in m_Items) { // 检查是否到了末尾。数组的话。。。没写好 if(o == null) { break; } // 返回当前元素。然后前进一步 yield return o; } } } ...

2012-10-19 · 2 min · bystander

模拟Office2010文件菜单的TabControl模板

这是Office2010中的文件菜单点开后的效果。本文我将以强大的WPF来实现类似的效果。希望你能有所收获。而不是只拷贝/粘贴代码而已。 开始之前。先把TabControl找个地方放着。 <Window x:Class="TestClient.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"> <TabControl Name="tabSteps"> <TabItem Header="Info" IsSelected="True"> <TextBlock>Info content</TextBlock> </TabItem> <TabItem Header="Recent"> <TextBlock>Recent content tab</TextBlock> </TabItem> <TabItem Header="New"> <TextBlock>New content tab</TextBlock> </TabItem> <TabItem Header="Print"> <TextBlock>Print content tab</TextBlock> </TabItem> <TabItem Header="Save &amp; Send"> <TextBlock>Save &amp; send content tab</TextBlock> </TabItem> <TabItem Header="Help"> <TextBlock>Help tab</TextBlock> </TabItem> </TabControl> </Window> 然后会大概是这个效果 为了改变TabControl的显示效果。我们使用模板机制,我们把模板写进一个资源字典里。这样就可以重用了。添加一个资源字典的步骤如下 右键点击工程-添加-资源字典 然后在资源字典里添加一些代码。 <ControlTemplate x:Key="OfficeTabControl" TargetType="{x:Type TabControl}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="160" /> <ColumnDefinition/> </Grid.ColumnDefinitions> <Border Background="#FFE9ECEF" Grid.Column="0" BorderBrush="LightGray" BorderThickness="1" SnapsToDevicePixels="True" /> <StackPanel IsItemsHost="True" Grid.Column="0" Margin="0,0,-1,0" SnapsToDevicePixels="True" /> <ContentPresenter Content="{TemplateBinding SelectedContent}" Grid.Column="1" Margin="15,0,0,0" /> </Grid> </ControlTemplate> 这样就添加了一个有一个grid元素的名为OfficeTabControl的控件模板 . Grid 被分成两列,一列是标签页,一列是页内容。左边的列包含一个灰色背景和亮灰色的边缘线,然后一个StackPanel,IsItemsHost属性被设置为true, 这样标签项被会放在这个栈面板里。第二列是ContentPresenter 这会放置标签页内容。然后让我们前面的TabControl使用新模板。设置Template 属性。 <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="OfficeTab.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <TabControl Name="tabSteps" Template="{StaticResource OfficeTabControl}"> 在这之前,先把资源字典加到窗体的Reesouce里。然后再设置。然后运行软件。效果会有一些不一样。 然后要修改左侧单个标签的显示效果。通过改变模板来实现。给模板添加如下的代码 <ControlTemplate x:Key="OfficeTabControl" TargetType="{x:Type TabControl}"> <ControlTemplate.Resources> <Style TargetType="{x:Type TabItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabItem}"> <Grid SnapsToDevicePixels="True"> <ContentPresenter Name="buttonText" Margin="15,0,5,0" TextBlock.FontFamily="Calibri" TextBlock.FontSize="12pt" TextBlock.Foreground="Black" Content="{TemplateBinding Header}" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ControlTemplate.Resources> 然后再运行 VisualState很有意思。我们可以放在Grid里。然后设置正常状态和鼠标悬停的状态。 为了添加鼠标悬停效果,我们添加两个Borders元素。一个右边缘是灰线,另一个用在背景上。亮蓝色放在上下边缘 <Border Name="hoverShape" Height="40" Margin="0,0,1,0" SnapsToDevicePixels="True" BorderThickness="0,0,1,0" BorderBrush="LightGray"> <Border BorderBrush="#FFA1B7EA" BorderThickness="0,1" Background="#FFE5EEF9" Height="40" SnapsToDevicePixels="True" /> </Border> 之后,我们为VisualState创建故事板,一个是正常状态。会使得hoverShape的透明度为0.另一个是鼠标悬停的状态。透明度会变成1 <Grid SnapsToDevicePixels="True"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="CommonStates"> <VisualState Name="MouseOver"> <Storyboard> <DoubleAnimation Storyboard.TargetName="hoverShape" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:.1"/> </Storyboard> </VisualState> <VisualState Name="Normal"> <Storyboard> <DoubleAnimation Storyboard.TargetName="hoverShape" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:.1"/> </Storyboard> </VisualState> </VisualStateGroup> 之后效果如下 ...

2012-10-17 · 2 min · bystander

类型安全的黑板模式(属性包)

有时候对于对象来说。在一个软件中,不直接通过互相引用而做到共享信息是非常有用的。比如像带有插件的软件。可以互相进行通信。假设我们有了很多对象。其中一些包含一些数据。而另一些对象需要消费这些数据 不同的子集,我们不通过对数据生产者和消费者的直接引用来实现,而是通过更低耦合的方式。叫做创建一个“BlackBoard”(黑板)对象。该对象允许其他对象自由对其进行读取/写入数据。这种解耦方式使得消费者不知道也不必知道数据来自哪里。如果想要了解更多关于黑板模式的信息。我们常说的。Google是你最好的朋友。 一个最简单的黑板对象应该是 Dictionary一些简单的命名值的字典。所有的对象共享同一个字典引用。使得他们可以交换这些命名数据。这种方法有两个问题。一个是名字。一个是类型安全—数据生产者和消费者对每一个数据值都必须共享一个字符串标识。消费者也没有对字典中的值进行编译时的类型检查,比如,可能期望一个小数,结果运行时读到了字符串。本文对这两个问题演示了一种解决方案。 背景 最近我在开发一个通用任务的异步执行的引擎。我的通用任务通常有Do/Undo方法。原则上是相互独立的,但是有一些任务需要从已经执行的任务重请求数据。比如。一个任务可以 为一个硬件设备建立一个API,随后的任务就可以使用创建好的API来操作硬件设备。但是。我不想我的执行引擎知道关于这个执行任务的任何信息。而且。我也不想直接手工的就在一个任务里引用另一个任务。 黑板类 黑板类本质上是一个Dictionary的包装类,对外暴露Get和Set方法。黑板类允许其他对象存储并且取回数据。但是要求这些数据使用一个 BlackboardProperty 类型的标识符来表示这些数据是可存取的。BlackboardProperty 对象应该在那些准备读写黑板类的对象之间共享,因此,他应该在那些类中作为一个静态成员。(很像WPF的依赖属性。是他们所属控件的静态成员) 注意:命名安全应该可以通过同样的方式实现。但是但是依然没有解决类型安全的问题。那么。到了主要的部分了。那就是黑板类的代码了 public class Blackboard : INotifyPropertyChanged, INotifyPropertyChanging { Dictionary<string, object> _dict = new Dictionary<string, object>(); public T Get<T>(BlackboardProperty<T> property) { if (!_dict.ContainsKey(property.Name)) _dict[property.Name] = property.GetDefault(); return (T)_dict[property.Name]; } public void Set<T>(BlackboardProperty<T> property, T value) { OnPropertyChanging(property.Name); _dict[property.Name] = value; OnPropertyChanged(property.Name); } #region property change notification public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanging(string propertyName) { if (PropertyChanging != null) PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #endregion } 黑板属性(BlackBoardProperty)类 BlackBoardProperty 类 提供了一个标识符来存取黑板对象中的数据。定义了名称和值的类型。也定义了一个默认的返回值。以防黑板类中对应属性没有值。 /// <summary> /// 对应黑板类中的属性的强类型标识符 /// </summary> /// <typeparam name="T">该类能识别的属性值的类型</typeparam> public class BlackboardProperty<T> { /// <summary> /// 属性的名称 /// <remarks> /// 黑板类的属性通过名称来存储。请注意不要让相同的名字有不同的属性值。因为如果被用在同样的黑板类上。他们会互相覆盖值 /// </remarks> /// </summary> public string Name { get; set; } //当黑板对象没有包含对应属性的时候。该工厂方法被用来提供一个默认的值 // Func<T> _createDefaultValueFunc; public BlackboardProperty(string name) : this(name, default(T)) { } /// <summary> /// /// </summary> /// <param name="name"></param> /// <param name="defaultValue"> /// 当黑板类不包括该属性的时候。该值会被返回。 /// <remarks> /// 如果缺省的值是一个常量或是一个值类型的时候,使用该构造方法。 /// </remarks> /// </param> public BlackboardProperty(string name, T defaultValue) { Name = name; _createDefaultValueFunc = () => defaultValue; } /// <summary> /// </summary> /// <remarks> /// 如果缺省值是一个引用类型,并且,你不想要共享该实例给多个黑板对象的时候。请使用该 /// 构造函数 /// </remarks> /// <param name="name"></param> /// <param name="createDefaultValueFunc"></param> public BlackboardProperty(string name, Func<T> createDefaultValueFunc) { Name = name; _createDefaultValueFunc = createDefaultValueFunc; } public BlackboardProperty() { Name = Guid.NewGuid().ToString(); } public T GetDefault() { return _createDefaultValueFunc(); } } 我承认不是非常有用的代码。但是。能够模拟两个类的使用。 下一个例子会更和现实情况接近。但是肯定是被简化过了的。在下面的例子里。我定义了集中不同的任务。我用这些任务来启动对硬件设备的连接。操作设备。关闭连接。这些任务通过一个执行引擎依次执行,这些任务通过一个公用的黑板类来共享数据。至于这个任务类的和执行引擎(ExecutionEngine)类还是留到另一篇文章中把。 ...

2012-10-16 · 3 min · bystander

如何创建WPF用户控件&在WPF项目中使用

作者给的Demo我合并了下。VS2010直接打开解决方案。二者都有。 介绍 本文展示在WPF中如何创建用户控件并且如果在WPF项目中使用。我将使用VS2008和C#来展示如何创建一个自定义的ToolTip 背景 这篇由Sacha Barber.写的和我的有点像。 使用代码 开始。首先,我们创建一个用户控件。因此,我们选择新建WPF用户控件类库(WPF User Control Library)。 现在。我们可以创建或者编辑XAML代码来创建自定义的用户控件了。我使用XAML来创建自定义的ToolTip。你想做什么随你。 <UserControl Name="UserControlToolTip" x:Class="CustomToolTip.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" RenderTransformOrigin="0,0" HorizontalAlignment="Left" VerticalAlignment="Top" > <UserControl.RenderTransform> <TransformGroup> <ScaleTransform ScaleX="1" ScaleY="1"/> <SkewTransform AngleX="0" AngleY="0"/> <RotateTransform Angle="0"/> <TranslateTransform x:Name="UserControlToolTipXY" X="0" Y="0"/> </TransformGroup> </UserControl.RenderTransform> <Grid HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="200" MinHeight="120"> <Grid.RowDefinitions> <RowDefinition Height="0.333*"/> <RowDefinition Height="0.667*"/> </Grid.RowDefinitions> <Rectangle Fill="#FFFBFBFB" Stroke="#FF000000" RadiusX="10" RadiusY="10" RenderTransformOrigin="0.139,0.012" StrokeThickness="1" Grid.RowSpan="2"> <Rectangle.BitmapEffect> <DropShadowBitmapEffect Opacity="0.8"/> </Rectangle.BitmapEffect> </Rectangle> <Rectangle RadiusX="10" RadiusY="10" RenderTransformOrigin="0.139,0.012" StrokeThickness="10" Stroke="{x:Null}" Margin="1,1,1,1" Grid.Row="0" Grid.RowSpan="2"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0.725"> <GradientStop Color="#00E6D9AA" Offset="0.487"/> <GradientStop Color="#FFE6D9AA" Offset="0.996"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Rectangle RadiusX="10" RadiusY="10" RenderTransformOrigin="0.493,0.485" StrokeThickness="10" Stroke="{x:Null}" Grid.RowSpan="2" Margin="1,1,1,1"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0.014,0.5" StartPoint="0.211,0.5"> <GradientStop Color="#00E6D9AA" Offset="0.513"/> <GradientStop Color="#FFE6D9AA" Offset="0.996"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Rectangle RadiusX="10" RadiusY="10" RenderTransformOrigin="0.493,0.485" StrokeThickness="10" Stroke="{x:Null}" Grid.RowSpan="2" Margin="1,1,1,1"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0.493,0.002" StartPoint="0.493,0.33"> <GradientStop Color="#00E6D9AA" Offset="0.513"/> <GradientStop Color="#FFE6D9AA" Offset="0.996"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Rectangle RadiusX="10" RadiusY="10" RenderTransformOrigin="0.493,0.485" StrokeThickness="10" Stroke="{x:Null}" Grid.RowSpan="2" Margin="1,1,1,1"> <Rectangle.Fill> <LinearGradientBrush EndPoint="0.99,0.441" StartPoint="0.794,0.441"> <GradientStop Color="#00E6D9AA" Offset="0.513"/> <GradientStop Color="#FFE6D9AA" Offset="0.996"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <TextBlock Text="TextBlock" TextWrapping="Wrap" x:Name="TextBlockToolTip" RenderTransformOrigin="0.5,0.5" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="20,0,0,20" /> <TextBlock Name="ToolTipTitle" HorizontalAlignment="Stretch" Margin="15,16,15,6.1" FontSize="14" Text="title" d:LayoutOverrides="Height" /> </Grid> </UserControl> 同时。我们需要添加一些方法和属性来控制这些元素。 namespace CustomToolTip { public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public double UserControlToolTipX { get { return this.UserControlToolTipXY.X; } set { this.UserControlToolTipXY.X = value; } } public double UserControlToolTipY { get { return this.UserControlToolTipXY.Y; } set { this.UserControlToolTipXY.Y = value; } } public string UserControlTextBlockToolTip { get { return TextBlockToolTip.Text; } set { TextBlockToolTip.Text = value; } } public string UserControlToolTipTitle { get { return ToolTipTitle.Text; } set { ToolTipTitle.Text = value; } } } } ...

2012-10-14 · 3 min · bystander

WPF绘制圆角多边形

介绍 最近,我发现我需要个圆角多边形。而且是需要在运行时从用户界面来绘制。WPF有多边形。但是不支持圆角。我搜索了一下。也没找到可行的现成例子。于是就自己做吧。本文描述了圆角多边形的实现,也包括如何用在你的项目里。在Demo里面的RoundedCornersPolygon 类是完整的实现。 下载的Demo包括两部分 1. 通过XAML绘制圆角多边形 2. 运行时创建圆角多边形 背景 多边形可以被认为是沿着一个给定半径的圆的边缘和一些指定点/边。所构成的点的集合。 在WPF中。你可以给Polygon对象的Points属性添加一系列的点来制作多边形。 XAML方式 <Canvas> <Polygon Points="10,50 180,50 180,150 10,150" StrokeThickness="1" Stroke="Black" /> </Canvas> C#方式 var cnv = new Canvas(); var polygon = new Polygon {StrokeThickness = 1, Fill = Brushes.Black}; polygon.Points.Add(new Point(10, 50)); polygon.Points.Add(new Point(180, 50)); polygon.Points.Add(new Point(180, 150)); polygon.Points.Add(new Point(10, 150)); cnv.Children.Add(polygon); this.Content = cnv; 上面两个例子会输出下面的矩形 使用代码 我写的RoundedCornersPolygon 类和普通的多边形类很相似。但是有更多的属性来控制圆角。首先。看一个例子。展示一下圆角矩形类的使用 XAML方式 <Canvas> <CustomRoundedCornersPolygon:RoundedCornersPolygon Points="10,50 180,50 180,150 10,150" StrokeThickness="1" Stroke="Black" ArcRoundness="25" UseAnglePercentage="False" IsClosed="True"/> <Canvas> C#方式 var cnv = new Canvas(); var roundedPolygon = new RoundedCornersPolygon { Stroke = Brushes.Black, StrokeThickness = 1, ArcRoundness = 25, UseAnglePercentage = false, IsClosed = true }; roundedPolygon.Points.Add(new Point(10, 50)); roundedPolygon.Points.Add(new Point(180, 50)); roundedPolygon.Points.Add(new Point(180, 150)); roundedPolygon.Points.Add(new Point(10, 150)); cnv.Children.Add(roundedPolygon); this.Content = cnv; 输出如下: 多边形有四个主要属性 ArcRoundness 属性指定了从距离LineSegment终点多远的距离开始弯曲,通常和UseRoundnessPercentage 一起使用。UseRoundnessPercentage属性指定了ArcRoundness 值是百分比还是一个固定的值。 举个例子。ArcRoundness 被设置成10,而且UseRoundnessPercentage 被设置成false,那么弯曲将会在距离线段终点10的地方开始。而如果UseRoundnessPercentage 被设置成ture。则会是从线段终点10%的地方开始弯曲。 /// <summary> /// Gets or sets a value that specifies the arc roundness. /// </summary> public double ArcRoundness { get; set; } /// <summary> /// Gets or sets a value that specifies if the ArcRoundness property /// value will be used as a percentage of the connecting segment or not. /// </summary> public bool UseRoundnessPercentage { get; set; } IsClosed 指定多边形的最后一个点是否和第一个点闭合。为了成为一个多边形。一般应该被设置为true ...

2012-10-13 · 3 min · bystander