C#windows窗体应用程序窗口绘制简单理解
本文简单以Form_paint事件绘制统计直方图为例作为例子讲解。
其他控件的(例如panel等容器)的paint事件类似,不做详述。
首先理解窗体的布局
窗体的坐标系
窗体的坐标是类似X,Y坐标轴的,但是Y轴是倒着的,像这样:
窗体的绘制或者说更新非常频繁
鼠标对于控件聚焦啊,各种文本框的change啊,所以需要设置特定的触发条件控制窗口的重绘。- 窗体对于数据的更新和接受
这里我简单用了一个文本框传入一组数据用于绘制直方图的参考 - 窗体的布局
- 控件及其name属性
label控件:
name label1,用于实例数据展示,这里提示用逗号“,”分隔数据,因为我里面用的替换公式用匹配的逗号。
button控件:
属性name buttonReSet,属性 text 刷新数据,用于设置窗体重新绘制的触发条件。
属性name buttonClear,属性text 重置,用于清除当前窗体绘制的数据。
textbox控件:
属性name textBoxData,属性text 如图一串数据,目的是赋初值(这也是很多异常“未将对象引用到对象实例”的常见情况,很多参数是有了,但是方法并有得到数据,只是一个空值null会引起异常报错)。
- 事件
在这个例子中我使用的是form_paint事件。该事件位于事件栏外观一项。按分类顺序最下面,这里就不插图了。
最后实现效果图,没有时间去优化美工,见谅:
在显示第二份数据时请先点击重置,以清除绘制记录!!!
- 接下来就是代码了
- 程序代码如下,附带少量注释
//这里是窗体的paint事件代码,开启菜单时便会绘制的部分
private void FormBrush_Paint(object sender, PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Pen LinePen = new Pen(Color.Black, 3);
//LinePen.DashStyle=System.Drawing.Drawing2D.DashStyle.Solid;
LinePen.EndCap =System.Drawing.Drawing2D.LineCap.ArrowAnchor;//定义线尾的样式为箭头
int ZeroY = 450;//定义原点坐标Y值
int ZeroX = 30;//定义原点坐标X值
Point Line_start = new Point(ZeroX, ZeroY);
Point Liney_end = new Point(ZeroX, 50);
Point Linex_end = new Point(660, ZeroY);
g.DrawLine(LinePen, Line_start, Liney_end);//y轴
g.DrawLine(LinePen, Line_start, Linex_end);//x轴
//画Y轴精度
Pen percisionPen = new Pen(Color.Black, 2);
for (int i = 0; i < 10; i++)
{
Line_start = new Point(30, ZeroY );
Linex_end = new Point(50, ZeroY );
g.DrawLine(percisionPen, Line_start, Linex_end);//y轴粗精度
ZeroY -= 40;
}
ZeroY = 450;//定义原点坐标Y值
ZeroX = 30;//定义原点坐标X值
for (int i = 0; i < 20; i++)
{
Line_start = new Point(30, ZeroY);
Linex_end = new Point(40, ZeroY);
g.DrawLine(percisionPen, Line_start, Linex_end);//y轴细精度
ZeroY -= 20;
}
//这里有个可以改进的地方,因为我用的是两个循环画的大精度和小精度,所以在这个比例大小情况下每隔一个精度就会有一个大精度会覆盖小精度。可以定义一个变量,用-1的n次方来决定精度的长短。
//绘制Y轴标度
string drawStr;
Font drawFont = new Font("粗体", 12);
SolidBrush drawBrush = new SolidBrush(Color.Black);
float x = 5, y = 440;//因为绘制文本与绘制线条图案不一样,所以使用了浮点型变量。
int drawint = 0;
for (int i=0;i<20;i++)
{
drawStr = Convert.ToString(drawint);
PointF drawPoint = new PointF(x, y);
g.DrawString(drawStr, drawFont, drawBrush, drawPoint);
drawint += 5;//这里设置标度递增值为5
y -= 20;
}
}
//这里是控件控制绘制直方图的部分
private void buttonReSet_Click(object sender, EventArgs e)
{
Graphics g = this.CreateGraphics();
string[] str = Regex.Split(textBoxData.Text.TrimEnd("\r\n".ToCharArray()).Replace("\r\n", ","), ",");
//这里用split类识别textbox控件里面的逗号“,”并将里面的数字分隔放入数组
intDate = Array.ConvertAll<string, int>(str, s => int.Parse(s));
//这里用数组转换类型巨好用的公式将string类型转换为int类型,因为后面绘制直方图时Rectangle类传递的矩形长宽数据参数只能是整数类型。
SolidBrush RectangleBrush = new SolidBrush(Color.RoyalBlue);
int StartPointX = 60;//定义矩形起始X位置
int StartPointY = 450;
int RecWidth = 50;//定义矩形宽度
Pen RedPen = new Pen(Color.Red, 3);
RedPen.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDot;
//绘制矩形,注意矩形绘制和文本绘制等这种区域性图形的绘制,是以左上角的坐标为起点,然后根据长宽的值来绘制。
for (int i = 0; i < intDate.Length; i++)
{
Rectangle rect = new Rectangle(StartPointX, StartPointY - intDate[i] * 4, RecWidth, (intDate[i]) * 4);
//这里是我最想要吐槽的地方,因为前面绘制标度的时候每个刻度的值是按5递增,则实际单位比原窗口源单位值打了五倍,如果直接按照传递值绘制,则每个矩形图的高度都会多了整整4份,所以要减去4倍初始值。
Point dashPoint = new Point(30, StartPointY - intDate[i] * 4);
Point dashPointEnd = new Point(StartPointX + 50, StartPointY - intDate[i] * 4);
//fillRectangle绘制实心矩形
g.FillRectangle(RectangleBrush, rect);
g.DrawLine(RedPen, dashPoint, dashPointEnd);
StartPointX += 90;
}
}
最后唠叨一句
以上代码在我上传的文档有,《.NET高级程序编程》里面,还有部分另外四个关于多线程,文件读取与保存(抛弃c文件流,用控件OpenfalieDialog和SaveFileDialog实现),tcp/ip实现网络消息传递功能等的代码,欢迎下载交流,脸厚如墙欢迎大家批评指教。
本文章原创,代码原创,欢迎大家转载惠存!