C# GDI+绘图高级编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
理解绘图规则 一般来说,Windows的一个优点(实际上是现代操作系统的优点)是它可以让开发人员不考虑特定设备的细节。例如:不需要理解硬盘设备驱动程序,只需在相关的.NET类中调用合适的方法,就可以编程读写磁盘上的文件。这个规则也适用于绘图。计算机在屏幕上绘图时,把指令发送给视频卡。问题是市面上有几百种不同的视频卡,大多数有不同的指令集合功能。如果把这个i考虑在内,在应用程序中为每个视频卡驱动程序编写在屏幕上绘图的特定代码,这样的应用程序就根本不可能编写出来。这就是为什么在Windows最早的版本中有Windows Graphical Device Interface(GDI)的原因。 GDI+提供了一个抽象层,隐藏了不同视频卡之间的区别,这样就可以调用Windows API函数完成指定的任务了,GDI还在内部指出在运行特定的代码时,如果让客户机的视频卡完成要绘制的图形。GDI还可以完成其他任务。大多数计算机都有多个显示设备---监视器、打印机。GDI成功的使应用程序所使用的打印机看起来与屏幕一样。如果要打印某些东西,而不是显示他们,只需告诉系统输出设备是打印机,再用相同的方式调用相同的Windows API函数可以。 可以看出DC(设备环境)是一个功能非常强大的对象,在GDI下,所有的绘图工作都必须通过设备环境完成。DC甚至可用于不涉及在屏幕或其他硬件设备上绘图的其他操作,例如在内存中修改图像。 GDI开发人员提供了一个相当高级的API,但它仍是一个基于旧Windows API并且有C语言风格函数的API,所以使用起来不是很方便。GDI+在很大程度上是GDI和应用程序之间的一层,提供了更直观、基于继承性的对象模型。尽管GDI+基本上是GDI的一个包装器,但Microsoft已经能通过GDI+提供新的功能了并宣称他又一些性能方面的改进。 1.GDI+命名空间 (不说了,自己看去吧!!!) 2.设备环境和Graphics对象 GDI使用设备环境(DC)对像识别输出设备。DC对象存储特定设备的信息并把GDI API函数调用转换为要发送给设备的命令。还可以通过DC对象确定对应的设备有什么功能(如打印机是彩色还是黑白的)。如果要求设备完成它不能完成的任务,设备对象就会检测到并采取措施。 DC对象不仅可以硬件还可以用作到Windows的一个桥梁。例如如果Windows知道只有一小部分应用程序窗口需要重新绘制,DC就可以捕获和撤销在该地区外的绘图工作。因为DC与Windows的关系非常密切,通过Dc来工作就可以用其他方式简化代码。 绘制图形 下面举例来说明如何在应用程序的主窗口中绘图。DisplayAtStartup 创建一个C# 应用程序并在启动窗体时在构造函数中绘制它。这并不是在屏幕上绘图的最佳方式,这个示例并不能在启动后按照需要重新绘制窗体。这样只是不必作太多的工作就可以说明一些问题。 首先把窗体的背景色设置为白色。如果使用设计视图设置背景色,系统会自动添加代码: private void InitializeComponent() { this.AutoScaleBaseSize = new System.Drawing.Size(5,13); this.BackColor = System.Drawing.Color.White; this.ClientSize = new System.Drawing.Size(292,266); this.Name = "Form1"; this.Text = "Form1"; } 接着给Form1构造函数添加代码。使用窗体的CreateGraphics()方法创建一个Graphics对象,其中包括绘图时需要的使用的Windows DC。创建的DC即与显示设备相关也与窗口相关。 public Form1() { InitializeComponent(); Graphics dc = this.CreateGraphics(); this.Show(); Pen bluePen = new Pen(Color.Blue,3); dc.DrawRectangle(bluePen,0,0,50,50); //矩形 Pen redPen = new Pen(Color.Red,2); dc.DrawEllipse(redPen,0,50,80,60); // 椭圆 } 然后调用Show()方法显示窗口。必须让窗口立即显示,因为在其显示之前不能作任何工作。(没有绘图的地方) 最后显示一个矩形和椭圆。注意其中坐标(x,y)表示从窗口的客户区域左上角向右的X个像素,向下的Y个像素。 (其中DrawRectangle()和DrawEillipse()这两个函数前面已经讲过不再重复了。) 上面程序窗体如果最小化再恢复,绘制好的图形就不见了。如果在该窗体上拖动另一个窗口,使之只遮挡一部分图形,再把该窗口拖离这个窗体,临时被遮挡的部分就消失了,只剩下一半椭圆或矩形了!原因是:如果窗体的一部分被隐藏了,Windows通常会立即删除与其中显示的内容相关的所有信息。在窗口的某一部分消失时,那些像素也就丢失了(即Windows释放了保存这些像素的内存)。 但要注意窗口的一部分被隐藏了,当它检测到窗口不再被隐藏时,就请求拥有该窗口的应用程序重新绘制其内容。这个规则有一些例外----窗口的一小部分被挡住的时间比较短(显示菜单时)。一般情况下应用程序就需要在以后重新绘制它。 由于本示例把绘图代码放在Form1的构造函数中,故不能在启动后再次调用该构造函数进行重新绘制。 使用OnPaint()绘制图形 Windows会利用Paint事件通知应用程序完成重新绘制的要求。Paint事件的Form1处理程序处理虚方法OnPaint()的调用,同时传给他一个参数PaintEventArgs。也就是说只要重写OnPaint()执行画图操作。 下面创建一个Windows应用程序DrawShapes来完成这个操作。 protected override void OnPaint(PaintEventarges e) { base.OnPaint(e); Graphics dc = e.Graphics; Pen bluePen = new Pen(Color.Blue,3); dc.DrawRectangle(bluePen,0,0,50,50); Pen redpen = new Pen(Color.Red,2); dc.DrawEllipse(redPen,0,50,80.60); } PaintEventArgs是一个派生自EventArgs的类,一般用于传送有关事件的信息。PaintEventArgs有另外两个属性,其中一个比较重要的是Graphics实例,它们主要用于优化绘制窗口中需要绘制的部分。这样就不必调用CreateGraphics(),在OnPaint()方法中获取DC。 在完成我们的绘图后,还要调用基类OnPaint()方法,因为Windows在绘图过程中可能会执行一些他自己的工作。 这段代码的结果与前面的示例结果相同,但当最小化或隐藏它时,应用程序会正确执行。 使用剪切区域 DrawShapes示例说明了在窗口中绘制的主要规则,但它并不是很高效。原因是它试图绘制窗口中的所有内容,而没有考虑需要绘制多少内容。如下图所示,运行DrawShapes示例,当该示例在屏幕上绘制时,打开一个窗口,把它移动到DrawShapes窗体上,使之隐藏一部分窗体。 到现在为止一切正常。但移动上面的窗口时,DrawShapes窗口会再次全部显示出来,WIndows通常会给窗体发送一个Paint事件,要求它重新绘制本身。矩形和椭圆都位于客户区域的左上角,所以在任何时候都是可见的。在本例中不需要重新绘制这部分,而只要重新绘制白色背景区域。但是,Windows并不知道这一点,他认为应引发Paint事件,调用OnPaint()方法的执行代码。OnPiant()不必重新绘制矩形和椭圆。 在本例中,没有重新绘制图形。原因是我们使用了设备环境。Windows将利用重新绘制某些区域所需要的信息预先初始化设备环境。在GDI中,被标记出来的重绘区域称为无效区域,但在GDI+中,该术语改为剪切区域,设备环境知道这个区域的内容,它截取在这个区域外部的绘图操作,且不把相关的绘图命令传送给显卡。这听起来不错,但仍有一个潜在的性能损失。在确定是在无效区域外部绘图前,我们不知道必须进行多少设备环境处理。在某些情况下,要处理的任务比较多,因为计算哪些像素需要改变什么颜色,将会占用许多处理器时间。 其底线是让Graphics实例完成在无效区域外部的绘图工作,肯定会浪费处理器时间,减慢应用程序的运行。在设计优良的应用程序中,代码将执行一些检查,以查看需要进行哪些绘图工作,然后调用相关的Graphics实例方法。下面将编写一个示例DrawShapesClipping,修改DisplayShapes示例,只完成需要的重新绘制工作。在OnPaint()代码中,进行一个简单的测试,看看无效区域是否需要绘制的区域重叠,如果是就调用绘图方法。 首先,需要获得剪切区域的信息。这需要使用PaintEventArgs的另一个属性。这个属性叫做ClipRectangle,包含要重绘区域的坐标,并包装在一个结构实例System.Drawing.Rectangle中。Rectangle是一个相当简单的结构,包含4个属性:Top、Bottom、Left、Right。它们分别含矩形的上下的垂直坐标,左右的水平坐标。 接着,需要确定进行什么测试,以决定是否进行绘制。这里进行一个简单的测试。注意,在我们的绘图过程中,矩形和椭圆完全包含在(0,0)到(80,130)的矩形客户区域中,实际上,点(82,132)就已经在安全区域中了,因为线条大约偏离这个区域一个像素。所以我们要看看剪切区域的左上角是否在这个矩形区域内。如果是,就重新绘制如果不是就不必麻烦了。 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics dc = e.Graphics; if (e.ClipRectangle.Top < 32 && e.ClipRectangle.Left < 82) { Pen bluePen = new Pen(Color.Blue,3); dc.DrawRectangle(bluePen,0,0,50,50); Pen redPen = new Pen(Color.Red,2); dc.DrawEllipse(redPen,0,50,80,60); } } 注意:这个结果和前一个结果完全相同,只是进行了早期测试,确定不需要重绘制的区域,提高了性能。还要注意这个是否进行绘图测试是非常粗略的。还可以进行更精细的测试,确定矩形和椭圆是否要重新绘制。这里有一个平衡。可以在OnPaint()中进行更复杂的测试,以提高性能,也可以使OnPaint()代码复杂一些。进行一些测试总是值得的,因为编写一些代码,可以更多的解除Graphics实例之外的会址内容,Graphics实例只是盲目地执行绘图命令。 测量坐标和区域 GDI+使用几个类似的结构来表示坐标或区域。下面介绍几个结构,他们都是在System.Drawing命名空间中定义的。 结构 | 主 要 公 共 属 性| | Point | X,Y | | PointF | | | Size | Width,Height | | SizeF | | | Rectangle | Left,Right,Top,Bottom,Width,Height| | RectangleF | ,X,Y,Location,Size | (1) Point、PointrF结构 从概念上讲,Point在这个结构中是最简单的,在数学上,它完全等价于一个二维矢量,包含两个公共整形属性,表示它与某个特定位置的水平和垂直距离(在屏幕上),如下图。 为了从点A到点B,需要水平移动20个单位,并向下垂直移动10个单位,在图中标为x和y,这就是他们的一般含义。创建一个Point结构,表示他们: Point ab = new Point(20,10); Console.WriteLine("Moved{0} across,{1} down",ab.x,ab.y); X 和 Y都是读写属性,也可以在Point中设置这些值: Point ab = new Point(); ab.X = 20; ab.Y = 10; Console.WirteLine("Moved{0} across,{1} down",ab.X,ab.Y); 注意:按照惯例,水平和垂直坐标表示为x和y(小写),但对应的Point属性是X和Y(大写),因为在C#中,公共属性的一般约定是名称以大写字母开头。 PointF与Point完全相同,但X和Y属性的类型是float,而不是int。PointF属性用于坐标不是整数值得情况。已经为这些结构定义了数据类型转换,这样就可以把Point隐式转换为PointF(这个转换是结构之间的)。但没有相应的逆过程,要把Point转换为Point,必须显示的复制值或使用下面的3个转换方法:Round(),Truncate(),Ceiling()。 PointF abFloat = new PointF(25.5F,10.9F); // converting to Point Point ab = new Point(); ab.X = (int)abFloat.X; ab.Y = (int)abFloat.Y; Point ab1 = Point.Round(abFloat); Point ab2 = Point.Truncate(abFloat); point ab3 = Point.Ceiling(abFloat); // but conversion back to PointF is implicit PointF abFloat2 = ab; 在默认情况下,GDI+把单位看作是屏幕(或打印机,无论图形设备是什么,都可以这样认为)的像素,这就是Graphics对象方法把它们接受到的坐标看作其参数的方式。例如:点 new Point(20,10)表示在屏幕上水平移动20个像素,向下垂直移动10个像素。通常这些像素从窗体客户区域的左上角开始测量,如上图。但是,情况并不是如此。在某些情况下,需要以窗口的左上角(包括其边框)为原点来绘图,甚至以屏幕的左上角为原点。除特殊说明,大多数可以假定像素是相对于客户区域的左上角。 (2) Size、SizeF结构 Size结构用于int类型,SizeF用于float类型。 在许多情况下Size结构与Point结构是相同的。有两个整形属性,表示水平和垂直距离----区别是两个属性的名称是:Width和Heihgt。 Size ab = new Size(20,10); Console.WriteLine("Moved {0} across,{1} down",ab.Width,ab.Height); 严格的讲,Size在数学上与Point表示的含义相同;但在概念上它使用的方式略有不同。Point用于说明实体在什么地方,而Size用于说明实体有多大。但是Size和 Piont是紧密相关的,目前甚至支持他们之间的显示转换: Point point = new Point(); Size size = (Size)point; Point anotherPoint = (Point)size; 例如:前面绘制的矩形,其左上角的坐标是(0,0),大小是(50,50)。这个矩形的大小是(50,50),可以用一个Size实例来表示。其右下角的坐标也是(50,50),但它由一个Point来表示。 Point和Size结构的相加运算符都已经重载了,所以可以把一个Size加到Point结构上,得到另一个Point结构: static void Main(string[] args) { Point topLeft = new Point(10,10); Size rectangleSize = new Size(50,50); Point bottomRight = topLeft + rectangleSize; Console.WriteLine("topLeft = " + topLeft); Console.WriteLine("bottomRight = " + bottomRight); Console.WirteLine("Size = " + rectangleSize); } 运行结果: topLeft = {X=10,Y=10} bottomRight = {X=60,Y=60} Size = {Width=50,Height=50} 这个结果说明Point和Size的ToString()方法已被重写并以{X,Y}的格式显示。 还可以进行Point和Size之间的显示数据类型转换: Point topLeft = new Point(10,10); Size s1 = (Size)topLeft; Point p1 = (Point)s1; 说明:s1.Width被赋予topLeft.X,s1.Height被赋予topLeft.Y的值。最后p1与topLeft的值相同。 接上一章内容 (3)Rectangle 和 RectangleF 这两个结构表示一个矩形区域。与Point和Size一样,这里只介绍Rectangle结构,Rectangle与RectangleF基本相同,但它的属性类型是float类型,而Rectangle的属性类型是int类型。 Rectangle可以看作由一个Point和一个Size组成,其中Point表示矩形的左上角,Size表示其大小。它的一个结构函数把Point和Size作为其参数。 下面重新编写前面DrawShapes示例代码,绘制一个矩形: Graphics dc = e.Graphics; Pen bluePen = new Pen(Color.Blue,3); Point topLeft = new Point(0,0); Size howBig = new Size(50,50); Rectangle rectangleArea = new Rectangle(topLeft,howBig); dc.DrawRectangle(bluePen,rectangleArea); (4)Region Region表示屏幕上一个有复杂图形的区域。如下图: 可以想象,初始化Region实例的过程相当复杂。从广义上看,可以指定哪些简单的图形组成这个区域,或者绘制这个区域的边界的路径。这种处理就需要Region类。 在进行更高级的绘图工作前介绍几个调试问题。(有一些帮助大家还是看看吧!) 如果在本章的示例中设置了断点,就会注意到调试图形程序不是那样简单。因为进入和退出调试程序常常会把Paint信息传送给应用程序。结果是OnPaint重载方法上设置的断点会让应用程序反复地绘制本身这样程序就不能完成任何工作。 这是典型的一种情况。要明白程序为什么应用程序没有正确显示,可以在OnPaint上设置断点。应用程序会像期望的那样,遇到断点后进入调试程序。此时在 前景上会显示开发环境MDI窗体。如果把开发环境设置为满屏显示,以便更易于观察所有的调试信息,就会完全隐藏目前正在调试的应用程序。 接着检查某些变量的值,希望找出某些有用的信息。然后按F5,告诉程序继续执行,告诉应用程序继续执行,完成某些处理后,看看应用程序在显示其他内容时会 发生什么。但首先发生的是应用程序显示在前景中,Windows检测到窗体再次可见,并提示给他发送了一个Paint事件。当然这表示程序遇到了断点。如 果这就是我们希望的结果,那就很好。但更常见的是,我们希望以后在应用程序绘制了某些有趣的内容之后再遇到断点。我们根本没有在OnPaint中设置断 点,应用程序也不会显示它在最初的启动窗口中显示的内容之外的其他内容。 有一种方式可以解决这个问题。如果有足够大的屏幕,最简单的方式就是恢复开发环境窗口,而不是把它设置为最大化,使之远离应用程序窗口,这样应用程序就不 会被挡住了。但在大多数情况下,这并不是一个有效的解决方案,因为这样会使开发环境窗口过小。另一个解决方案使用相同的规则,即使应用程序声明为在调试时 放在最上层。方法是在Form类中设置属性TopMost,这很容易在InitialzeComponet方法中完成: priavte void InitialzeComponent() { this.TopMost = true; } 也可以在 Visual Studio 2005的属性窗口中设置这个属性。 窗口这是为TopMost 表示应用程序不会被其他窗口挡住(除了其他放在最上层的窗口)。它总是放在其他窗口的上面,甚至在另一个应用程序得到焦点时,也是这样。这是任务管理器的执行方式。 利用这个技巧是必须小心,因为我们不能确定Windows何时会决定应为某种原因引发Paint事件。如果在某些特殊的情况下,OnPaint出了问题 (例如:应用程序在选择某个菜单项后绘图,但此时出了问题)。最好的方式是在OnPaint中编写一些虚拟代码,测试某些条件,这些条件只在特殊情况下才 为True。然后在if 块中设置断点,如下所示: protected override void OnPaint(PaintEventArgs e) { // Condition() evaluates to true when we want to break if (Condition() == true) { int ii = 0; // <-- SET BREAKPOINT!!! } 绘制可滚动的窗口---介绍如何绘制的内容不适合窗口的大小,需要做哪些工作。 下面扩展DrawShapes示例,来解释滚动的概念。为了使该示例更符合实际,首先创建一个BIgShapes示例,该示例将矩形和椭圆画大一些。此时将使用Point,Size,Rectange结构定义绘图域,说明如何使用他们。Form1类的相关部分如下所示: // member fields private Point rectangleTopLeft = new Point(0,0); private Size rectangleSize = new Size(200,200); private Point ellipseTopLeft = new Point(50,200); private Size ellipseSize = new Size(200,150); private Pen bluePen = new Pen(Color.Blue,3); private Pen redPen = new Pen(Color.Red,2); private override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics.dc = e.Graphics; // member fields if (e.ClipRectangle.Top < 350 || e.ClipRectangle.Left < 250) { Rectangle rectangleArea = new Rectangle(rectangleTopLeft,rectangleSize); Rectangle ellipseArea = new Rectangle(ellipseTopLeft,ellipse.Size); dc.DrawRectangle(bluePen,rectangleArea); dc.DrawEllipse(redPen,ellipseArea); } } 注意这里还把Pen、Size、Point对象变成成员字段---这比每次需要绘图时都创建一个新Pen的效率高。 这里有一个问题,图形在300*300像素的绘图区域中放不下。 一般情况下,如果文档太大,不能完全显示,应用程序就会添加滚动条,以便用户滚动窗口,查看其中选中的部分。这是另一个区域,在该区域中如果使用标准控件建立Windows窗体,就让.NET运行环境和基类处理程序。如果窗体上有各种控件,Form实例一般知道这些控件在哪里,如果其窗体可能比较小,Form实例就知道需要添加滚动条。Form实例还会自动添加滚动条,不仅如此,它还可以正确绘制用户滚动到的部分屏幕。此时,用户不需要在代码中做什么工作。但在本章中,我们要在屏幕上绘制图形,所以要帮助Form实例确认何时能滚动。 添加滚动条是很简单的。Form仍会处理所有的操作---因为它不知道绘图区域有多大。在上面的BigShapse示例中没有滚动条的原因是,Windows不知道它们需要滚动条。我们需要确认的是,矩形的大小从文档的左上角(或者是在进行任何滚动前的客户区域左上角)开始向下延伸,其大小应足以包含整个文档。本章把这个区域称为文档区域。在下图可以看出,本例的文档区域应是(250,350)像素。 使用相关的属性Form.AutoScrollMinSize即可确定文档的大小。因此给InitializeComponent()方法或Form1构造函数添加下述代码: private void InitializeComponent() { this.AutoScaleBaseSize = new System.Drawing.Size(5,13); this.ClientSize = new System.Drawing.Size(292,266); this.Name = "From1"; this.Text = "BigShapes"; this.BackColor = Color.White; this.AutoScrollMinSize = new Size(250,350); } 另外,AutoScrollSize属性还可以用VS2005属性窗口设置。 在应用程序启动时设置最小尺寸,并保持不变,在这个应用程序中是必要的,因为我们知道屏幕区域一般是有多大。在运行该应用程序时,这个“文档”是不会改变大小的。但要记住,如果应用程序执行显示文件内容的操作,或者执行某些改变屏幕区域的操作,就需要在其他时间设置这个属性(此时,必须手工调整代码,VS2005属性窗口只能在构建窗体时设置属性的初始值)。 设置MinScrollSize只是一个开始,仅有它是不够的。下图为示例应用程序目前的外观。 注意,不仅窗体正确设置了滚动条,而且他们的大小也正确设置了,以指定文档正确显示的比例。可以试着在运行示例重新设置窗口的大小,这样就会发现滚动条会正确响应,甚至如果窗口变得足够大,不再需要滚动条时,他会消失。 但是,如果使用一个滚动条,并向下滚动它,会发生什么情况?如下图,显然,出错了。 出错的原因是我们没有在OnPaint()重写方法的代码中考虑滚动条的位置。如果最小化窗口,再恢复它,重新绘制一遍窗口,就可以很清楚地看出这一点。结果如图所示。 图形像以前一样进行了绘制,矩形的左上角嵌套在客户区域的左上角,就好像根本没有移动过滚动条一样。 在更正这个问题前,先介绍一下在这些屏幕图上发生了什么。 首先从BigShapes示例开始,如图---所示。在这个例子中,整个窗口刚刚重新进行了绘制。看看前面的代码,该代码的作用是使graphics实例用左上角坐标(0,0)(相对于窗口客户区域的左上角)绘制一个矩形---它是已经绘制过的。问题是,graphics实例在默认情况下把坐标解释为是相对于客户窗口的,它不知道滚动条的存在。代码还没有尝试为滚动条的位置调整坐标。椭圆也是这样。 下面处理图---的问题。在滚动后,注意窗口上半部分显示正确,这是因为它们是在应用程序第一次启动时绘制的。在滚动窗口时,Windows没有要求应用程序重新绘制已经显示在屏幕中的内容。Windows只指出屏幕上目前显示的内容可以平滑移动,以匹配滚动条的位置。这是一个非常高效的过程,因为它也能使用某些硬件加速来完成。在这个屏幕图中,有错的是窗口下部的1/3部分。在应用程序第一次显示时,没有绘制这部分窗口,因为在滚动窗口前,在部分在客户区域的外部。这表示Windows要求BigShapes应用程序绘制这个区域。它引发Paint事件,把这个区域作为剪切的矩形。这也是OnPaint() 重载方法完成的任务。 问题的另一种表达方式是我们把坐标表示为相对于文档开头的左上角---需要转换它们,使之相对于客户区域的左上角。图---说明了这一点。 为了使该图更清晰,我们向下向右扩展了该文档,超出了屏幕的边界,但这不会改变我们的推论,我们还假定其上有一个水平滚动条和一个垂直滚动条。 在该图中,细矩形标记了屏幕区域的边框和整个文档的边框。粗线条标记试图要绘制的矩形和椭圆。P标记要绘制的某个随意点,这个点在后面会作为一个示例。在调用绘图方法时,提供graphics实例和从B点到P点的矢量,这个矢量表示为一个Point实例。我们实际上需要给出从点A到点B的矢量。 不知道A点到P点的矢量,而知道B点到P点的矢量,这是P相对于文档左上角的坐标---要在文档的P点绘图.还知道从B点到A点的矢量,这是滚动的距离,它存储在Form类的一个属性AutoScrollPosition中.但是不知道从A点到P点的矢量. 现在只需进行矢量相减即可.为了使之更简便,Graphics类执行了一个方法来进行这些计算---TranlateTransform.提供水平和垂直坐标,表示客户区域的左上角相对于文档的左上角,然后Graphics设备考虑客户区域相对于文档区域的位置,计算这些坐标. dc.TranslateTranform(this.AutoScrollPosition.X,this.AutoScrollPosition.Y); 在本例还要测试剪切区域,看看是否需要进行绘制工作.这个测试需要调整,把滚动的位置也考虑在内.完成后,该实例的整个绘图代码如下所示: protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics dc = e.Graphics; Size scrollOffset = new Size(this.AutoScrollPosition); if (e.ClipRectangle.Top + scrollOffset.Width < 350 || e.ClipRectangle.Left + scrollOffset.Height < 250) { Rectangle rectangleArea = new Rectangle(rectangleTopLeft + scrollOffset, rectanlgeSize); Rectangle ellipseArea = new Rectangle(ellipseTopLeft + scrollOffset,ellipseSize); dc.DrawRectangle(bluePen ,rectangleArea); dc.DrawEllipse(redPen,ellipseArea); } 得到正确的滚动屏幕. 世界\页面\设备坐标 测量相对于文档区域左上角的位置和测量相对于屏幕(桌面)左上角的位置之间的区别非常重要,GDI+为它们指定了不同的名称. * 世界坐标(Word Coordinate):要测量的点距离文档区域左上角的位置(以像素为单位). * 页面坐标(Page Coordiante):要测量的点距离客户区域左上角的位置(以像素为单位). 注意: 熟悉GDI开发的人员要注意,世界坐标对应于GDI中的逻辑坐标.页面坐标对应于设备坐标.编写逻辑坐标和设备坐标之间的转换代码在GDI+中有了变化.在GDI中,转化是使用Widows API函数LPtoDP()和DPtoLP()通过设备环境进行的,而在GDI+中,由Control类来维护转化过程中的所需要的信息,Form和各种Windows窗体控件设备派生于Control类. GDI+还有第3种坐标,即设备坐标(Device Coordinate).设备坐标类似于页面坐标,但其测量单位不是像素,而是用户通过调用Graphics.PageUnit属性指定的单位.它可以使用的单位除了默认的像素外,还包括英寸和毫米.它可以用作获取设备的不同像素密度方式.如:在监视器上,100像素约是1英寸.但激光打印机可以达到1200dpi(点/英寸)---这表示一个100像素宽的图形在该激光打印机上打印时会比较小.把单位设置为英寸,指定图形为1英寸宽,就可以确保图形在不同的设备上有相同的大小. 颜色 在GDI+中,颜色用System.Drawing.Color结构的实例来表示。一般情况下,初始化这个结构后,就不能使用对应的Color实例对该结构进行一些操作了----只能把它传送给其他的需要Color的调用方法。前面遇到这种结构,在前面的每个示例中都设置了窗口客户区域的背景色,还设置了要显示的各种图形的颜色。Form.BackColor属性返回一个Color实例。本节将详细介绍这个结构,特别是要介绍构建Color的几种不同方式。 (1) 红绿蓝(RGB)值 监视器可以显示的颜色总数非常大---超过160亿。其确切的数字是2的24方式,即16,777,216。显示,需要对这些颜色进行索引,才能指定在给定的某个像素上要显示什么颜色。 给颜色进行索引的最常见方式是把它们分为红绿蓝成份,这种方式基于以下原则:人眼可以分辨的任何颜色都是由一定量的红色光、绿色光和蓝色光组成的。这些光称为成份(component)。实际上,如果每种成份的光分为256种不同的强度,它们提供了足够平滑的过渡,可以把人眼能分辨出来的图像显示为具有照片质量。因此,指定颜色时,可以给出这些成份的量,其值在0~255之间,其中0表示没有这种成份,255表示这种成份的光达到最大的强度。 这些出了向GDI+说明颜色的第一种方式。可以调用静态函数Color.FromArgb()指定该颜色的红绿蓝值。微软没有为此提供构造函数,原因是除了一般的RGB成份外,还有其它方式表示颜色。因此,微软认为i给定以的构造函数传递会引起误解: Color redColor = Color.FromArgb(255,0,0); Color funnyOrangyBrownColor = Color.FromArgb(255,155,100); Color blackColor = Color.FromArgb(0,0,0); Color whiteColor = Color.FromArgb(255,255,255); 3个参数分别是红绿蓝指。这个函数有许多重载方法,其中一些也允许指定Alpha混合指(这是A在方法FromArgb()中的名称)。Aplha混合超出了本章的范围,但把它与屏幕上已有的颜色混合起来,可以描绘出半透明的颜色。这可以得到一些漂亮的效果,常用于游戏。 (2)命名颜色 使用FromArgb()构造颜色是一种非常灵活的技巧,因为它表示可以指定人眼睛辨识出的任何颜色。但是,如果要得到一些标准、众所周知的纯色,例如红色或蓝色,命名想要的颜色是比较简单的。因此微软还在Color中提供了许多静态属性,每个属性返回一种命名颜色。在下面的实例中,把窗口的背景设置为白色时,就使用了其中一种属性: this.BackColor = Color.White; // has the same effect as; // this.BackColor = Color.FromArgb(255,255,255); 有几百种这样的颜色。完整的列表参见SDK文档。包括所有的纯色:红、白、蓝、绿和黑,还包括MediumAquamarine、LightCoral、DarkOrchid等颜色。还有一个KnownColor枚举,列出了命名的颜色。 (3)图形显示模式和安全的调色板 原则上监视器可以显示超出160亿种RGB颜色,实际上这种取决于如何在计算机上这置显示属性。在Windows中,传统上有3个主要的颜色选项:真彩色(24位)、增强色(16位)、256色。(在目前的一些图形卡上,真彩色是32位的,因为硬件进行了优化,但此时32位中只有24位用于该颜色)。 只有真彩色模式允许同时显示所有的RGB颜色。这听起来是最佳选择,但它是有代价的:完整的RGB值需要用3个字节来保存,这表示要显示的每个像素都需要用图形卡内存中的3个字节来保存。如果图形卡内存需要额外的费用,就可以选择其他模式。增强颜色模式用两个字节表示以像素。每个RGB成份用5位就足够了。所以红色只有32种不同的强度,而不是256种。蓝色和绿色也是这样,总共有65535种颜色。这对于需要偶尔察看照片质量级的图像来说是足够了,但比较微妙的阴影区域会被破坏。 256色模式给出的颜色更少。但是在这种模式下,可以选择任何颜色,系统会建立一个调色板,这是一个从160亿RGB颜色中选择出来的256种颜色列表。在调色板中指定了颜色后,图形设备就只显示所指定的这些颜色。当获得高性能和视频内存需要额外的费用时,才使用256色模式。大多数计算机游戏都使用 这种模式----它们仍能得到相当好的图形,因为调色板经过了非常仔细的选择。 一般情况下,如果显示设备使用增强色或256色模式,并要显示某种RGB颜色,它就会从能显示的颜色池中选择一种在数学上最接近的匹配颜色。因此知道颜色模式是非常重要的。如果要绘制某些涉及微妙阴影区域或照片质量级的图像,而用户没有选择24位颜色模式,就看不到期望的效果。如果要使用GDI+进行绘制,就应该用不同的颜色模式测试应用程序。 (4)安全调色板 这是一种非常常见的默认调色板。它工作的方式是为每种颜色成分这置6个间隔相等的值,这些值分别是0,51,102,153,204,255。换言之,红色成分可以是这些值中的任一个。绿色成分和蓝色成分也一样。所以安全调色板中的颜色就包括(0,0,0)(黑色)、(153,0,0)(暗红色)、(0,255,102)(蓝绿色)等,这样就得到了6的立方=216种颜色。这是一种让调色板包含色谱中颜色和所有亮度的简单方式,但实际上这是不可行的,因为数学上登间隔的颜色成分并不表示这些颜色的区别在人眼看来也是相等的。但安全调色板使用非常广泛,相当多的应用程序和图像仍然使用安全调色板上的颜色。 如果把Windows设置为256色模式,默认的调色板就是安全调色板,其中添加了20种标准的Windows颜色和20种备用颜色。 画笔和钢笔 本节介绍两个辅助类,在绘制图形时需要使用它们。前面已经见过了Pen类,它用于告诉工人graphics实例如何绘制线条。相关的类是System.Drawing.Brush,告诉graphics实例如何填充区域。例如,Pen用于绘制前面示例中的矩形和椭圆的边框。如果需要把这些图形绘制为实心的,就要使用画笔指定如何填充它们。这两个类有一个共同点:很难对他们调用任何方法。用需要的颜色和其他属性构造一个Pen或Brush实例,再把它传送给需要Pen或Brush的绘图方法即可。 <注>: 如果使用以前的GDI编程,可能会注意到在前两个示例中,在GDI+中使用Pen的方式是不同的.在GDI中,一般是调用一个WindowsAPI函数SelectObject(),它把钢笔关联到设备环境上.这个钢笔用于所有需要钢笔的绘图操作中,直到再次调用SelectObject()通知设备环境停止使用它时为止.这个规则也适用于画笔或其它对象,例如字体和位图,而使用GDI+,微软使用一种无状态的模式,其中没有默认的钢笔或其它帮助对象.只需给每个方法调用指定合适的帮助对象即可. <1>画笔 GDI+有几种不同类型的画笔,这里只解释几个比较简单的画笔,每种画笔都由一个派生自抽象类System.Drawing.Brush的类实例来表示.最简单的画笔System.Drawing.SolidBrush仅指定了区域用纯色来填充: Brush solidBeigeBrush = new SolidBrush(Color.Beige); Brush solidFunnyOrangeyBrownBrush = new SolidBrush(Color.FromArgb(25,155,100)); 另外,如果画笔是一种Web安全颜色,就可以用另一个类System.Drawing.Brushes构造出画笔.Brushes是永远不能实例化的一个类(它有一个私有构造函数,禁止实例化).它有许多静态属性,每个属性都返回指定颜色的画笔.如下: Brush solidAzureBrush = Burshes.Azure; Brush solidChoolateBrush = BrushesChoolate; 比较复杂的一种画笔是影线画笔(hatch brush),它通过绘制一种模式填充区域,这种类型的画笔比较高级,所以Drawing2D命名空间中,用System.Drawing.Drawing2D.HatchBrush类表示.Brushes类不能帮助我们使用影线画笔,而需通过提供一个影线型式和两种颜色(前景色和背景色,背景色可以忽略,此时将使用默认的黑色),来显示构造一个影线画笔.影线型式可以取自于枚举Sysytem.Drawing.Drawing2D.HatchStyle,其中有许多HatchStyle值,其完整列表参阅SDK. 一般型式包括: ForwardDiagonal,Cross,DiagonalCross,SmallConfetti,ZigZag.示例如下: Brush crossBrush = new HatchBrush(HatchStyle.Cross,Color.Azure); // background color of CrossBrush is black Brush brickBrush = new HatchBrush(HatchStyle.DiagonalBrick,Color.DarkGoldenrod,Color.Cyan); GDI只能使用实践和影线画笔,GDI+添加了两种新画笔: * System.Drawing.Drawing2D.LinearGradientBrush用一种在屏幕上可变的颜色填充区域. * System.Drawing.Drawing2D.PathGradientBrush与此类似,但其颜色沿着要填充的区域的路径而变化. <2>钢笔 钢笔只使用一个类System.Drawing.Pen来表示.但钢笔比画笔复杂一些,因为它需要指定线条应有多宽(像素),对于一条比较宽的线段,还要确定如何填充该线条中的区域.钢笔还可以指定其他许多属性,本章不讨论它们,其中包括前面提到的Alignment属性,该属性表示相对于图形的边框,线条该如何绘制,以及在线条的末尾绘制什么图形(是否使图形光滑过度). 粗线条中的区域可以用纯色填充,或者使用画笔来填充.因此Pen实例可以包括Brush实例的引用.这是非常强大的,因为这表示可以绘制有影线填充或线性阴影的线条.构造Pen实例有四中不同的方式.可以通过传送一种颜色,或者传送一种画笔.这两个构造函数都会生成一个像素宽的钢笔.另外,还可以传送一种颜色或画笔,以及一个表示钢笔宽度的float类型的值.(该宽度必须是一个float类型的值,以防执行绘图操作的Graphics对象使用非默认的单位,例如毫米或英寸,例如可以指定宽度是英寸的某个分数).例如可以构造如下的钢笔: Brush brickBrush = new HatchBrush(HatchStyle.DiagonalBrick,Color.DarkGoldenrod,Color.Cyan); Pen solidBluePen = new Pen(Color.FromArgb(0,0,255)); Pen solidWideBluePen = new Pen(Color.Blue,4); Pen brickPen = new Pen(brickBrush); Pen brickWidePen = new Pen(brickBrush,10); 另外,为了快速构造钢笔,还可以使用类System.Drawing.Pens,它与Brushes类一样.包括许多存储好的钢笔.这些钢笔的宽度都是一个像素,使用通常的Web安全颜色,这样就可以用下述方式构建一个钢笔: Pen solidYellowPen = Pens.Yellow; 绘制图形和线条System.Drawing.Graphics有很多方法,利用这些方法可以绘制各种线条、空心图形和实心图型。下图给出了只要方法。 在结束绘制简单对象的主题前,用一个简单示例来说明使用画笔可以得到的各种可视效果。该实例是ScrollMoreShapes,它是ScrollShapes的修正版本。除了矩形和椭圆外,我们还添加了一条粗线,用各种定制的画笔填充图形。前面解释了绘图的规则,所以这里只给出代码,而不作多的注释。首先,因为添加了新画笔,所以需要指定使用命名空间System.Drawing.Drawing2D: using System; using System.Collection.Generic; using System.ComponentModel; usgin System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms; 接着是Form1类中的一些额外字段,其中包含了要绘制图形的位置信息,以为要使用的各种钢笔和画笔: private Rectangle rectangleBounds = new Rectangle(new Point(0,0),new Size(200,200)); private Rectangle ellipseBounds = new Rectangle(new Point(50,200),new Size(200,150)); private Pen bluePen = new Pen(Color.Blue,3); private Pen redPen = new Pen(Color.Red,2); private Brush solidAzureBrush = Brushes.Azure; private Brush solidYellowBrush = new SolidBrush(Color.Yellow); static private Brush brickBrush = new HatchBrush(HatchStyle.DiagonalBrick,Color.DarkGoldenrod,Color.Cyan); private Pen brickWidePen = new Pen(brickBrush,10); 把BrickBrush字段声明为静态,就可以使用该字段的值初始化BrickWidePen字段了。C#不允许使用一个实例字段初始化另一个实例字段,因为还没有定义要先初始化哪个实例字段,如果把字段声明为静态字段就可以解决这个问题,因为只实例化了Form1类的实例,字段是静态字段还是实例字段就不重要了。 from:http://www.cnblogs.com/Holmes-Jin/archive/2012/01/30/2332030.html |
高级T_SQL语法(2005)
排名函数: RANK() 返回结果集的分区内每行的排名,FIELD为排名依据。行的排名是相关行之前的排名数加1.例如有两个并列第一,那么后一个将排名第三,而不是第二。 语法:RANK() OVER([PARTITION BY clause]<ORDER BY clause>) 示例: select *, rank() over (partition by ClassID order by Mark DESC )as rank from dbo.Student 结果: DENSE_RANK():返回结果集分区中行的排名。在排名中没有任何间断。行的排名等于所讨论的行之前的所有排名数加1.例如有两个并列第一,那么下一个将排名第二,而不是第三。 语法:DENSE_RANK() OVER([PARTITION BY clause]<ORDER BY clause>) 示例: select *, dense_rank() over (partition by ClassID order by MarkDESC ) as rank from dbo.Student 结果: NTILE():将有序分区内的行分发到指定数目的组中。各个组由编号。,编号从1开始,对于每个行,NITLE()将返回此行所属组的编号。 语法:NTILE (int_expressions) OVER([PARTITION BY clause]<ORDER BY clause>) int_expressions:正整数常量表达式。用于指定每个分区必须被划分成的组数。 示例: select *,NTILE(4) over ( order by Mark DESC ) as NewCLASS fromdbo.Student ROW_NUMBER()一般用于数据库分页。返回结果集分区内行的序列号。每个分区的第一行从1开始。 语法:ROW_NUMBER () OVER([PARTITION BY clause]<ORDER BY clause>) 示例: select *, ROW_NUMBER() over (partition by […]
View DetailsC#GDI+图像处理
支持格式:BMP、GIF、JPEG、EXIF、PNG、TIFF、ICON、WMF、EMF等,几乎涵盖所有常用格式 图像类: Image类:Bitmap和Metafile的类提供功能的抽象基类。 Metafile类:定义图形图元文件,图元文件包含描述一系列图形操作的记录,这些操作可以被记录(构造)和被回放(显示) Bitmap类:封装GDI+位图,此位图由图形图像及其属性的像素数据组成,Bitmap是用于处理由像素数据定义的图像的对象。 常用属性以及方法: 名称 说明 公共属性 Height 获取此Image对象的高度。 RawFormat 获取此Image对象的格式。 Size 获取此Image对象的宽度和高度。 Width 获取此Image对象的宽度。 公共方法 GetPixel 获取此Bitmap中指定像素的颜色。 MakeTransparent 使默认的透明颜色对此Bitmap透明。 RotateFlip 旋转、翻转或者同事旋转和翻转Image对象。 Save 将Image对象以指定的格式保存到指定的Stream对象。 SetPixel 设置Bitmap对象中指定像素的颜色。 SetPropertyItem 将指定的属性项设置为指定的值。 SetResolution 设置此Bitmap的分辨率。 构造BitMap实例: public Bitmap(Image ); //从现有图像创建,其中可换成等效参数 public Bitmap(int, int); public Bitmap(Image, bool );//bool:颜色校正标志位 public Bitmap(Type , string );//type提取资源的类, string 资源名 public Bitmap(int , int , PixelFormat );// PixelFormat像素格式 枚举 public Bitmap(int , int , Graphics);// Graphics设置分辨率 public Bitmap(int , int , int stride, PixelFormat , IntPtr );// stride 指定相邻扫描行开始处之间字节偏移量的整数。传递给此参数的值必须为4 的倍数。IntPtr指向包含像素数据的字节数组的指针。 PixelFormat枚举: 名称 说明 DontCare = 0 没有指定像素格式 […]
View DetailsC#GDI+基础(二)画刷详解
命名空间: System.Drawing.Drawing2D SolidBrush:一般的画刷,通常只用一种颜色去填充GDI+图形 创建一般画刷: SolidBrush sdBrush1 = new SolidBrush(Color); HatchBrush:阴影画刷,有两种颜色:前景色和背景色,以及6种阴影。 创建阴影画刷: HatchBrush(HatchStyle,Color);//前景 HatchBrush(HatchStyle,Color,Color);//前景、背景 HatchStyle对应阴影方案列表。 名称 说明 BackwardDiagonal 从右上到左下的对角线的线条图案。 Cross 指定交叉的水平线和垂直线。 DarkDownwardDiagonal 从顶点到底点向右倾斜的对角线,两边夹角比ForwardDiagonal小50%,宽度是其两倍。此阴影图案不是锯齿消除的。 DarkHorizontal 指定水平线的两边夹角比Horizontal小50%并且宽度是Horizontal的两倍。 DarkUpwardDiagonal 指定从顶点到底点向左倾斜的对角线,其两边夹角比BackwardDiagonal小50%,宽度是其两倍,但这些直线不是锯齿消除的。 DarkVertical 指定垂直线的两边夹角比Vertical小50%并且宽度是其两倍。 DashedDownwardDiagonal 指定虚线对角线,这些对角线从顶点到底点向右倾斜。 DashedHorizontal 指定虚线水平线。 DashedUpwardDiagonal 指定虚线对角线,这些对角线从顶点到底点向左倾斜。 DashedVertical 指定虚线垂直线。 DiagonalBrick 指定具有分层砖块外观的阴影,它从顶点到底点向左倾斜。 DiagonalCross 交叉对角线的图案。 Divot 指定具有草皮层外观的阴影。 ForwardDiagonal 从左上到右下的对角线的线条图案。 Horizontal 水平线的图案。 HorizontalBrick 指定具有水平分层砖块外观的阴影。 LargeGrid 指定阴影样式Cross。 LightHorizontal 指定水平线,其两边夹角比Horizontal小50%。 LightVertical 指定垂直线的两边夹角比Vertical小50%。 Max 指定阴影样式SolidDiamond。 Min 指定阴影样式Horizontal。 NarrowHorizontal 指定水平线的两边夹角比阴影样式Horizontal小 75%(或者比LightHorizontal小25%)。 NarrowVertical 指定垂直线的两边夹角比阴影样式Vertical小 75%(或者比LightVertica小25%)。 OutlinedDiamond 指定互相交叉的正向对角线和反向对角线,但这些对角线不是锯齿消除的。 Percent05 指定5%阴影。前景色与背景色的比例为5:100。 Percent90 指定90%阴影。前景色与背景色的比例为90:100。 Plaid 指定具有格子花呢材料外观的阴影。 Shingle 指定带有对角分层鹅卵石外观的阴影,它从顶点到底点向右倾斜。 SmallCheckerBoard 指定带有棋盘外观的阴影。 SmallConfetti 指定带有五彩纸屑外观的阴影。 SolidDiamond 指定具有对角放置的棋盘外观的阴影。 Sphere 指定具有球体彼此相邻放置的外观的阴影。 Trellis 指定具有格架外观的阴影。 Vertical 垂直线的图案。 […]
View DetailsC#GDI+编程基础(一)
GDI+存在的意义:将编程与具体硬件实现细节分开。 GDI+步骤:获取画布,绘制图像、处理图像 命名空间: System.Drawing命名空间提供对GDI+基本图形功能的访问 System.Drawing.Drawing2D:提供高级的二维和矢量图形功能 System.Drawing.Imaging:命名空间提供高级GDI+图像处理功能 System.Drawing.Text:提供高级GDI+排班功能 System.Drawing.Pringting:提供打印相关服务 System.Drawing.Design:扩展设计时,用户界面逻辑和绘制的类。用于扩展,自定义。 画图工具: Graphics(画布):类封装一个GDI+绘图图面,提供将对象绘制到显示设备的方法,Graphics与特定的设备上下文关联。画图方法都被包括在Graphics类中,在画任何对象之前都需要创建一个Graphics类实例作为画布。 创建画布 (三种方法): 利用控件或窗体的Paint事件中的PainEventArgs。 适用场景:为控件创建绘制代码。 示例: //窗体的Paint事件的响应方法 private void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; } //直接重载控件或窗体的OnPaint方法: protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; } 注意:Paint事件在重绘控件时发生。 调用某控件或窗体的CreateGraphics方法以获取对Graphics对象的引用,该对象表示该控件或窗体的绘图图面。 适用场景:在已存在的窗体或控件上绘图 Graphics g = this.CreateGraphics(); 由从Image继承的任何对象创建Graphics对象。 适用场景: 需要更改已存在的图像 Image img = Image.FromFile(@"IMG\graph.jpg"); Graphics g = Graphics.FromImage(img); 方法成员: 名称 说明 DrawArc 画弧。 DrawBezier 画立体的贝尔塞曲线。 DrawBeziers 画连续立体的贝尔塞曲线。 DrawClosedCurve 画闭合曲线。 DrawCurve 画曲线。 DrawEllipse 画椭圆。 DrawImage 画图像。 DrawLine 画线。 DrawPath 通过路径画线和曲线。 DrawPie 画饼形。 DrawPolygon […]
View DetailsDrawString 抗锯齿
在绘制文字之前加上这一行,就可以绘制任意文字而不会出现锯齿: g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; from:http://blog.sina.com.cn/s/blog_7744890b0102v13q.html
View DetailsSQL中Group By的使用
1、概述 2、原始表 3、简单Group By 4、Group By 和 Order By 5、Group By中Select指定的字段限制 6、Group By All 7、Group By与聚合函数 8、Having与Where的区别 9、Compute 和 Compute By 1、概述 “Group By”从字面意义上理解就是根据“By”指定的规则对数据进行分组,所谓的分组就是将一个“数据集”划分成若干个“小区域”,然后针对若干个“小区域”进行数据处理。 2、原始表 3、简单Group By 示例1
1 2 3 |
select 类别, sum(数量) as 数量之和 from A group by 类别 |
返回结果如下表,实际上就是分类汇总。 4、Group By 和 Order By 示例2
1 2 3 4 |
select 类别, sum(数量) AS 数量之和 from A group by 类别 order by sum(数量) desc |
返回结果如下表 在Access中不可以使用“order by 数量之和 desc”,但在SQL Server中则可以。 5、Group By中Select指定的字段限制 示例3
1 2 3 4 |
select 类别, sum(数量) as 数量之和, 摘要 from A group by 类别 order by 类别 desc |
示例3执行后会提示下错误,如下图。这就是需要注意的一点,在select指定的字段要么就要包含在Group By语句的后面,作为分组的依据;要么就要被包含在聚合函数中。 6、Group By All 示例4
1 2 3 |
select 类别, 摘要, sum(数量) as 数量之和 from A group by all 类别, 摘要 |
示例4中则可以指定“摘要”字段,其原因在于“多列分组”中包含了“摘要字段”,其执行结果如下表 “多列分组”实际上就是就是按照多列(类别+摘要)合并后的值进行分组,示例4中可以看到“a, a2001, 13”为“a, a2001, 11”和“a, a2001, 2”两条记录的合并。 SQL Server中虽然支持“group by all”,但Microsoft SQL Server 的未来版本中将删除 GROUP BY ALL,避免在新的开发工作中使用 GROUP BY ALL。Access中是不支持“Group By All”的,但Access中同样支持多列分组,上述SQL Server中的SQL在Access可以写成
1 2 3 |
select 类别, 摘要, sum(数量) AS 数量之和 from A group by 类别, 摘要 |
7、Group By与聚合函数 在示例3中提到group by语句中select指定的字段必须是“分组依据字段”,其他字段若想出现在select中则必须包含在聚合函数中,常见的聚合函数如下表: 函数 作用 支持性 sum(列名) 求和 […]
View Details“System.Data.Entity.Internal.AppConfig"的类型初始值设定项引发异常。
1 2 3 4 |
<connectionStrings> <add name="ConnectionStringName" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=DataBaseName;Integrated Security=True;Pooling=False" /> </connectionStrings> |
必须写在
1 2 3 4 |
<configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> |
之后 。 from:http://www.cnblogs.com/A2008A/archive/2012/08/10/2631443.html
View Details如何用开源经历为你的简历增加光彩
在这篇文章中,我将会分享我的方法,让大家利用开源贡献在技术领域的求职中脱颖而出,成为强有力的候选者。 凡事预则立,不预则废。在你即将进入一个新的领域或者正准备熬夜修订你的简历之前,清楚地定义你正在寻找的工作的特征是值得的。你的简历是一部有说服力的作品,因此你必须了解你的观众,从而让它发挥出所有的潜力。看你简历的可能是任何需要你的技能并且能在预算之内聘用你的人。当编辑简历的时候,读一读你的简历上的内容,同时想象一下,以他们的角度怎么看待这份简历。你看起来像是一个“你”将会聘用的候选人吗? 我个人认为,对于目标职位的理想候选人所表现出来的关键特征,列出一张清单是很有帮助的。我结合了个人经验、阅读工作招聘信息、询问相同角色的同事等方面来收集这个清单。LinkedIn 和各种会议是寻求一些乐意提供这种建议的人的很好的地方。一些人喜欢谈论他们自己,那么通过邀请他们讲述他们自己的一些故事可以帮助你来拓展你的知识面,这样大家都会感觉很好。当你和其他人谈论他们的职业路线时,你不仅将会明白怎样去得到你想要从事的工作,而且还能知道你应该避免那些容易让你失去工作机会的特征或行为。 例如,对于一个不太资深的工作位置来说,关键特征列表可能如下所示: 技术方面: 拥有 CI (持续集成) 方面的经验,特别是 Jenkins 深厚的脚本编写背景,如 Python 和 Ruby 精通 Eclipse IDE 基本的 Git 和 Bash 知识 个人而言: 自我驱动的学习者 良好的交流和文档技巧 在团队开发方面富有经验(团队成员) 精通事件跟踪的工作流 尽管去申请职位 记住,你没有必要为了得到一份工作而去满足上面的工作描述列表中列出的每个标准。 工作细节(JD)描述了这个角色,让你一开始就知道你即将签约并为之工作几年的公司的全部信息,并且这份工作并不会让你觉得有什么挑战性,或者要求你去拓展你的技能。如果你对你无法满足清单上的技能列表而感到紧张,那么检查一下自己是否有来自其他方面的经历并能与之媲美的技能。例如,即使有些人从来没有使用过 Jenkins[1],那他也可能从之前使用过Buildbot[2] 或者travis CI[3] 的项目经验中明白持续集成测试的原则。 如果你正在申请一家大型公司,他们可能拥有一个专门的部门和一套完整的筛选过程来确保他们不会聘用任何不能胜任职位的候选人。也就是说,在你求职的过程中,你所能做的只是提交申请,而决定是否拒绝你是公司管理层的工作。不要过早地将工作拒之门外。 现在你已经知道了你的任务是什么,并且还知道你将需要让面试官印象深刻的技巧。下一步要做的取决于你已有的经验。 制造已经存在的事物之间的关联 列出一张你过去几年曾经参与过的所有项目。下面是一条快速得到这张清单的方法,跳转到你的 Github profile 中的Repositories标签页,并且过滤掉 fork 过来的项目。除此之外,检查下你的清单上是否有曾经处于领导地位的Organizations[4]。如果你已经有了一份简历,那么请确保你已经将你所有的经历都列在了上面。 考虑下任何一个你曾经作为一个潜在的领导经历并拥有过特权的 IRC 频道。检查下你的 Meetup 和Eventbrite 账号,并将你曾经组织过或者作为志愿者参与过的活动添加到你的清单上。浏览你前几年的日程并且标注所有志愿服务,或者有作为导师的经历,又或者参与过的公共演讲。 现在进入了比较艰难的环节了,将清单上列出的必备技能与个人经历列表上的内容一一对照,我喜欢给该工作所需要的每个特征用一个字母或者数字作为标记,然后在每一段你经历或参与过并表现出了某一特征的地方标记相同的符号。当你不太确定的时候,那就毫不犹豫地标记上它,尽管这样做更像是在吹嘘,但也好过显示出你的无能。 在我们写简历的时候常常被这样的情况所困扰,就是我们不愿冒着过分吹嘘自己的技能的风险。通常应该这样去想,“那些组织了聚会的人会表现出了更好的领导才能和计划技巧吗?”,而不是“当我组织了这个聚会的时候我是否展示出了这些技巧?”。 如果你已经充分了解了你在过去的一两年里的业余时间都是怎么度过的,而且你写了很多代码,那么你可能现在正面临着一个令人奇怪的问题,你已经拥有了太多的经验以至于一张纸的简历已经无法容纳下这些经验了。那么,如果那些列在你的清单上的经验,但无法证明你尝试去表现的任何技能的话,那么请扔掉它们吧。如果这份已经被缩短的简历清单上的内容仍然超过一张单页纸的容量的话,那么将你的经验按照一定的优先级排序,例如根据与所需技术的相关经历或丰富经验。 在这一方面,显而易见,如果你想要磨练一个独特的技能,那么你就需要一个不错的经历。考虑使用一个类似 OpenHatch[5] 的问题聚合器,并用它来寻找一个通过使用你从没使用过的工具和技术来锻炼你的技能的开源项目。 让你的简历更加漂亮 一份简历是否美观取决于它的简洁度、清晰度和布局。每一段经历都应该通过足够的信息来展示给读者,并让他们立刻明白为什么你要将它包含进去,而且恰到好处。每种类型的信息都应该使用一致的文档格式来表示,一份含有斜体格式的日期或者右对齐的或者与整体风格不协调的部分绝对会让人分心。 使用工具来给你的简历排版会使之前设定的目标更加容易实现。我喜欢使用 LaTeX[6],因为它的宏系统能够使可视化一致性变得更加容易,并且大量的面试官都能一眼就认出它。你的工具的选择可能是LibreOffice[7] 或者 HTML,这取决于你的技能和你希望怎样去发布你的简历。 记住一点,一份以电子方式提交的简历可以通过关键字被浏览到。因此,当你需要描述你的工作经历的时候使用和工作招聘告示一样的英文缩写对你的求职会有很大的帮助。为了让你的简历更加容易被面试官看到,首先就要放上最重要的信息。 程序员通常难以在为文档排版时量化平衡和布局。我最喜欢的修改和评估我的文档中的空格是否处于正确位置的技术,就是全屏显示我的 PDF 或者打印出来,然后在镜子里面查看它。如果你正在使用 LibreOffice Writer,保存一份你的简历的副本,然后将你的简历中的字体换成一种你看不懂的语言。这两种技术都强制将你从阅读的内容中脱离出来,让你以一种新的方式查看文档的整体布局。他们把你从一个“那句话措辞不当!”这样的批评转到了注意如“在这行上只有一个字,看起来挺逗”之类的事情。 最后,再次检查你的简历是否在它将要的展示的多媒体上看起来完全正确。如果你以网页的形式发布它,那么在不同屏幕大小的浏览器中测试它的效果。如果它是一份 PDF 文档,那么在你的手机或者你的朋友的电脑上打开它,并确保它所需要的字体都是可用的。 接下来的步骤 最后,不要让你辛苦做出来的简历内容浪费了,将它完整的复制到你的 LinkedIn 帐号上(完全使用招聘公告中的流行词),然后毫无疑问招聘人员就会找到你了。尽管他们描述的工作内容并不是恰好适合你,但是你可以利用他们的时间和兴趣来得到关于你的简历中有哪些地方好与不好的反馈信息。 作者:edunham[8] 译者:pengkai[9] 校对:mudongliang[10],wxy[11] 本文由 LCTT[12] 原创编译,Linux中国[13] 荣誉推出 [1]: https://jenkins-ci.org/ [2]: http://buildbot.net/ [3]: https://travis-ci.org/ […]
View Details17 年编程生涯的三大经验总结
今年将迎来我编程的第十七个年头。我的编程之旅始于九十年代末,上大学的时候,主要涉足基于表格的网页设计,传统的ASP,和Microsoft Access数据库。原来只是当作业余爱好的编程现在已经成为了我的事业和激情。我一生一半的时间都在学习、蹒跚、成功、失败,并且经常情不自禁地为代码 美丽和复杂的天性而折腰。 我在代码上淫浸了足够长的时间,因此看到了很多语言和平台的兴盛和消亡,看到了很多模式被普及,被苛责,然后再次被推广。在某些时候,我常常分不清这是大势所趋还是明日黄花。 编程的流行趋势是短暂的,但我坚守的规则,往往在生活中的其他地方也能发挥作用。事实上,生活就像代码(我已经买了这个域名来证明这一点!)。以下是我总结的3个伟大的经验教训,历经一次又一次编程和生活的大浪淘沙。 1.可商榷的决定往往是一种权衡。 伟大的辩论总是发生在开发社区中。无论它是最近关于TDD作为web开发的一种可行方法的辩论,还是什么水平的开发人员应该使用ORM(或 micro-ORMs)。无论是.NET MVC应该优于WebForms还是以JavaScript为中心的app应该比基于页面的app更受青睐,对我来说,答案都一样:看你权衡之后的取舍? 在任何比较两种流行方法的辩论中,我们总是会从自己的立场出发,两利相权取其重,两害相权取其轻。在我的职业生涯早期,我曾执着于追求所谓的正确答 案。感觉过程是线性的:摆脱做事的老办法,转而投向新的并且更好的方法的怀抱。曾经有一段时间我深信,编写自己的SQL查询是一种过时的练习,并且 ORMs是最后赢家。 但是,我了解到,更好的办法应该由内容决定的。例如,今天完全成熟的ORMs在隔离映射相关数据网格到对象的冗长管道提供了伟大服务,但隔离也使得某种非标准查询变得困难并且有潜在的效率低下问题。n+1 select problem就是经典的在少写代码和写更多高效代码之间做权衡。我使用ORM的程度完全受我期待应用程序使用的数据量,我所受到的潜在的时间限制,app长期可扩展性需求这三者的影响。(顺便说一句,我目前是micro-ORMs,比如说Dapper的忠实粉丝,它能让我编写我自己的SQL和一些精巧的对象-关系映射)。 我已经将这个经验应用到了我生活的其他方面。我是应该买一套公寓还是长租房子?我是应该启动自己的生意还是工作于已经成立的公司?没有绝对正确的选择。当你权衡利弊了之后,你便可以更好地应对生活中的各种难题。 2.清晰并不总和简洁相关。 和大多数工程师一样,我对持续重构一直到代码尽可能地少和简洁的机会垂涎三尺。如果可以选择更少又更简洁的代码来完成同样的任务,那么我为什么要选 择要个更多代码的方案呢?通常情况下,更简洁的语言会导致更好的交流。画蛇添足只会阻碍核心信息的提取。但是,最终的目标不应该是简洁——而应该是可交 流。于我而言,下面这段直截了当的代码,在它更长的时候…… ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (HasFarm() && HasBoat()) { Broadcast("You are wealthy!"); } else if (HasFarm() && !HasBoat()) { Broadcast("You are OK!"); } else if (!HasFarm() && HasBoat()) { Broadcast("You are OK!"); } else if (!HasFarm() && !HasBoat()) { Broadcast("You are poor!"); } ……反而比这个简洁版本更明确。 ? 1 2 3 (HasFarm() && HasBoat()) ? Broadcast("You are wealthy!") : (HasFarm() || HasBoat()) ? Broadcast("You are OK!") : Broadcast("You are poor!"); 虽然这是一个品味问题(有些人可能会觉得后者看上去更加一目了然),但是我在这里要表述的观点是,有时候解释的最伟大方法并不是简化。这个经验也适 用于日常生活,我花了大量时间来思考怎么样才能更好地传达消息以便于对方接收——有时更详细的讲解并非没有价值,而是更明确传达信息的必须。 举例来说,我想要更明确和更详细地告诉我爸爸应该如何关闭iPad(“按住右侧的按钮一段时间……”)。或者,我看似多此一举地键入了一些我已经提 交到本地分支的内容给我的同事(“刚刚犯的错误已被修复”),然后当它涉及到部署更新到产品中时,我就能很明确地知道哪些具体的提交被合并和出现(“检查 4812-4822行,其中包括在6/15发行版本中的DoneDone问题,将在今晚的产品发布中提出来。”)。 3.累计良性债务,并且要持续偿还。 我在一个特别害怕欠债的家庭中长大。八十年代中期,我的父母倾其所有又东拼西凑,付了他们第一套房子75%的首付,然后在七年内付清了剩余款项。用现金支付是常态。信用支付在他们看来几乎是一种罪过。作为一个孩子,我的看法是,债务完全是坏的。我从不认为欠债是一种优势。 直到我看到其他人是如何对待债务的——在我20出头的时候——我终于知道了债务也可以是有益的。如果你能够合理地承担债务,那么之后你也能获得成功。如果借助现在更好的上升空间可以加速你之后的成长,那么债务可以成为一笔巨大的财富。 代码也是如此。有时它值得你现在承担一点债务——错过抽象或者有一些未优化的SQL代码——如果这样做可以让你更快地发布内容给不断增长的观众的话。关键是要了解你必须偿还它,以及你可以在适当的时间段之后偿还。 这就是债务在生活和编程中的窍门。偿还债务需要持续进行。将一周10%的时间用于重构,相当于你是在按时支付编码的信用卡账单。如果你保持一种持续、可支撑的还债状态,那么累积债务实际上对你是有好处的。 译文链接:http://www.codeceo.com/article/17-year-3-tips-programming.html 英文原文:Programming’s three life lessons 翻译作者:码农网 – 小峰 from:http://www.oschina.net/news/73943/17-years-code
View Details