《C# GDI+ 破境之道》:第一境 GDI+基础 —— 第一节:画直线
今天正式开一本新书,《C# GDI+ 破镜之道》,同样是破镜之道系列丛书的一分子。
关于GDI+呢,官方的解释是这样的:
GDI+ 是 Microsoft Windows 操作系统的窗体子系统应用程序编程接口 (API)。 GDI+ 是负责在屏幕和打印机上显示的信息。 顾名思义,GDI+ 是包含 GDI 与早期版本的 Windows 图形设备接口的后续版本。
好,两个关键信息:
- 窗体子系统应用的编程接口
- 图形设备接口
充分说明了GDI+的应用场景与用途。需要了解更多呢,就去查阅一下吧。
本书的开始,不打算去解释一些枯燥的概念,比如什么是Graphics、Brush、Pen甚至是Color;第一境毕竟是基础,我打算先带大家玩儿,等玩儿开了、玩儿嗨了,咱们再来总结这些概念,就会相当好理解了。咱们就先从最基本的画元素开始吧:)
本节,主要是说道一下如何使用GDI+画直线。体育老师说了,两点确定一条直线,那么,画直线的关键呢,就是确定两个点了。音乐老师也说了,直线呢,是向两边无限延长的,木有尽头。那我们还是别挑战无极限了,所以,咱们在这里说的画直线呢,其实是画线段。
这是我建立的一个简单的WinForm窗体(FormDrawLines)。 摆了几个按钮,用来绘制各种不同的线条以及展示不同线条的特性。
两个辅助按钮,用来切换线条的颜色和窗体是否使用双缓冲。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms; public partial class FormDrawLines : Form
{
private Random random = null;
private Color penColor = Color.Transparent;
private Point lastMouseDownLocation = Point.Empty;
private bool startDrawPointToPointLine = false;
private bool startDrawFollowMouseLine = false; public FormDrawLines()
{
InitializeComponent();
random = new Random(DateTime.Now.Millisecond);
penColor = Color.White;
} …… }
命名空间引用、私有变量及构造函数
几个辅助方法,不是本节重点,这里简单说明一下用途,一笔带过:P
1、获取画布中的一个随机点
private Point GetRandomPoint()
{
return new Point(random.Next(, ClientRectangle.Width), random.Next(, ClientRectangle.Height - pnlToolbox.Height));
}
获取随机点 —— GetRandomPoint
2、显示信息,其中,lblInformation为一个Label控件。
private void ShowInformation(string message)
{
lblInformation.Text = message;
}
显示信息 —— ShowInformation
3、切换线条颜色,其中,colors为ColorDialog组件。
private void btnChangePenColor_Click(object sender, EventArgs e)
{
if (colors.ShowDialog(this) == DialogResult.OK)
{
penColor = colors.Color;
}
}
切换线条颜色 —— btnChangePenColor_Click
4、切换是否使用双缓冲
private void btnSwitchDoubleBuffered_Click(object sender, EventArgs e)
{
DoubleBuffered = !DoubleBuffered; ShowInformation($"二级缓冲:{DoubleBuffered}。");
}
切换是否使用双缓冲 —— btnSwitchDoubleBuffered_Click
下面是本节的重点:
1、随机画线
private void btnDrawRandomLine_Click(object sender, EventArgs e)
{
var pointA = GetRandomPoint();
var pointB = GetRandomPoint(); using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 2f))
{
g.Clear(SystemColors.AppWorkspace);
g.DrawLine(pen, pointA, pointB);
} ShowInformation($"画随机线,{pointA}->{pointB}。");
}
随机画线 —— btnDrawRandomLine_Click
g.Clear(SystemColors.AppWorkspace); 是用来清屏的。
关键方法是
//
// Summary:
// Draws a line connecting two System.Drawing.Point structures.
//
// Parameters:
// pen:
// System.Drawing.Pen that determines the color, width, and style of the line.
//
// pt1:
// System.Drawing.Point structure that represents the first point to connect.
//
// pt2:
// System.Drawing.Point structure that represents the second point to connect.
//
// Exceptions:
// T:System.ArgumentNullException:
// pen is null.
public void DrawLine(Pen pen, Point pt1, Point pt2);
Graphics.DrawLine 方法原型
这是画线的最基础方法,给一根笔、两个点,就可以在画布上作画了:)
- 笔,决定了线的颜色及粗细;
- 两点,决定了线的位置及长度;
应该不难理解。
2、消除锯齿
通过“随机画线”,我们发现,画出的线边缘锯齿状严重,垂直和水平线还好,带点角度就惨不忍睹了。还好,GDI+为我们提供了一系列消除锯齿的选项,虽然有时也很难差强人意,不过总的来说还是可以接受的。
private void btnDrawSmoothLine_Click(object sender, EventArgs e)
{
var pointA = GetRandomPoint();
var pointB = GetRandomPoint();
var mode = (SmoothingMode)(random.Next(, )); using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 2f))
{
g.Clear(SystemColors.AppWorkspace);
g.SmoothingMode = mode;
g.DrawLine(pen, pointA, pointB);
} ShowInformation($"消除锯齿,{pointA}->{pointB},模式:{mode.ToString()}。");
}
消除锯齿 —— btnDrawSmoothLine_Click
关键点在于g.SmoothingMode = mode;为了尽量多的展示平滑模式带来的效果,mode来自于System.Drawing.Drawing2D.SmoothingMode的随机取值;
//
// Summary:
// Specifies whether smoothing (antialiasing) is applied to lines and curves and
// the edges of filled areas.
public enum SmoothingMode
{
//
// Summary:
// Specifies an invalid mode.
Invalid = -,
//
// Summary:
// Specifies no antialiasing.
Default = ,
//
// Summary:
// Specifies no antialiasing.
HighSpeed = ,
//
// Summary:
// Specifies antialiased rendering.
HighQuality = ,
//
// Summary:
// Specifies no antialiasing.
None = ,
//
// Summary:
// Specifies antialiased rendering.
AntiAlias =
}
System.Drawing.Drawing2D.SmoothingMode
严格来讲,消除锯齿并不属于画线的范畴,在画其他图形元素时同样有效,他归属于GDI+的2D渲染质量,指定是否将平滑(抗锯齿)应用于直线和曲线以及填充区域的边缘。这一点,需要明确。
3、画虚线
private void btnDrawDashLine_Click(object sender, EventArgs e)
{
var pointA = GetRandomPoint();
var pointB = GetRandomPoint();
var style = (DashStyle)(random.Next(, )); using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 2f))
{
g.Clear(SystemColors.AppWorkspace);
g.SmoothingMode = SmoothingMode.HighQuality;
pen.DashStyle = style;
g.DrawLine(pen, pointA, pointB);
} ShowInformation($"画虚线,{pointA}->{pointB},样式:{style.ToString()}。");
}
画虚线 —— btnDrawDashLine_Click
画虚线的关键点在于制定笔的样式:pen.DashStyle = style;同样,为了多展示集中样式,style取了枚举的随机值;
//
// Summary:
// Specifies the style of dashed lines drawn with a System.Drawing.Pen object.
public enum DashStyle
{
//
// Summary:
// Specifies a solid line.
Solid = ,
//
// Summary:
// Specifies a line consisting of dashes.
Dash = ,
//
// Summary:
// Specifies a line consisting of dots.
Dot = ,
//
// Summary:
// Specifies a line consisting of a repeating pattern of dash-dot.
DashDot = ,
//
// Summary:
// Specifies a line consisting of a repeating pattern of dash-dot-dot.
DashDotDot = ,
//
// Summary:
// Specifies a user-defined custom dash style.
Custom =
}
System.Drawing.Drawing2D.DashStyle
没有取0和5,Solid = 0为实线,Custom = 5为自定义样式,我们在第一境里先不介绍这类自定义的用法,容易玩儿不嗨……
4、画线冒
这是一个很朴实的需求,比如我想画一个连接线,一头带箭头,或者两头都带箭头,又或者一头是圆点另一头是箭头等。经常被问到,其实在GDI+中,非常容易实现,甚至还可以指定虚线的线冒,可爱:)
private void btnDrawLineCap_Click(object sender, EventArgs e)
{
var pointA = GetRandomPoint();
var pointB = GetRandomPoint();
var style = (DashStyle)(random.Next(, ));
var lineCaps = new List<int> { , , , , , , , , , };
var dashCaps = new List<int> { , , };
var startCap = (LineCap)lineCaps[random.Next(, )];
var endCap = (LineCap)lineCaps[random.Next(, )];
var dashCap = (DashCap)dashCaps[random.Next(, )]; using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 4f))
{
g.Clear(SystemColors.AppWorkspace);
g.SmoothingMode = SmoothingMode.HighQuality;
pen.DashStyle = style;
pen.SetLineCap(startCap, endCap, dashCap);
g.DrawLine(pen, pointA, pointB);
} ShowInformation($"画线冒,{pointA}->{pointB},起点线冒:{startCap.ToString()},终点线冒:{endCap.ToString()},虚线冒:{dashCap.ToString()},线条样式:{style.ToString()}。");
}
画线冒 —— btnDrawLineCap_Click
关键点在于pen.SetLineCap(startCap, endCap, dashCap);同样,startCap, endCap分别取了System.Drawing.Drawing2D.LineCap的随机值;dashCap取了System.Drawing.Drawing2D.DashCap的随机值;
//
// Summary:
// Specifies the available cap styles with which a System.Drawing.Pen object can
// end a line.
public enum LineCap
{
//
// Summary:
// Specifies a flat line cap.
Flat = ,
//
// Summary:
// Specifies a square line cap.
Square = ,
//
// Summary:
// Specifies a round line cap.
Round = ,
//
// Summary:
// Specifies a triangular line cap.
Triangle = ,
//
// Summary:
// Specifies no anchor.
NoAnchor = ,
//
// Summary:
// Specifies a square anchor line cap.
SquareAnchor = ,
//
// Summary:
// Specifies a round anchor cap.
RoundAnchor = ,
//
// Summary:
// Specifies a diamond anchor cap.
DiamondAnchor = ,
//
// Summary:
// Specifies an arrow-shaped anchor cap.
ArrowAnchor = ,
//
// Summary:
// Specifies a mask used to check whether a line cap is an anchor cap.
AnchorMask = ,
//
// Summary:
// Specifies a custom line cap.
Custom =
}
System.Drawing.Drawing2D.LineCap
//
// Summary:
// Specifies the type of graphic shape to use on both ends of each dash in a dashed
// line.
public enum DashCap
{
//
// Summary:
// Specifies a square cap that squares off both ends of each dash.
Flat = ,
//
// Summary:
// Specifies a circular cap that rounds off both ends of each dash.
Round = ,
//
// Summary:
// Specifies a triangular cap that points both ends of each dash.
Triangle =
}
System.Drawing.Drawing2D.DashCap
同样,我们也可以通过分别设置pen的StartCap、EndCap、DashCap属性来达到相同目的;
好了,到这里呢,关于线的基本画法就已经全部介绍完了,感觉有点EZ? BORED?那么我们就来利用现有的知识,耍个花活?
5、点点连线
这里比简单的画线,稍微复杂一点点,需要两个事件配合:
private void btnDrawPointToPointLine_Click(object sender, EventArgs e)
{
startDrawPointToPointLine = true;
lastMouseDownLocation = Point.Empty; using (var g = CreateGraphics())
{
g.Clear(SystemColors.AppWorkspace);
} ShowInformation($"点点连线,等待起点(鼠标单击画布内任意位置)。");
}
点点连线 —— btnDrawPointToPointLine_Click
private void FormDrawLines_MouseDown(object sender, MouseEventArgs e)
{
if (startDrawPointToPointLine)
{
if (Point.Empty.Equals(lastMouseDownLocation))
{
lastMouseDownLocation = e.Location;
ShowInformation($"点点连线,起点:{lastMouseDownLocation},等待终点(鼠标单击画布内任意位置)。");
}
else
{
using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 2f))
{
g.Clear(SystemColors.AppWorkspace);
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawLine(pen, lastMouseDownLocation, e.Location);
} ShowInformation($"点点连线,{lastMouseDownLocation}->{e.Location}。"); startDrawPointToPointLine = false;
lastMouseDownLocation = Point.Empty;
}
}
}
点点连线 —— FormDrawLines_MouseDown
原理很简单,当我们点击“点点连线”按钮的时候,激活标记位startDrawPointToPointLine、归位lastMouseDownLocation,并提示需要鼠标操作,选择一个起始点;
当我们在画布区域内单击一个下,就触发了FormDrawLines_MouseDown事件, 它会判断,当startDrawPointToPointLine处于激活状态并且lastMouseDownLocation处于原位时,它就把鼠标的当前位置赋值给lastMouseDownLocation,作为线段的起始点位置,并提示需要鼠标操作,选择一个终点;
当我们再次在画布区域内单击一个下,就又触发了FormDrawLines_MouseDown事件, 它会判断,当startDrawPointToPointLine处于激活状态并且lastMouseDownLocation不处于原位时,它就把鼠标的当前位置作为线段的终点位置,并画出线段;然后就是恢复startDrawPointToPointLine为未激活状态,并归位 lastMouseDownLocation;
恐怕要非常适应这种多事件配合的方式了,因为鼠标跟随也是多事件配合一起玩儿的:P
6、鼠标跟随
在点点连线的基础上,我们把标记位换成了startDrawFollowMouseLine;同时,增加了FormDrawLines_MouseMove事件;
private void FormDrawLines_MouseMove(object sender, MouseEventArgs e)
{
if (startDrawFollowMouseLine && !Point.Empty.Equals(lastMouseDownLocation))
{
using (var g = CreateGraphics())
using (var pen = new Pen(penColor, 2f))
{
g.Clear(SystemColors.AppWorkspace);
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawLine(pen, lastMouseDownLocation, e.Location);
} ShowInformation($"鼠标跟随,{lastMouseDownLocation}->{e.Location}。");
}
}
鼠标跟随 —— FormDrawLines_MouseMove
原理也不难,就是在选了起点以后,鼠标的移动事件会把鼠标的当前位置作为终点,重绘线段,以达到跟随的效果;由于截图也看不出动态效果,就不上图了,有兴趣的童鞋可以Run代码看看效果:)
Okay,关于GDI+画线的部分,我们就到此告一段落了。
篇外话
这里涉及了坐标系,美术老师说:
横坐标,坐标原点左为负,坐标原点右为正,从左到右越来越大;
纵坐标,坐标原点下为负,坐标原点上为正,从下到上越来越大;
但是在GDI+的世界坐标系里,纵坐标的描述正好相反;并且坐标原点初始时在画布的左上角,而不是画布的中央; 用心体会一下:)
喜欢本系列丛书的朋友,可以点击链接加入QQ交流群(994761602)【C# 破境之道】
方便各位在有疑问的时候可以及时给我个反馈。同时,也算是给各位志同道合的朋友提供一个交流的平台。
需要源码的童鞋,也可以在群文件中获取最新源代码。
《C# GDI+ 破境之道》:第一境 GDI+基础 —— 第一节:画直线的更多相关文章
- 《C# GDI+ 破境之道》:第一境 GDI+基础 —— 第二节:画矩形
有了上一节画线的基础,画矩形的各种边线就特别好理解了,所以,本节在矩形边线上,就不做过多的讲解了,关注一下画“随机矩形”的具体实现就好.与画线相比较,画矩形稍微复杂的一点就是在于它多了很多填充的样式. ...
- 《C# GDI+ 破境之道》:第一境 GDI+基础 —— 第三节:画圆形
有了上一节画矩形的基础,画圆形就不要太轻松+EZ:)所以,本节在画边线及填充上,就不做过多的讲解了,关注一下画“随机椭圆”.“正圆”.“路径填充”的具体实现就好.与画矩形相比较,画椭圆与之完全一致,没 ...
- 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造
第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造 MVC默认模板的视觉设计从MVC1到MVC3都没有改变,比较陈旧了:在MVC4中做了升级,好看些,在不同的分辨率下,也能工作得 ...
- 《C# 爬虫 破境之道》:第二境 爬虫应用 — 第一节:HTTP协议数据采集
首先欢迎您来到本书的第二境,本境,我们将全力打造一个实际生产环境可用的爬虫应用了.虽然只是刚开始,虽然路漫漫其修远,不过还是有点小鸡冻:P 本境打算针对几大派生类做进一步深耕,包括与应用的结合.对比它 ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第六节:第一境尾声
在第一境中,我们主要了解了爬虫的一些基本原理,说原理也行,说基础知识也罢,结果就是已经知道一个小爬虫是如何诞生的了~那么现在,请默默回想一下,在第一境中,您都掌握了哪些内容?哪些还比较模糊?如果还有什 ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第五节:数据流处理的那些事儿
为什么说到数据流了呢,因为上一节中介绍了一下异步发送请求.同样,在数据流的处理上,C#也为我们提供几个有用的异步处理方法.而且,爬虫这生物,处理数据流是基础本能,比较重要.本着这个原则,就聊一聊吧. ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第二节:WebRequest
本节主要来介绍一下,在C#中制造爬虫,最为常见.常用.实用的基础类 ------ WebRequest.WebResponse. 先来看一个示例 [1.2.1]: using System; usin ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第一节:整体思路
在构建本章节内容的时候,笔者也在想一个问题,究竟什么样的采集器框架,才能算得上是一个“全能”的呢?就我自己以往项目经历而言,可以归纳以下几个大的分类: 根据通讯协议:HTTP的.HTTPS的.TCP的 ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第四节:同步与异步请求方式
前两节,我们对WebRequest和WebResponse这两个类做了介绍,但两者还相对独立.本节,我们来说说如何将两者结合起来,方式有哪些,有什么不同. 1.4.1 说结合,无非就是我们如何发送一个 ...
随机推荐
- 在A卡下的 Matlab 运行C/C++混编的GPU程序
首先将你的.MEX文件和matlab脚本放在一个文件夹下开始运行 如果出错查看是那个.MEX文件出错 用depends这个软件查看他的依赖dll文件下载对应文件 放到当前文件夹下,运行成功.
- 低秩稀疏矩阵恢复|ADM(IALM)算法
一曲新词酒一杯,去年天气旧亭台.夕阳西下几时回? 无可奈何花落去,似曾相识燕归来.小园香径独徘徊. ---<浣溪沙·一曲新词酒一杯>--晏殊 更多精彩内容请关注微信公众号 "优化 ...
- Linux查看端口监听占用
# 查看所有 netstat -ntlp # 过滤PORT8080 netstat -ntlp | grep 8080 -t # 仅显示tcp相关选项 -u # 仅显示udp相关选项 -n # 拒绝显 ...
- LCA - 倍增法去求第几个节点
You are given a tree (an undirected acyclic connected graph) with N nodes, and edges numbered 1, 2, ...
- 匈牙利算法(Kuhn-Munkres)算法
这个算法有点难度,一般比较标准的描述网页上也有相关的描述,我在这里就简单的用十分通俗的语言给大家入个门 主要可以结合https://blog.csdn.net/zsfcg/article/detail ...
- (分块)Holes CodeForces - 13E
题意 n(n≤105)个洞排成一条直线,第ii个洞有力量值ai,当一个球掉进洞ii时就会被立刻弹到i+ai,直到超出n.进行m(m≤105)次操作: ·修改第i个洞的力量值ai. ·在洞xx上放一个球 ...
- 「4.0」一个人开发一个App,小程序从0到1,布局
一提到布局这个词,我总是索索发抖,不是因为天冷,而是因为布局的目标实在太宏大.古代想雄霸天下的王,就喜欢布局这个,布局那个,结果硬生生把自己的国家给布局没了.至于是哪个君王,我倒可以非常认真,非常坦诚 ...
- 5分钟搭建网站实时分析:Grafana+日志服务实战
原文地址:https://yq.aliyun.com/articles/227006 阿里云日志服务是针对日志类数据一站式服务,用户只需要将精力集中在分析上,过程中数据采集.对接各种存储计算.数据索引 ...
- 定义可选URL片段 定义自定义片段变量 精通ASP-NET-MVC-5-弗瑞曼
- UVA 最大面积最小三角形剖分
点击打开题目 题目大意: 以顺时针或逆时针给出一个简单多边形的n个点的坐标,用n-2条互不相交的,且与边不相交的对角线,分成n-2个三角形,要求其中最大三角形的面积最小 开始还汪星人咬乌龟,无从下口, ...