当前位置:Gxlcms > mysql > WPF程序设计读书笔记(2

WPF程序设计读书笔记(2

时间:2021-07-01 10:21:17 帮助过:29人阅读

第1章 基本画刷 标准窗口的内部,被称为客户区。正是在窗口的这一区域,显示文字、图形、控件,并接收用户的输入。 WPF 的颜色被封闭成 Color 结构体,定义在 System.Window.Media 命名空间中。和一般的图形环境一样, Color 结构体使用 RGB 三原色来表达颜

第1章 基本画刷

标准窗口的内部,被称为客户区。正是在窗口的这一区域,显示文字、图形、控件,并接收用户的输入。

WPF的颜色被封闭成Color结构体,定义在System.Window.Media命名空间中。和一般的图形环境一样,Color结构体使用RGB三原色来表达颜色。

Color结构包含名为RGB的三个可读写属性,它们的类型都是byte。三个属性都是0时为黑色,都是255时为白色。

Color结构还包含一个“alpha通道”,其属性名为A。它是用来控制颜色的“不透明”度的,0表示完全透明,255表示完全不透明。

和所有结构体一样,Color具有一个无参数的构造函数,它产生一个ARGB都是0的颜色,也就是一个透明的黑色。你可以手动设定这四个属性,如下:

Color clr=new Color();

clr.A=255;

clr.R=255;

clr.G=0;

clr.B=255;

这样我们就得到了一个洋红色。

Color结构提供了几个静态方法,让你可以方便的创建Color对象:

Color color=Color.FromRgb(r,g,b);

这里会得到你所指定的颜色,其Alpha值是255。你还可以这样:

Color color=Color.FromArgb(a,r,g,b);

由你来指定Alpha值。

前面我们所使用的RGB颜色空间,也被称为sRGB颜色空间,“s”就是标准的意思。而Color结构也支持另一种被称为scRGB的颜色空间,这种颜色空间通常又被称为sRGB64,因为它不是使用一个字节而是8个字节来存储颜色值。在Color结构中,scRGB被储存为了float类型。分别叫ScAScRScGWcB。这些属性和ARGB会相互影响,改变G会造成ScG的改变,反之亦然。

另外,System.Window.Media也包含一个叫Colors的类,它有141个静态颜色值属性,它们的名称都是好记的颜色名称,从AliceBlueAntiqueWhiteYellowYellowGreen。我们可以这样使用:

Color color=Colors.PapayaWhip;

这些颜色的名称和Web浏览器常用的颜色名称是一样的。另外需要注意的一个问题是,140个颜色属性的Alpha值都是255,有一个颜色属性的Alpha值是0,它就是Transparent属性。

程序可以设定Background属性,但这个属性的类型却不是Color,它是一个Brush对象。

Brush是一个抽象类,只有它的子类实例才能用来设定Window对象的Background属性,而所有这些子类都在System.Window.Media命名空间里。本章稍后将讨论SolidColorBrush(单色画刷)类和两个继承自GradientBrush(渐变画刷)的类

SolidColorBrush是最简单的画刷,只使用单一的颜色。你可以在第1章后面的程序中加入以下代码来改变窗口的背景色。

Color backColor = Color.FromRgb(0,255,255);

SolidColorBrush brush = new SolidColorBrush(backColor);

this.Background = brush;

下面的程序在执行时,会依据“鼠标指针靠近窗口中心的程度”,来改变客户区的背景颜色。此程序利用usingSystem.Window.Media命名空间加进来,本书后面大部分的程序也都会用到这个命名空间。

//*********************************************************

//VaryTheBackground.cs 2010 18th July by mouyong

//*********************************************************

using System;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

namespace part1.ch01

{

class VaryTheBackground:Window

{

SolidColorBrush brush = new SolidColorBrush(Colors.Black);

[STAThread]

public static void Main()

{

Application app = new Application();

app.Run(new VaryTheBackground());

}

public VaryTheBackground()

{

Title = "改变背景色";

Width = 384;

Height = 384;

Background = brush;

}

protected override void OnMouseMove(MouseEventArgs e)

{

base.OnMouseMove(e);

//得到客户区的实际宽和高

double height = ActualHeight - 2 * SystemParameters.ResizeFrameHorizontalBorderHeight - SystemParameters.CaptionHeight;

double width = ActualWidth - 2 * SystemParameters.ResizeFrameVerticalBorderWidth;

//得到鼠标的坐标

Point ptMouse = e.GetPosition(this);

//计算客户区的实际中心点

Point ptCenter = new Point(width/2,height/2);

//计算中心点与鼠标位置的距离

Vector vectMouse = ptMouse - ptCenter;

//计算中心点与鼠标位置之间连线与水平线形成的夹角

double angle = Math.Atan2(vectMouse.Y,vectMouse.X);

//计算在鼠标与中心点连线上,客户区内切椭圆边框到中心点的距离

Vector vectEllipse = new Vector(width/2*Math.Cos(angle),height/2*Math.Sin(angle));

//根据前面两个距离的比值,来设定灰度的多少

Byte byLevel=(Byte)(255*(1-Math.Min(1,vectMouse.Length/vectEllipse.Length)));

//重新设置背景颜色

Color color = brush.Color;

color.R = color.G = color.B = byLevel;

//这句代码一定要有,否则背景不会重绘

brush.Color = color;

}

}

}

当你向客户区中心点移动鼠标时,背景变成较亮的白色,而鼠标超过内切椭圆边线时,背景会变成黑色(默然说话:的确是一个椭圆,你可以把鼠标移向窗体的四个角,你会发现,在还没有把鼠标移出窗体外的时候,窗口已经变成黑色,没有更多的变化了。)。

这个变化在鼠标每次移动时都会发生,那是因为只要brush一有改变,客户区就会被重绘,但这一切都是幕后进行的。这所以会有动态的反应,是因为Brush继承自Freezable类,而Freezable类实现了一个名为Changed的事件(event),Brush对象只要一有改变,这个事件就会被触发。所以只要一改变brush,背景就会被重绘。

WPF底层大量使用Changed事件和类似机制,以实现动画和其他特性。

画刷也有一个与Colors相似的类Brushes类。它也提供了141个静态只读的属性,对应于Colors141个颜色属性,名称也是一样的。不过Brushes返回的是SolidColorBrush对象。你可以用下面的方式设定Background

Background=Brushes.PaleGoldenrod;

但是Brushes下面所有的SolidColorBrush对象都是处于冻结状态,也就是说,不能再被改变。它是通过把Freeable对象的CanFreeze属性设为true来实现的。你可以通过调用Freeze方法来实现对象的冻结和不可变动。IsFrozen属性如果变成true,就表示已经被冻结。冻结的对象可以提高效率,还可以在多个线程间共享,没有被冻结的则不行。虽然无法将冻结的对象解冻,但是你可以做出一个没冻结的复制版本。下面的代码可以定义VaryTheBackground中的brush字段:

SolidColorBrush brush=Brushes.Black.Clone();

如果你想看到这141个画刷出现在同一个窗口的客户区,FlipThroughTheBrushes程序可以达成你的愿望,你可以用上下箭头来改变画刷。

//*********************************************************

//FlipThroughTheBrushes.cs 2010 18th July by mouyong

//*********************************************************

using System;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

using System.Reflection;

namespace part1.ch01

{

namespace part1.ch01

{

class FlipThroughTheBrushes : Window

{

int index = 0;

PropertyInfo[] props;

[STAThread]

public static void Main()

{

Application app = new Application();

app.Run(new FlipThroughTheBrushes());

}

public FlipThroughTheBrushes()

{

props = typeof(Brushes).GetProperties(BindingFlags.Public | BindingFlags.Static);

SetTitleAndBackground();

}

private void SetTitleAndBackground()

{

Title = "变化笔刷到 - " + props[index].Name;//获得属性的名称

Background = (Brush)props[index].GetValue(null, null);//获得实际的SolidColorBrush对象

}

protected override void OnKeyDown(KeyEventArgs e)

{

base.OnKeyDown(e);

if (e.Key == Key.Down || e.Key == Key.Up)

{

index += e.Key == Key.Up ? 1 : props.Length - 1;

index %= props.Length;

SetTitleAndBackground();

}

}

}

}

}

此程序使用反射(reflection)来取得Brushes类的成员。构造函数第一行使用typeof(Brushes)来得到一个Type类的对象。Type类有一个方法,叫GetProperties,返回PropertyInfo对象的数组,数组内的每个元素都对应到Brushes类里的一个属性。调用GetProperties时,可以通过BinddingFlags来限制获得的属性状态,这里就限制只获得公开和静态的属性。

在构造函数和重写的OnKeyDown方法中,程序都调用了SetTitleAndBackground,以便将Title属性和Background属性设定为Brushes类的某个成员。Name会返回属性的名称,这里一开始就是“AliceBlue”。GetValue方法返回实际的SolidColorBrush对象。它需要两个参数,第一个参数需要属性所在的对象,因为我们现在取到的都是静态属性,所以传入null;第二个参数只有属性是一个数组时才有必要传入,所以我们也传入null

System.Windows命名空间具有SystemColors类,其作用类似于ColorsBrushes,只具有静态的只读属性,返回Color值和SolidColorBrush对象。这些设定存储在Windows注册表中。利用此类,可以得知目前用户的颜色喜欢。比方说,SystemColors.WindowColor用来表示用户对于客户区的颜色喜好,而SystemColors.WindowTextColor是用户对于客户区文字的颜色喜好,而SystemColors.WindowBrusht SystemColors.WindowTextBrush则是返回对应颜色的SolidColorBrush对象。对于大多数的真实应用程序来说,应该使用这些颜色,可以达到统一、协调的视觉效果。

只继承自Freezeable类的对象,才可以被冻结。而Color是一个结构体,所以不存在冻结不冻结的问题。

如果不用单色画刷,可以改用渐变画刷,将两种(或多种)颜色混合,逐渐改变。对于WPF来说,创建一个渐变画刷是非常容易的,且渐变画刷在现代的色彩设计中也很受欢迎。

渐变画刷最简单的形式是LinearGradientBrush,只需要两个Color(我们不妨称这两种颜色为clr1clr2)对象,和两个Pointpt1pt2)对象。pt1的位置的颜色是clr1,而pt2的位置的颜色是clr2.pt1pt2之间的连线上,则是混合了crl1crl2的颜色,连线中心点是clr1clr2的平均值。垂直于连线的位置,和连线上的点使用相同的颜色。至于超过pt1pt2的两边会是什么颜色,稍后再讨论。

WPF渐变画刷有一个特性,让你不用基于窗口尺寸而调整画刷的点。默认情况下,你指定的点是“相对于窗口面积”的,这里的窗口面积被视为一个单位宽,一个单位高。(默然说话:即无论你的窗口的实际宽高是多少,我们统统认为它们都是一个单位宽,一个单位高,这就叫“相对”)那么,左上角的坐标就是(00),而右下角的坐标就是(11)。

例如,如果你想要让客户区的左上角为红色,右下角为蓝色,是间是渐变色,则使用下面的构造函数,这里需要指定两种颜色和两个点。

LinearGradientBrush brush = new LinearGradientBrush(Colors.Red,Colors.Blue,new Point(0,0),new Point(1,1));

下面是完整的程序:

//*********************************************************

//GradiateTheBrush.cs 2010 17th July by mouyong

//*********************************************************

using System;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

namespace part1.ch01

{

class GradiateTheBrush:Window

{

[STAThread]

public static void Main()

{

Application app = new Application();

app.Run(new GradiateTheBrush());

}

public GradiateTheBrush()

{

Title = "渐变画刷";

LinearGradientBrush brush = new LinearGradientBrush(Colors.Red,Colors.Blue,new Point(0,0),new Point(1,1));

Background = brush;

}

}

}

当你改变客户区的尺寸时,渐变画刷会随之改变。这要归功于Freezable所实现的Changed事件。

使用相对坐标系统来设定点很方便,但这不是唯一的做法。GradientBrush类有一个MappingMode属性,类型为BrushMappingMode枚举。此枚举只有两种值,分别为RelativeToBoundingBox(使用相对坐标,默认值)Absolute(使用“设备无关单位”)。

如果你需要建立水平或垂直的渐变,还可以使用LinearGradientBrush的构造函数:

new LinearGradientBrush(clr1,clr2,angle);

指定角度。0度是水平渐变,clr1在左边,等同于:

new LinearGradientBrush(clr1,clr2,new Point(0,0),new Point(1,0));

90度是垂直渐变,clr1在上面,等同于:

new LinearGradientBrush(clr1,clr2,new Point(0,0),new Point(0,1));

其他的角度用起来可能需要一点技巧,就一般的例子来说,第一个点一定是原点,第二个点计算如下:

new Point(cos(angle),sin(angle));

45度为例,第二个点逼近(0.707,0.707)。别忘了这是“相对于”客户区的点,所以,如果客户区不是正方形(通常都不是),这两个点之间的连线就不会是45度。另外,窗口右下角也会有一大块超出这个点,这部分会如何处理呢?默认情况下,通常会着上第二种颜色。你可以设置LinearGradientBrushSpreadMethod属性,它是GradientSpreadMethod枚举类型,默认是Pad,表示超出部分延续之前的颜色,不渐变,你还可以设置为ReflectRepeat。试着把GradiateTheBrush程序修改为下面这样:

LinearGradientBrush brush = new LinearGradientBrush(Colors.Red,Colors.Blue,new Point(0,0),new Point(0.25,0.25));

brush.SpreadMethod = GradientSpreadMethod.Reflect;

Background = brush;

(0,0)(0.25,0.25)之间,画刷从红到蓝渐变,然后(0.25,0.25)(0.5,0.5)之间,从蓝到红渐变,接着在(0.5,0.5)(0.75,0.75)之间,从红到蓝渐变,最后在(0.75,0.75)(1,1)之间,从蓝到红渐变。

如图2-1所示,我们的pt1pt2位于窗口的左上和右下角,而虚线代表的是等色线(意思就是在这条线上的颜色都是相等的)。在窗口的宽和高发生改变的时候,等色线的角度也就会随之更改(因为等色线总是垂直于pt1pt2的连线的),但我们可能并不希望这样的一个效果,我们希望无论如何改变窗口的宽和高,等色线总是保持某个角度不变的(如图2-2,洋红的等色线始终保持在对角的连线上),这样,我们就要去调整pt1pt2的位置,以使得等色线始终处于对角线的连线上。

2-1

2-2

这样一来,就带来了一个问题,如何随着窗口的调整,动态的求出pt1pt2的位置,好让连接pt1pt2和连线始终垂直于左下到右上的对角线呢?换句话说,我们应该设计一个公式,让pt1pt2的位置与窗口的宽和高关联起来。我们再来看图2-3:我们需要再添加一点标识和一条辅助线,以便得到我们想要的公式。

2-3

如图2-3,这个窗口的宽我们用W来表示,高用H来表示,则左下到右上的对角线长度就应该是默然说话:著名的勾股定理还记得吧?斜边的平方等于两个直角边的平方和,所以斜边长就是两个直角边的平方和开根),计算对角线的长度干嘛?其实这不是我们的目的,我们的目的是为了得到对角线到pt2的距离。这里我们作了一条辅助线L,它平行于pt1pt2的连线,也垂直于对角线。根据定律, L的长度与对角线到pt2的长度是相等的,也就是说,求出了L的长度,也就得到了pt2到对角线的长度。接下来请看演算:(默然说话:下面将要使用三角函数,我就不多罗嗦了,记不得的同学请参考别的书籍

一方面,如果我们把H当作α所在的直角三角形的对边,那就有成立。

另一方面,如果我们把L当作α所在直角三角形的对边,那就有成立。

(默然说话:好吧,我就再多罗嗦几句。正弦就是对边比斜边,如果把H作为对边,那么,斜边就是对角线,而对角线长度的计算,前面已经写过了。如果以L为对边,那斜边就是W。所以就有了上面的两个等式)

根据等量代换原则,就有 成立。

整理后得到

这样我们就计算出了L的长度,也就是pt2到对角线的距离。在这里重要的是,我们找到了pt2的位置与长和宽的关系,同理可证pt1的位置与长和宽的关系也是相似的。

默然说话:不用多想α是多少,因为它只是我们的一个引入的中间变量,对于我们研究的问题,毫无关系。我只是想找出pt1pt2的位置和长宽的关系而引入了它而已。

下面的程序在构造函数中建立一个“可以被修改的”LinearGradientBrush对象,其MappingModeAbsolute。构造函数中还委托了SizeChanged事件的处理器,只要窗口尺寸改变,就会跟着发生SizeChanged事件。

//*********************************************************

//AdjustTheGradient.cs 2010 17th July by mouyong

//*********************************************************

using System;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

namespace part1.ch01

{

class AdjustTheGradient:Window

{

LinearGradientBrush brush;

[STAThread]

public static void Main()

{

Application app = new Application();

app.Run(new AdjustTheGradient());

}

public AdjustTheGradient()

{

Title = "调整渐变";

SizeChanged += WindowOnSizeChanged;

brush = new LinearGradientBrush(Colors.Red,Colors.Blue,0);

brush.MappingMode = BrushMappingMode.Absolute;

Background = brush;

}

private void WindowOnSizeChanged(object sender, SizeChangedEventArgs e)

{

double width = ActualWidth - 2 * SystemParameters.ResizeFrameVerticalBorderWidth;

double height = ActualHeight - 2 * SystemParameters.ResizeFrameHorizontalBorderHeight - SystemParameters.CaptionHeight;

Point ptCenter = new Point(width/2,height/2);//中心点

Vector vectDiag = new Vector(width,-height);

Vector vectPerp = new Vector(vectDiag.Y,-vectDiag.X);

vectPerp.Normalize();

vectPerp *= width * height / vectDiag.Length;

brush.StartPoint = ptCenter + vectPerp;

brush.EndPoint = ptCenter - vectPerp;

}

}

}

默然说话:你可以看到,无论你如何调整窗口的大小,两个颜色的中间过渡色总是处于左下至右上角的对角线上,太神奇了!而这一切都发生在WindowOnSizeChanged事件方法里

事件处理器一开始是计算客户区的宽度和高度,如同本章稍早的VaryTheBackground程序做法一样。用Vector对象vectDiag来表示对角线的向量(从左下到右上)。也可以利用右上角坐标减左下角坐标,来计算得到:

vectDiag=new Point(width,0)-new Point(0,height);

vectPerp向量垂直于对角线。建立相互垂直的向量很容易,只要把XY属性的值对调,并把其中一个数的正负号反向就可以了。调用Normalize方法是为了正规化这个向量,之后,又把vectPerp乘以L默然说话:就是前面我们计算过的那个公式),这样就得到了一个pt1pt2的一个向量。

(默然说话:是不是还是觉得晕?看不明白?嘿嘿,说实话,我也看不明白这一段呀,实在是超出了我的所学范围,仍然盼望高手解惑。)

最后的步骤是设定StartPointEndPoint属性。这些属性一般是通过画刷的构造函数来设定的,而且除去继承的属性,它们是LinearGradientBrush仅有的两个属性。

人气教程排行