一步步教你制作WPF圆形玻璃按钮

1.介绍 从我开始使用vista的时候,我就非常喜欢它的圆形玻璃按钮。WPF最好的一个方面就是允许自定义任何控件的样式。用了一段时间的Microsoft Expression Blend后。我做出了这个样式。我觉得做的还行。因为。我决定分享。如我所说。我使用Microsoft Expression Blend来做。但是。我也是用XAML编辑器–Kaxaml。 2.概述 玻璃按钮样式包含了三层。组织了玻璃效果(Glass Effect)和一个ContentPresenter 来存储按钮的内容。所有的这些层都在一个最外层的Grid里。当鼠标放到按钮上,按下去的时候也定义了一些触发器(Triggers),来增加一些交互。 我把这个样式做成了资源文件。但是这个Key可以删除,来使得所有的按钮都是这个效果。 好我们来看一下这些层次。这些被广泛应用在微软产品中的按钮。 **3.按钮层次 ** 3.1背景层 第一层是一个椭圆。其实是一个canvas,一会在上面画反射和折射层,填充的颜色和按钮的背景(Background)关联。 下面是Blend中的截图 图2 <!-- Background Layer --> <Ellipse Fill="{TemplateBinding Background}"/> 3.1.1折射层 第二层模拟了光从上到下的折射。被放在反射层之前是因为,要达到反光玻璃的效果,反射层必须在按钮的中间某处有一个硬边缘。这一层实际上是另一个椭圆。但是这次。我们使用一个径向渐变(白色-透明)的填充。来模拟光的折射。渐变开始于第一层底部的中央。结束于上面的中间。然而。为了降低折射光的强度。渐变还是开始于椭圆的底部再下一点为好。可以从图上和代码里清晰的看到。 <!-- Refraction Layer --> <Ellipse x:Name="RefractionLayer"> <Ellipse.Fill> <RadialGradientBrush GradientOrigin="0.496,1.052"> <RadialGradientBrush.RelativeTransform> <TransformGroup> <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/> <TranslateTransform X="0.02" Y="0.3"/> </TransformGroup> </RadialGradientBrush.RelativeTransform> <GradientStop Offset="1" Color="#00000000"/> <GradientStop Offset="0.4" Color="#FFFFFFFF"/> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> 3.1.2反射层 第三层是光的反射层。是最难的部分。问题是反射效果不能使用任何标准的形状来画。因此。使用路径(Path)来画反射区域。当时。手工画也是可以的。但老实说。手工画图实在没什么可享受的(除非你是一个艺术家,或者有一个数位板),无论如何。我现在MS Blend中华好一个椭圆并转换成一个路径,然后我使用贝塞尔曲线点调整得到平滑的路径,你可以添加渐变到一个复杂的Path对象上。就像你对其他与定义的图形,比如椭圆,矩形所做的一样。为了得到光泽反射。我额每年需要一个透明-白色的径向渐变填充,从路径的底部开始(也就是按钮的中间某处),结束在顶部。我想如果我是一个艺术家。我会让渐变更准一点。可是我不是。因此。就这样。因为我们要把我们的按钮放在一个Grid里。所有我们设置VerticalAlignment=“Top” 这样反射区域在按钮的中间的结束了。 图三 <!-- Reflection Layer --> <Path x:Name="ReflectionLayer" VerticalAlignment="Top" Stretch="Fill"> <Path.RenderTransform> <ScaleTransform ScaleY="0.5" /> </Path.RenderTransform> <Path.Data> <PathGeometry> <PathFigure IsClosed="True" StartPoint="98.999,45.499"> <BezierSegment Point1="98.999,54.170" Point2="89.046,52.258" Point3="85.502,51.029"/> <BezierSegment IsSmoothJoin="True" Point1="75.860,47.685" Point2="69.111,45.196" Point3="50.167,45.196"/> <BezierSegment Point1="30.805,45.196" Point2="20.173,47.741" Point3="10.665,51.363"/> <BezierSegment IsSmoothJoin="True" Point1="7.469,52.580" Point2="1.000,53.252" Point3="1.000,44.999"/> <BezierSegment Point1="1.000,39.510" Point2="0.884,39.227" Point3="2.519,34.286"/> <BezierSegment IsSmoothJoin="True" Point1="9.106,14.370" Point2="27.875,0" Point3="50,0"/> <BezierSegment Point1="72.198,0" Point2="91.018,14.466" Point3="97.546,34.485"/> <BezierSegment IsSmoothJoin="True" Point1="99.139,39.369" Point2="98.999,40.084" Point3="98.999,45.499"/> </PathFigure> </PathGeometry> </Path.Data> <Path.Fill> <RadialGradientBrush GradientOrigin="0.498,0.526"> <RadialGradientBrush.RelativeTransform> <TransformGroup> <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1" ScaleY="1.997"/> <TranslateTransform X="0" Y="0.5"/> </TransformGroup> </RadialGradientBrush.RelativeTransform> <GradientStop Offset="1" Color="#FFFFFFFF"/> <GradientStop Offset="0.85" Color="#92FFFFFF"/> <GradientStop Offset="0" Color="#00000000"/> </RadialGradientBrush> </Path.Fill> </Path> 最后。我添加一个ContentPresenter 到按钮中间。经验告诉我,内容区域再向下一个像素会使得按钮看起来更漂亮。因此,在这里我用了margin属性(注意。因为内容区域在Grid的中间(Center)。所以2个像素的top实际上是向下移动了一个像素 ) 好了。最后在Blend中看起来大概是这样 图4 4.添加一些交互性 4.1鼠标悬停效果 为了有鼠标悬停效果,我们需要增加光源的亮度。因此。我们为IsMouseOver 事件定义一个触发器,复制并且粘贴反射和折射层的渐变设置代码。对于折射层。我仅仅移动了渐变的起点向上了一点。在反射层中。我改变了渐变停止点。使不透明的白色多一点。 <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="RefractionLayer" Property="Fill"> <Setter.Value> <RadialGradientBrush GradientOrigin="0.496,1.052"> <RadialGradientBrush.RelativeTransform> <TransformGroup> <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/> <TranslateTransform X="0.02" Y="0.3"/> </TransformGroup> </RadialGradientBrush.RelativeTransform> <GradientStop Offset="1" Color="#00000000"/> <GradientStop Offset="0.45" Color="#FFFFFFFF"/> </RadialGradientBrush> </Setter.Value> </Setter> <Setter TargetName="ReflectionLayer" Property="Fill"> <Setter.Value> <RadialGradientBrush GradientOrigin="0.498,0.526"> <RadialGradientBrush.RelativeTransform> <TransformGroup> <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1" ScaleY="1.997"/> <TranslateTransform X="0" Y="0.5"/> </TransformGroup> </RadialGradientBrush.RelativeTransform> <GradientStop Offset="1" Color="#FFFFFFFF"/> <GradientStop Offset="0.85" Color="#BBFFFFFF"/> <GradientStop Offset="0" Color="#00000000"/> </RadialGradientBrush> </Setter.Value> </Setter> </Trigger> ...

2012-10-12 · 2 min · bystander

自定义WPF LinkLabel 控件

WPF里是没有LinkLabel控件的。因此我自己写一个。首先。我们看一下WPF中什么类似的组件可以实现这个链接功能 如果你想要模拟一个LinkLabel控件。你可以在TextBlock里使用内联的Hyperlink。像下面这样 <TextBlock> <Hyperlink> <Run Text="Test link"/> </Hyperlink> </TextBlock> 你可以使用Label控件。加一个内联的HyperLink,但是我认为TextBlock更好。因为你可以在Expression Blend 中通过InlineCollection 编辑所有子元素的属性 图1 虽然这种方法也行,但是我还是不太喜欢。因为我觉得我还是写一个类似windows窗体程序中的LinkLabel控件。然后我就做了。首先看一下控件的样子 图2 第一个是默认的LinkLabel控件。第二个是LinkLabelBehavior 属性被设置为HoverUnderline ,第三个的Foreground和 HoverForeground 属性都使用了自定的颜色。 LinkLabel控件支持的属性 1.Foreground和 HoverForeground属性 允许自定义这两个属性的值 2.LinkLabelBehavior 属性 允许设置下划线的显示与否 3.自定义HyperlinkStyle 属性 你可以使用这个属性给超链接设置自定义的样式。如果你已经自定了Foreground和 HoverForeground。则会被覆盖。 Url 超链接的目标 所有这些属性都继承自标准的System.Windows.Controls.Label 控件 通过Blend/Xaml设置这些属性很简单 <ThemedControlLibrary:LinkLabel Content="Link Label" FontSize="22"/> <ThemedControlLibrary:LinkLabel Content="Link Label" LinkLabelBehavour="HoverUnderline" /> <ThemedControlLibrary:LinkLabel Foreground="#FF847901" HoverForeground="#FF06C8F2" Content="Link Label" LinkLabelBehavour="NeverUnderline"/> 图三 然后是控件的使用方法。仅仅添加命名空间到xaml中。然后使用就行了。 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="DemoApplication.Window1" Title="DemoApplication" Height="300" Width="300" xmlns:ThemedControlsLibrary="clr-namespace:ThemedControlsLibrary;assembly=ThemedControlsLibrary" > <Grid> <ThemedControlsLibrary:LinkLabel HorizontalAlignment="Left" VerticalAlignment="Top" Content="LinkLabel"/> </Grid> </Window> 控件的完整代码很简单。就定义一下需要的属性,和控制这些属性应该显示在Blend中的(category)目录位置就行了。 public class LinkLabel : Label { private const string _linkLabel = "LinkLabel"; public static readonly DependencyProperty UrlProperty = DependencyProperty.Register("Url", typeof(Uri), typeof(LinkLabel)); [Category("Common Properties"), Bindable(true)] public Uri Url { get { return GetValue(UrlProperty) as Uri; } set { SetValue(UrlProperty, value); } } public static readonly DependencyProperty HyperlinkStyleProperty = DependencyProperty.Register("HyperlinkStyle", typeof(Style), typeof(LinkLabel)); public Style HyperlinkStyle { get { return GetValue(HyperlinkStyleProperty) as Style; } set { SetValue(HyperlinkStyleProperty, value); } } public static readonly DependencyProperty HoverForegroundProperty = DependencyProperty.Register("HoverForeground", typeof(Brush), typeof(LinkLabel)); [Category("Brushes"), Bindable(true)] public Brush HoverForeground { get { return GetValue(HoverForegroundProperty) as Brush; } set { SetValue(HoverForegroundProperty, value); } } public static readonly DependencyProperty LinkLabelBehavourProperty = DependencyProperty.Register("LinkLabelBehavour", typeof(LinkLabelBehaviour), typeof(LinkLabel)); [Category("Common Properties"), Bindable(true)] public LinkLabelBehaviour LinkLabelBehavour { get { return (LinkLabelBehaviour)GetValue(LinkLabelBehavourProperty); } set { SetValue(LinkLabelBehavourProperty, value); } } static LinkLabel() { FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata( typeof(LinkLabel), new FrameworkPropertyMetadata(typeof(LinkLabel))); } } ...

2012-10-11 · 2 min · bystander

C#制作进度窗体

介绍 这是我在CodeProject上的第一篇文章。我希望对你有用 当我开发软件的时候。我通常因为一个很耗时是任务需要完成。而请求让用户等待,并且通过也允许用户取消。不论我做何种操作(比如下载文件。保存大文件等等)。我都需要做下面几件事: 通过一个模态对话框来让用户等待操作完成 能让用户看到进度。 能让用户随时取消。 我搜了好久也没找到拿来就能用的窗体控件,也许是我没找到。于是我自己写。。 图1 背景 BackgroundWorker 类包含了我需要完成任务的所有东西。我只需要给他提供一个对话框。 使用代码 ProgressForm 包含了一个BackgroundWorker ,你要做的仅仅就是提供了一个完成工作的方法。 ProgressForm form = new ProgressForm(); form.DoWork += new ProgressForm.DoWorkEventHandler(form_DoWork); //如果想为后台任务提供参数的话 form.Argument = something; 为了开始BackgroundWorker,只需要调用ShowDialog 方法。返回值则取决于任务是怎么完成的。 DialogResult result = form.ShowDialog(); if (result == DialogResult.Cancel) { //用户点击了取消 } else if (result == DialogResult.Abort) { /未处理的异常抛出 //你可以得到异常信息 MessageBox.Show(form.Result.Error.Message); } else if (result == DialogResult.OK) { //正常完成 //结果存储在 form.Result里 } 最后。任务方法看起来是这样的。 void form_DoWork(ProgressForm sender, DoWorkEventArgs e) { //得到参数 object myArgument = e.Argument; //做一些耗时的任务... for (int i = 0; i < 100; i++) { //通知进度 sender.SetProgress(i, "Step " + i.ToString() + " / 100..."); //... //检查是否点击了取消 if (sender.CancellationPending) { e.Cancel = true; return; } } } 如果你想要改改进度条,或者进度条显示的文本。SetProgress 有一些重载的方法 public void SetProgress(string status); public void SetProgress(int percent); public void SetProgress(int percent, string status); 最后一个可自定义的字符串是:有两个预定义的字符串CancellingText 和DefaultStatusText. CancellingText ,这两个字符串,当用户点击取消的时候显示 如何实现 ProgressForm 紧紧嵌入了一个BackgroundWorker ,并包装进了主函数。 首先。我设计了如图所示的一个窗体,然后。添加了BackgroundWorker。 public partial class ProgressForm : Form { public ProgressForm() { InitializeComponent(); worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; worker.WorkerSupportsCancellation = true; worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler( worker_ProgressChanged); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( worker_RunWorkerCompleted); } void worker_DoWork(object sender, DoWorkEventArgs e) { } void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { } void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { } BackgroundWorker worker; } 我们必须把DoWork事件暴露给用户。我添加了一个委托。这样。我可以很容易的访问窗体成员 ...

2012-10-10 · 4 min · bystander

C#编写FTP客户端软件

1 介绍 我知道。网上有很多现成的FTP软件。但是。我们也想要了解FTP的一些底层机构,因此。 这个开源的项目在你学习FTP知识的时候也许对你有些帮组。程序的界面看起来像FileZilla,FileZilla虽然流行但是有些bug,当我打开我博客的时候总是有问题。我需要通过FTP连接我的服务器。发送文件,下载文件等等。因为。我决定写我自己的软件来处理所有的情况。FileZilla足够好。但它不是我的。 2 背景 看看我们已经知道的。我们知道FTP是一个标准的基于TCP网络协议。用于从一个主机向另一个主机传输文件。它是一个C/S架构。 图2 FTP程序曾经是基于命令行的。我们仍沿可以通过cmd.exe连接FTP服务器。因为FTP的确可以通过命令来操作。举个例子。我们可以在命令行使用“stor”命令来发送文件。为了完成这些请求。FTP服务器需要一直运行等待即将到来的客户端请求。我们可以从来自维基百科的解释更好的理解FTP: 客户端计算机可以通过服务器的21端口和服务器通信。叫做控制连接。它在一次会话期间保持开放。第一次连接的时候。叫做数据连接,服务器可以对客户端打开20端口(主动模式),建立一条数据通路,连接上客户端传输数据。或者客户端打开一个随机的端口(被动模式),去连接服务器,来传输数据。控制连接使用一个类似Telnet的协议,被用作客户端和服务器会话管理(命令,标识,密码)。。比如。“RETR filename” 会从服务器端下载文件。 图三 一个完整的FTP文件传输需要建立两种类型的连接,一种为文件传输下命令,称为控制连接,另一种实现真正的文件传输,称为数据连接。 服务器 通过三位ASCII的数字状态码,可能包含可选的描述信息,在控制连接上做出回应。比如。“200”或者是“200 OK”,表示上一条命令成功了。数字代表编号,描述信息给出了一些说明(比如“OK”),或者可能是一些需要的参数(比如需要帐号来存储文件),那么我们需要怎么做呢。很明显。发送命令,接收“OK”回应,发送数据。接收数据。完了。但是首先需要服务器已经准备好了。FTP服务器可以在主动和被动两种模式下运行。主动模式是基于服务器的连接而被动模式是基友客户端的连接。继续看。 在主动连接中,客户端把自己的ip和端口发送给服务器。然后服务器尝试连接到客户端,但是可能会因为防火墙的原因而被拒绝。我们在windows上都会使用反病毒/自带防火墙。是吧。那么我们来看看被动模式 在被动连接中。服务器通过一个“PASV”命令把自己的ip和端口发送给客户端。然后客户端通过该IP尝试连接服务器。对于发送文件非常有用。当我们发送文件的时候。优先使用“PASV”模式,如你们所说。大多数协议。像FTP/HTTP 使用ASCII编码,因为全球可用。因此我们会使用这种编码。你可以从下面得到FTP的命令列表 主动和被动都是对于服务器端来说的 3 使用代码 现在我们已经为编写软件做好准备了。我们写些有用的代码吧。:)首先。我们“打开文件对话框”,集成到我们的窗体里。 3.1 资源管理器组件 我们需要一个资源管理器组件在软件界面可以看到我们所有的文件。这样我们才可以选择哪些文件来发送到FTP服务器,新建一个Windows窗体控件库(下载包中提供了) 图四 最后看起来样子是上面这样。先添加一个TreeView,一些按钮,和一个搜索功能 TreeView.Nodes.Clear(); TreeNode nodeD = new TreeNode(); nodeD.Tag = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); nodeD.Text = “Desktop”; nodeD.ImageIndex = 10; nodeD.SelectedImageIndex = 10; TreeView.Nodes.Add(nodeD); 就像上面代码展示的那样。我们需要添加地一个主节点。我的文档。我的电脑等等。然后获得子目录。 string[] dirList; dirList = Directory.GetDirectories(parentNode.Tag.ToString()); Array.Sort(dirList); if (dirList.Length == parentNode.Nodes.Count) return; for (int i = 0; i < dirList.Length; i++) { node = new TreeNode(); node.Tag = dirList[i]; node.Text = dirList[i].Substring(dirList[i].LastIndexOf(@"\") + 1); node.ImageIndex = 1; parentNode.Nodes.Add(node); } 可以从下载包里看到完整的代码。我们还应该处理鼠标单击事件。 现在我们有了一个资源管理器。还有FTP和VS所需要的所有信息。 首先,我们连接服务器。我们应该怎么做呢? FTPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); AppendText(rchLog,"Status : Resolving IP Address\n",Color.Red); remoteAddress = Dns.GetHostEntry(Server).AddressList[0]; AppendText(rchLog, "Status : IP Address Found ->" + remoteAddress.ToString() + "\n", Color.Red); addrEndPoint = new IPEndPoint(remoteAddress, Port); AppendText(rchLog,"Status : EndPoint Found ->" + addrEndPoint.ToString() + "\n", Color.Red); FTPSocket.Connect(addrEndPoint); 是的。我们需要一个socket连接到服务器 ,然后发送命令 AppendText(rchLog, "Command : " + msg + "\n", Color.Blue); Byte[] CommandBytes = Encoding.ASCII.GetBytes((msg + "\r\n").ToCharArray()); FTPSocket.Send(CommandBytes, CommandBytes.Length, 0); //read Response ReadResponse(); ...

2012-10-09 · 2 min · bystander

理解并实现生成器模式

介绍 本文讨论生成器设计模式,讨论该模式什么情况下使用,怎么实现。并且。最后会有一个简单的生成器模式的实现。 背景 当我们的程序需要创建一个对象。而这个对象必须由很多不同的对象来构造的时候。为了构造最后的对象。我们不得不组合那些部分对象。最后我们会发现我们的代码被各种各样的部分对象的细节所弄的难以理解 为了说明上面的情况。我们做一个手机生产制造系统的例子。假定我们我们有一个已经安装在手机供应商那块的一个系统。现在供应商系那个要根据一些参数来创造一个新手机。比如触屏,操作系统,电池等。如果我们已经有了这些部分的对象,那么上述部分的任意组合将会导致客户端代码复杂难以管理。比如决定生产哪种手机的模块。 生成器模式目的就是解决上述问题的。GoF定义生成器模式如下: Separate the construction of a complex object from its representation so that the same construction process can create different representations. 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 这个定义意味着我们不得不设计这个系统。通过一种客户端仅仅定义参数,而生成器则接管创建复杂对象 的方式。我们看一下生成器模式的类图。 然后看看上图中的每一个类都表示什么 ConcreteBuilder: 创建复杂产品的具体类.将会知道他已经创建的Product(产品),也就是他已经装配了的Product, 客户端通过该类得到Product对象. Builder: 创建Product的接口 Director: 客户端代码,定义了哪些部分应该被组合在一起来创建具体的Product Product: 这是通过组合很多部分创建的对象 使用代码 我们现在跟随上述的定义,然后试着去实现一个基本的生成器模式 我们先在合适的地方定义Product的不同部分,我们简单的定义一些枚举类型,那么我们就可以通过组合不同的部分创建Product了。 // 一些helper枚举定义各种零件 public enum ScreenType { ScreenType_TOUCH_CAPACITIVE, ScreenType_TOUCH_RESISTIVE, ScreenType_NON_TOUCH }; public enum Battery { MAH_1000, MAH_1500, MAH_2000 }; public enum OperatingSystem { ANDROID, WINDOWS_MOBILE, WINDOWS_PHONE, SYMBIAN }; public enum Stylus { YES, NO }; 然后,我们看一下Product类,我们需要有一个可以通过装配创建的Product类,这里我们定义一个MobilePhone类,也就是概念里的Product类了。 // 这是 "Product" 类 class MobilePhone { // 不同部分的字段 string phoneName; ScreenType phoneScreen; Battery phoneBattery; OperatingSystem phoneOS; Stylus phoneStylus; public MobilePhone(string name) { phoneName = name; } //公有属性访问这些部分 public string PhoneName { get { return phoneName; } } public ScreenType PhoneScreen { get { return phoneScreen; } set { phoneScreen = value; } } public Battery PhoneBattery { get { return phoneBattery; } set { phoneBattery = value; } } public OperatingSystem PhoneOS { get { return phoneOS; } set { phoneOS = value; } } public Stylus PhoneStylus { get { return phoneStylus; } set { phoneStylus = value; } } // 显示手机相关信息的方法 public override string ToString() { return string.Format("Name: {0}\nScreen: {1}\nBattery {2}\nOS: {3}\nStylus: {4}", PhoneName, PhoneScreen, PhoneBattery, PhoneOS, PhoneStylus); } } ...

2012-10-08 · 3 min · bystander

接口VS 委托

背景 对于指定的任务有不同的方案可供选择,通常是很好的。因为可能某一种方案会更加适合该任务,但是有时候做决定会很难。因为这些不同的方案有其各自的优缺点。 我经常会停下来好好想想,是不是接口比委托更适合或者是更不适合某个任务。有时候我甚至会回去看我写的代码,这些代码刚开始使用委托来实现,我后来用接口替换掉。因此,是时候写篇文章来阐述一下这两种技术的优缺点了。 性能 我经常看到有人问接口是不是比委托更快啊。或者是不是相反。通常。别人给的答案会是: 接口更快。委托相当慢 委托更快,因为他们是指向方法的指针,接口则需要一个v-table(虚函数解析表),然后找到委托 他们一样快,但委托更容易使用 好吧。那些都是错的。也许在.Net 1中。委托真的很慢。但是事实是: 委托执行(execute)的时候更快 接口获得(get)的时候更快 在下面这段代码中: Action action = SomeMethod; 我们将得到一个Action(委托类型)来调用SomeMethod。问题是:委托是包含被调用方法的实例和指针的引用类型。而不仅仅只是一个指向方法的指针,通过引用类型,委托需要分配内存,因此,每一次你把一个方法变换成一个委托的时候。都会分配一个新的对象。 如果委托是一个值类型。会有些不一样。但是他们不是。。 另一方面,如果我们这样写代码: IRunnable runnable = this; 如果实现了IRunnable接口的对象。我们简单通过一个转换得到同样的引用。没有涉及内存分配。我们将可以通过下面的代码来进行速度比较: 对于委托: Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for(int i=0; i<COUNT; i++) { Action action = SomeMethod; action(); } stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed); 对于接口 Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for(int i=0; i<COUNT; i++) { IRunnable runnable = this; runnable.Run(); } stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed); 我知道接口会更快。不是因为他们执行更快。而是因为每一次迭代,一个新的Action委托都会被分配。 但是。如果把委托和接口的获得语句放在循环之外。委托会更快一些。 当创建事件的时候。举个例子。我们在给事件添加委托的时候,就只添加一次。这样。即使事件被触发再多次。也只进行了一次内存分配。 那么?谁赢了? 好。对于事件,委托将会更快些。 但是。在说委托更好或是更快之前,我们再看另一种情况。 匿名方法 在我看来,匿名方法是委托最糟糕的使用。但是同时。也正在变成最普遍的用法。 当你像这段代码这样调用的时候 for(int i=0; i<count; i++) MethodThatReceivesADelegate(() => SomeCall(i)); 事实上,编译器将会创建一个接受参数i的方法实例,然后创建另一个实例(即委托)来引用这个实例。 如果用接口来替换的话。编译器将指挥分配单一的对象,该对象实现了接口 可能的抱怨 一些人也许对那个接受参数i的方法实例的问题有所疑惑。他们可能认为,在每一次迭代中。实例里面的之被改变了。也许编译器可以优化这个委托的内存分配。实际只分配了一次。 好把。对于委托的内存分配我不知道。但是。对于要分配一个接受参数i的单一实例,确实真的。也是一个bug。如果MethodThatReceivesADelegate 把委托传递给另一个线程。其他的线程也许会接收到错误的i值,在.net 4.5中。这块不会出错。因为。每一次迭代。一个新的对象被创建。这就能保证当委托被传递到另一个线程里的时候。结果总是正确的。但这也就意味着每次都会有一个新的委托会创建。 如果MethodThatReceivesADelegate 仅仅使用委托一次。使用接口也许更好。不幸的是。我们没有办法实现匿名接口。 好。如果是为了方便。委托更好。但是如果要求性能。在这种情况下。接口会更好。因为避免了一次不必要的内存分配。 事实上,我创建了IRunnable接口是为了强制使用者实现了一个新的类型,而不是使用匿名委托。这样就可以解决在for循环(或是任何在foreach里使用的任何值)i值可变的问题,同时也有一定的性能提升。。 调用和动态调用 现在我们知道有匿名委托,但是没有匿名接口,只使用一次的情况下,接口将会比委托有更好的性能。因为只请求一个对象而不是两个。 这让我开始考虑,当一个方法接受一个只会执行一次的方法作为参数的时候,我应该使用委托还是是用接口。 但是。更多的性能相关的情况下我们可以这样用。。 你是否曾经有过要用动态调用代替直接委托调用的情况?一般是在编译时并不知道委托的参数类型的情况下。 好。当有一个接口。在接口的方法里有一个方法调用的参数类型未定。我不知道为什么。但是反射调用和委托的动态调用都极慢。比直接对参数做转换都慢。而数组长度验证。然后放到一个try/catch块里会抛出TargetInvocationException 异常。 因此。如果你有类似下面的代码: public interface IDynamicInvokable { object DynamicInvoke(params object[] parameters); } 那么你可以创建你的委托接口,是IDynamicInvokable 接口的继承接口,像这样: public interface IAction<T>: IDynamicInvokable { void Invoke(T parameter); } 这样你的用户就可以通过Invoke方法调用你的接口,如果他们在编译时不知道接口的类型。他们可以使用更泛化一些的IDynamicInvoke。 注意:我讨厌泛型这个名字。对于我来说。IDynamicInvoke 是调用方法最泛型的的途径,而IAction<T> 是类型接口,因此,我我说泛型的时候。我其实是在说更加普遍无类型的调用泛型。而不是类型指定的泛型。 那么,如果对委托做上千次调用。但是使用DynamicInvoke 代替Invoke,接口会做的更好 我又一次的问我自己。匿名委托的简单性值得吗?仅仅为了更好的性能我就把让我的用户对我方法的调用变得困难?并且这真的会影响到程序的整体性能吗? 泛型,差异,无类型的使用 我刚刚说我讨厌泛型的名字。因为使用泛型的代码通常是有类型的代码,而我们也许需要的无具体类型的代码,我认为这样更加泛一些。 但是。让我好好讨论一下.net的泛型。假设你知道一个委托的参数数目,但是你不知道具体的类型,这和DynamicInvoke 是不一样的。因为这个方法。简单的把所有的参数当成一个数组。 泛型具化或者是相反可以有一些帮助。但是很小。 比如。我们可以把 Func&lt;string&gt; 当成 Func&lt;object&gt; ``,或是把``Action&lt;object&gt; 看成 Action&lt;string&gt; 理由很简单,当返回一个值的时候(``Func``的情况),``string``是一个``object``。将不会做任何转换。将会简单地返回一个``string``,调用这会看成一个无类型的``object``。但是可以。而在``Action``这个情况下。它需要一个``object``,而``string.是可用的object,所以也可以。 但是。如果你想要把Func&lt;int&gt; 当作Func<object>。更广泛一点。想把所有的参数转换成object,可以吗? ...

2012-10-07 · 2 min · bystander

YAXLib---- XML序列化神器

今天早上翻译了Yet-Another-XML-Serialization-Library-for-the-NET,刚开始以为很短。翻译着发现不对。。然后你不逼你自己。怎么知道自己做不到。于是。将近4个小时把30页的文档翻译完了。因为文章很长。所以本文只列出前两部分。我把翻译好的做成了pdf, 文档下载:XML序列化神器 1 介绍 在本文中,会把要提到的XML序列化库叫做YAXLib,我们知道。.Net 还是提供了一些序列化功能的,尤其是XmlSerializer,该类被程序员广泛使用用来序列化对象成XML,当然,反序列化也是可以的。我认为XmlSerializer类的问题有几下几点 程序员不能自由的选择生成的xml的结构 不支持序列化一些集合类,比如Dictionary<,> 或者IEnumerable<>的属性 当反序列化的时候,如果缺失了一些域,则反序列化失败,这就使得用来存储一ixekeyi被用户编辑的配置文件变得不合适了。 2 为什么使用YAXLib YAXLib解决上述问题的特点 程序员可以决定xml文件的结构,一个属性可以是一个子元素,或者是其他属性的属性,或者是一个在类中没有对应属性的元素。 集合类也可以被序列化成一个逗号分隔(也可以是其他任何分隔符)的数据项列表,而且。为Dictionary<,>对象实现了一些特殊的格式化功能,这样,使得程序员可以完全控制生成的xml文件的结构 他支持System.Collections.Generic 命名空间中的所有泛型集合类(像Dictionary, HashSet, LinkedList, List, Queue,SortedDictionary, SortedList, 和 Stack) 和在System.Collections 命名空间中的非泛型集合类( ArrayList, BitArray, Hashtable, Queue, SortedList, 和 Stack)非泛型集合类可以包含多种不同的对象,而且,库还支持序列化和反序列化一维,多维,不规则的数组。 支持通过对基类/接口的引用,实现对一些对象集合的序列化和反序列化。 支持多级反序列化 程序员可以为生成的xml提供注释 当进行反序列化的时候,程序员可以选择性对于那些与类的属性相关,但没有出现在xml文件中的数据应该如何处理。这种情况下可以看错是一个错误,然后类库抛出一些异常,或者记录错误,或者可以被看成一个警告,然后用程序员预定义的值赋给对应的属性,而且,程序可以可以选择忽略这个问题,相关的异常将既不抛出也不作任何记录。请查看保留空引用标识那一节 看看什么时候可以忽略孤立的数据也许对你有帮助 程序员可以自己选择错误处理规则,对于数据敏感的应用程序,程序员可以选择在任何异常的情况下,库都应该抛出并且记录异常,对于其他的一些情况(比如要求不那么高的配置文件的存储),程序员可以选择把异常仅仅看成一个警告,仅仅记录一下,让程序的其他部分继续运行。 文档下载:XML序列化神器 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-05 · 1 min · bystander

简单扩展方法增强代码可读性

本文技术含量不高,但是思路可以借鉴。。 介绍 当你处理计时器,时间间隔,或是其他关于日期的计算的时候。你必然会使用TimeSpan类。 我觉得写出下面的代码可读性并不好。。 // 1个表示5小时的时间间隔 var theTimespan = new TimeSpan(0, 5, 0, 0, 0); 而下面的代码就要好一些 //一个表示5小时的时间间隔 var theTimespan = 5.Hours(); ** 扩展方法** 使用这些扩展了int类的方法。可以使得创建TimeSpan可读性更好 public static TimeSpan Days(this int value) { return new TimeSpan(value, 0, 0, 0, 0); } public static TimeSpan Years(this int value) { var dt = DateTime.Now.AddYears(value); return (dt - DateTime.Now).Duration(); } public static TimeSpan Hours(this int value) { return new TimeSpan(0, value, 0, 0, 0); } public static TimeSpan Minutes(this int value) { return new TimeSpan(0, 0, value, 0, 0); } public static TimeSpan Seconds(this int value) { return new TimeSpan(0, 0, 0, value, 0); } public static TimeSpan Milliseconds(this int value) { return new TimeSpan(0, 0, 0, 0, value); } 许可 本文所有源代码包括文件在CPOL下授权。。 原文地址:Simple-extension-methods-for-code-readability 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-04 · 1 min · bystander

11个高效的VS调试技巧

介绍 调试是软件开发周期中的一个很重要的部分,有时很有挑战性,有时候则让程序员迷惑,有时候让程序员发疯,但是。可以肯定的是,对于任何不是太那个微不足道的程序来说,调试是不可避免的。近年来,调试工具的发展已经使得很多调试任务简单省时了。 本文总结了十个调试技巧,当你使用VS的时候可以节省你很多时间。 1. 悬停鼠标查看表达式 调试有时候很有挑战性,当你步入一个函数想看看哪块出错的时候,查看调用栈来想想值是从哪来的。另一些情况下,则需要添加一些监视表达式,或者查看局部变量列表,这通常还是花费一些时间的,但是。如果你把你鼠标指向你感兴趣的一个变量。你会发现事情简单多了。而且,类和结构体可以通过单击展开。这样。你就可以方便快捷的找到你想查看的变量了。 2. 实时改变值 调试器不仅仅是一个分析程序崩溃或是异常结果的工具了,许多bug都可以通过步入新写的函数,检查函数是否如期望的那样运行来预防。有时候你可能会好奇“如果条件为真函数会正确运行吗”大多数情况下,根本不需要改变代码重启挑起,仅仅把鼠标悬停到一个变量上,双击值然后输入一个新值就可以了。。 3.设置下一条语句 一个典型的调试情况就是通过单步跟踪分析为什么一个函数调用失败了。当你发现一个函数调用的另一个函数返回错误的时候你会怎么做?重启调试?有更好的方法。拖动这个黄色的语句标识到你想下一步执行的语句前就可以了。比如你刚才失败的那块,然后步入。简单,不是吗? 4.编辑然后继续 调试一个复杂的程序,或是一个插件的时候,在一个被调用很多次的函数处发现一个错误。但是不想浪费时间停下来,重新编译然后重新调试。没问题,仅仅在该处改正代码然后继续单步就可以。VS会修正程序然后继续调试不需要重启 注意,编辑然后继续有大量的已知限制,首先,64位代码是不行的。如果他如果为你的C#程序工作。就去工程设置的生成选项,然后目标平台为x86.不要担心。发布版的目标平台和调试的时候是分开的。可以被设置为任何平台。。 第二.编辑然后继续改变在一个方法里应该是局部的。。如果你改变了方法签名,添加一些新方法或是类。你就不得不重启程序了。或者撤销改变来继续。改变方法也包含lambda表达式隐式修改的自动生成的代理类,因此也不能继续。 5.方便的监视窗口 大概现代的调试器都有一个监视窗口,无论如何。VS允许你简单的添加或移除变量。单击空行,输入你的表达式按下回车,或者是在不需要的表达式上按下Delete键就可以删除了。 而且。从监视窗口你不仅仅可以看到“正常”的变量。你可以输入$handles 来追踪你的程序打开了多少句柄(可以方便的修复内存泄漏) ,输入$err 可以看到上一个函数的错误码,然后使用工具-错误信息可以看到更详细的描述,或者输入@eax(64位是@rax)来查看包含函数返回值的寄存器。 6.带注释的反汇编 使用交互式的反汇编模式可以使得优化程序的关键部分变得很容易,VS给出对应你代码每一行的汇编指令,并且运行单步运行。同时,可以在任何位置设置断点。而且,表达式的查看和修改也像在C++代码里一样 7.带有栈的线程窗口 调试多线程的程序是痛苦的。。或者也可以是很有趣的。取决于你的调试器。VS2010真正优美的特性是线程窗口的栈视图,通过窗口的调用栈你可以方便的总览线程。 8.条件断点 如果你尝试通过断点再现一个罕见的事件,该情况引发了一些严重的错误。你可以添加条件断点。定义一个断点的条件,然后如果条件不成立,VS会忽略该断点 9.内存窗口 有些bug由不正确的结构体定义引起,忽略的对齐属性等等。查看内存中的内容可以定位然后修复bug。VS提供了一个放百年的内存窗口,可以把值以8/16/32/64位的形式展示。还有浮点值。也允许实时改变他们。就像在文本编辑器里一样。 10.转到定义 这个特性不是直接关于调试的,而是关于浏览大项目的。如果你尝试找到一些不是你自己写的代码中的错误,快速知道“这个类型是什么”或者“这个函数是干嘛的”,可以节省很多时间,VS通过一个转到定义命令方便了你。 11.命令窗口 第十一的技巧chaau已经建议过了。确实可以节省很多时间,VS支持命令窗口,可以通过,视图-其他窗口-命令窗口来启动。一旦激活,你可以输入不同的命令来自动化调试。举个例子。你可以通过如下命令 简单的模拟MFC COleDateTime 变量。 ? dt.Format("%Y-%m-%d %H:%M:%S") 许可 本文包括源代码和文件在CPOL下授权。 原文地址:10plus-powerful-debugging-tricks-with-Visual-Studi 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-03 · 1 min · bystander

CSV导入导出工具

介绍 本文介绍并且实现了在平面文件CSV和SQL server之间的导入导出功能。使用VS2005写的。使用了.net 2.0 本文基于前一篇文章:从CSV导入数据并存储到数据库,本文包含了新功能,比如,导出功能,在数据库创建表,批量拷贝。接下来的例子中有很多注释。 导入 通过ODBC驱动连接到一个CSV文件,然后把文件读到一张表了(基于前面提到的那篇文章) 使用不同的编码和不同的行分隔符(基于前文) 加载CSV文件到DataSet(基于前文) 如何显示对CSV文件的预览(基于前文) 通过SqlBulkCopy的对象向SQL server转移数据,原始数据是DataSet实例 使用结构表,基于CSV文件,创建一个新表 使用事件来处理批量拷贝的进程 导出 浏览SQL 数据库中的用户表 使用不同的编码和分隔符 使用SqlDataReader读取数据,使用StreamWriter转移数据到平面文件 使用 下载工程 新建一个数据库,或者使用一个存在的数据库 修改软件中的数据库连接字符串,在prop.cs文件中 public static string sqlConnString = "server=(local); database=Test_CSV_impex;Trusted_Connection=True"; 运行工程 一些代码段 加载CSV到DataSet中 /* *加载CSV到DataSet. * * 如果numberOfRows parameter 是 -1, 就加载所有行, 否则就加载指定数目的行 */ public DataSet LoadCSV(int numberOfRows) { DataSet ds = new DataSet(); try { // 创建并打开ODBC连接 string strConnString = "Driver={Microsoft Text Driver (*.txt; *.csv)}; Dbq=" + this.dirCSV.Trim() + "; Extensions=asc,csv,tab,txt;Persist Security Info=False"; string sql_select; OdbcConnection conn; conn = new OdbcConnection(strConnString.Trim()); conn.Open(); //创建SQL语句 if (numberOfRows == -1) { sql_select = "select * from [" + this.FileNevCSV.Trim() + "]"; } else { sql_select = "select top " + numberOfRows + " * from [" + this.FileNevCSV.Trim() + "]"; } //创建数据适配器 OdbcDataAdapter obj_oledb_da = new OdbcDataAdapter(sql_select, conn); //用CSV的数据填充DataSet obj_oledb_da.Fill(ds, "csv"); //关闭连接 conn.Close(); } catch (Exception e) //异常处理 { MessageBox.Show(e.Message, "Error - LoadCSV", MessageBoxButtons.OK,MessageBoxIcon.Error); } return ds; } 通过SqlBulkCopy从ODBC连接中转移数据到数据库 ...

2012-09-30 · 4 min · bystander