一次使用c#的canvas心得
2016-08-12 12:02
253 查看
申明:本人刚开始学习c# ,难免有些写的不好的地方,欢迎大家指出。
前几天,我忽然收到了一个新需求,要求做一个window的桌面应用程序,用于监控机器状态,说明文档不便公开,效果图如下:
首先,一看到这个的时候,心里想着,卧草,这么简单,分分钟给你弄出来啊(实际上并不是这么回事
)。
于是开始写代码。新建一个wpf项目。
然后在布局文件中加入一块画布:
因为只有一块画布,整个命名就随意了,暂时就叫做canvas吧
现在有了画布,就该画图了,这一个一个的小方块,画一个Rectangle就行了,至于方块上的编号,用了TextBlock,根据坐标点叠加在一起。
对,先画一个方块:
整个过程设计到一些精确计算,所以我的参数类型都是double的类型,因为是绝对定位容器,所以两个控件的Margin是必须的,我这里new 了一个Thickness, 参数分别距离边界左,上,右,下的距离,这里我只用到了left和top,意味着左上角的坐标点。
然后传入了控件的宽和高。
有点奇怪的是这里控件的背景色是用Fill来表示的(有知道命名原因的可以在留言中说明)。
再就是Name,这里的Name相当于是一个唯一ID,不允许重复,所以在动态生产控件的时候一定要注意。
RenderTransform就是控件的旋转角度以及旋转点坐标了。
好了,一个小方块已经做好了,更准确的说是一个小方块生成器。值得注意的是,生成了小方块,要添加到画布中去,也就是:
好的,接下来真的要注意了,我们生成了小方块,但是我们后面要怎么调用他呢(因为涉及到动态更改的情况,比如我们没有办法根据this.控件名.Text 来更改控件的Text值,因为找不到,编译不通过),后来,我用了一个Dictionary<Name,TextBlock>对象存储所有生成之后的方块对象,然后需要更改的时候就根据Name值取到相应的对象,这样就OK了,但是,虽然可以实现我想要的效果,我的心里还是有 一丢丢的不舒服(我也不知道为什么),总觉得这么写不是最好的方案,所以,我开始寻找更好的做法,对!RegisterName拯救了我!
这段代码会把控件注册到画布中去,这样的话就可以调用Canvas的FindName方法来调用控件了。
好的,非常好。工厂已经做好了,现在就差生产了:
我先整体拆分一下,整个图形分为三个环线,两边跟中间有所不同,我打算分两个生产车间(实际上开始我分的是三个,三个发现内外两个环线的代码差不多的时候,就把公共代码抽出来做成了一个方法)
首先,我们画中间的一个环,大致分为上下两条线,左右两个半圆,好的,开工:
我擦。。。我是在写说明文档么?大家都知道的事干嘛讲的那么详细?又不是一个特别复杂的事情?
唉,谁让我这么细心,这么帅气,这么温柔呢!咳咳,扯远了远了。
我们先从半圆入手,我观察到每个半圆都有18个小方块(我擦,为毛是18个,,,17个不行吗?),客户需求第一,唉,既然是18个,我打算以四分之一圆为单位画图,第9个圆和第10个圆都是翻转了90度,这样看起来比较对称。
首先我们我们画左上角的半圆,恩,坐标点怎么算?嗯嗯,纵坐标是?横坐标是?
咦?是不是还有先算半径啊!
于是2*pi*r=?咦?卧草,周长和半径都不知道?怎么算?
看来是不能怎么搞了,怎么办?
我发现9个方块都有各自的角度,有角度,有斜边,卧草,直角三角形。可以用三角函数啊
那么底边依次是 width * sin80 ... width * sin0。高依次是 width * sin10 ... width * sin90.(底边和和高度和不一样,也就是不能单纯了用半径来算,需要分开算)
那么,我们先算角度:
这里的angleTrim是为了我们可以在调用方法里面可以控制方块旋转的角度,比较灵活。
现在有了角度:
这里的yNumber指的是弧形的方块总数,在这里是18,作为参数传进来
在C#中 Math.Sin方法算的是弧度,我们要手动的把角度转换为弧度:
现在rx,ry都有了,我们也可以开始计算坐标了
有了上一步的基础,我们几乎很容易就可以算出各个点的坐标。因为是从上往下画,所以第一个点的横坐标应该是起始宽度+x轴方向半径+方块高度-width * sin80,纵坐标是起始高度 + width * sin10了,之后以此类推。因为是逆时针旋转,旋转角度要加“-”,之后呢以此类推
其他组成部分原理基本相同,就不废话了,一个环画出来了,另外两个也就容易了。
顺便把更新编号的简单代码也贴出来
好,这样基本就已经完成了所有的构图。
然后我在构造函数中调用
接下来,我们想更新这个图,达到动态更改的效果,我又写了一个update方法:
注: 因为我是通过socket传输的协议来解析数据改动画面的,所以设计到另一个基本问题。就是c#的UI是由创建他的线程来更改的。其他线程想要更改,就要先invoke一下,总体代码如下:
定义一个委托
调用方法之前初始化
总结,因为是菜鸟,所以一个简单的东西却花了不少的时间来研究,我的第一篇文章,很多地方也不知道怎么说,代码也很渣,希望大家多多提意见。
前几天,我忽然收到了一个新需求,要求做一个window的桌面应用程序,用于监控机器状态,说明文档不便公开,效果图如下:
首先,一看到这个的时候,心里想着,卧草,这么简单,分分钟给你弄出来啊(实际上并不是这么回事
)。
于是开始写代码。新建一个wpf项目。
然后在布局文件中加入一块画布:
<GroupBox Header="小车状态"> <DockPanel Margin="2.5" Background="White"> <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible"> <Canvas ClipToBounds="True" Name="canvas"> </Canvas> </ScrollViewer> </DockPanel> </GroupBox>
因为只有一块画布,整个命名就随意了,暂时就叫做canvas吧
现在有了画布,就该画图了,这一个一个的小方块,画一个Rectangle就行了,至于方块上的编号,用了TextBlock,根据坐标点叠加在一起。
对,先画一个方块:
private void DoDrewTetragonum(Double left, Double top, Double right, Double buttom, Double width, Double height, Double angle, Double centerX, Double centerY ,string text, Brush brush) { Rectangle rec = new Rectangle() { Margin = new Thickness(left, top, right, buttom), Width = width, Height = height, Fill = brush, Name = "tetragonum" + text, Stroke = brush, StrokeThickness = 1, RenderTransform = new RotateTransform(angle, centerX, centerY) }; TextBlock tb = new TextBlock(); tb.Margin = new Thickness(left, top, right, buttom); tb.Width = width; tb.Height = height; tb.Text = text; tb.Name = "number"+ text; tb.VerticalAlignment= VerticalAlignment.Center; tb.Padding = new Thickness(2,5,2,2); tb.RenderTransform = new RotateTransform(angle, centerX, centerY); canvas.Children.Add(rec); canvas.RegisterName(rec.Name, rec); canvas.Children.Add(tb); canvas.RegisterName(tb.Name,tb); }
整个过程设计到一些精确计算,所以我的参数类型都是double的类型,因为是绝对定位容器,所以两个控件的Margin是必须的,我这里new 了一个Thickness, 参数分别距离边界左,上,右,下的距离,这里我只用到了left和top,意味着左上角的坐标点。
Margin = new Thickness(left, top, right, buttom)
然后传入了控件的宽和高。
tb.Width = width; tb.Height = height;
有点奇怪的是这里控件的背景色是用Fill来表示的(有知道命名原因的可以在留言中说明)。
Fill = brush
再就是Name,这里的Name相当于是一个唯一ID,不允许重复,所以在动态生产控件的时候一定要注意。
Name = "tetragonum" + text
RenderTransform就是控件的旋转角度以及旋转点坐标了。
RenderTransform = new RotateTransform(angle, centerX, centerY)
好了,一个小方块已经做好了,更准确的说是一个小方块生成器。值得注意的是,生成了小方块,要添加到画布中去,也就是:
canvas.Children.Add(rec);
好的,接下来真的要注意了,我们生成了小方块,但是我们后面要怎么调用他呢(因为涉及到动态更改的情况,比如我们没有办法根据this.控件名.Text 来更改控件的Text值,因为找不到,编译不通过),后来,我用了一个Dictionary<Name,TextBlock>对象存储所有生成之后的方块对象,然后需要更改的时候就根据Name值取到相应的对象,这样就OK了,但是,虽然可以实现我想要的效果,我的心里还是有 一丢丢的不舒服(我也不知道为什么),总觉得这么写不是最好的方案,所以,我开始寻找更好的做法,对!RegisterName拯救了我!
canvas.RegisterName(rec.Name, rec);
这段代码会把控件注册到画布中去,这样的话就可以调用Canvas的FindName方法来调用控件了。
好的,非常好。工厂已经做好了,现在就差生产了:
我先整体拆分一下,整个图形分为三个环线,两边跟中间有所不同,我打算分两个生产车间(实际上开始我分的是三个,三个发现内外两个环线的代码差不多的时候,就把公共代码抽出来做成了一个方法)
首先,我们画中间的一个环,大致分为上下两条线,左右两个半圆,好的,开工:
private int DrewCar(Double startPointX, Double startPointY, Double width, Double height, int carCount, int distance, int xNumber, int yNumber, Double angleTrim, String key, Brush brush, int number = 1, Double space = 3) { Double sinX = 0; Double sinY = 0; Double rx = 0; Double ry = 0; Double angle = 0; angle = 180 / yNumber - angleTrim; for (int i = 0; i < yNumber/2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); } rx = sinX * width; ry = sinY * width; number++; /* * 左上角 **/ sinX = 0; sinY = 0; for (int i = 0; i < yNumber/2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); DoDrewTetragonum(startPointX + rx + height - width * sinX, startPointY + width * sinY, 0, 0, width, height, -angle * (i + 1), 0, height, key + number, brush); number = this.updateNumber(number, carCount); } /* * 左下角弧 **/ sinX = 0; sinY = 0; int newNumber = number + yNumber / 2 - 1; for (int i = 0; i < yNumber / 2; i++) { sinX += Math.Sin(Math.PI * (90-(i+1)* angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); DoDrewTetragonum(startPointX+rx + height - width*sinX, startPointY+height+ry*2+space+ distance - width*sinY, 0, 0, width, height, angle * (i+1), 0, 0, key+(newNumber - i), brush); number = this.updateNumber(number, carCount); } /* * 下边 **/ for (int i = 0; i < xNumber; i++) { DoDrewTetragonum(startPointX + rx + height + (width + space) * i, startPointY + height + ry * 2 + space+ distance, 0, 0, width, height, 0, 0, 0, key+number, brush); number = this.updateNumber(number, carCount); } /* * 右下角弧 **/ sinX = 0; sinY = 0; for (int i = 0; i < yNumber / 2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); DoDrewTetragonum(startPointX + rx + height + (width + space) * (xNumber -1) + width * sinX, startPointY + height + ry * 2 + space+ distance - width * sinY, 0, 0, width, height, -angle * (i + 1), width, 0, key+number, brush); number = this.updateNumber(number, carCount); } /* * 右上角弧 **/ sinX = 0; sinY = 0; newNumber = number + yNumber / 2 - 1; for (int i = 0; i < yNumber / 2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); DoDrewTetragonum(startPointX + rx + height + (width + space) * (xNumber -1) + width * sinX, startPointY + width * sinY, 0, 0, width, height, angle * (i + 1), width, height, key+(newNumber - i), brush); number = this.updateNumber(number, carCount); } /* * 上边 **/ for (int i = 0; i < xNumber; i++) { DoDrewTetragonum(startPointX + rx + height + (width + space) * (xNumber - 1) - (width + space) * i, startPointY, 0, 0, width, height, 0, 0, 0, key+number, brush); number = this.updateNumber(number, carCount); } return number; }
我擦。。。我是在写说明文档么?大家都知道的事干嘛讲的那么详细?又不是一个特别复杂的事情?
唉,谁让我这么细心,这么帅气,这么温柔呢!咳咳,扯远了远了。
我们先从半圆入手,我观察到每个半圆都有18个小方块(我擦,为毛是18个,,,17个不行吗?),客户需求第一,唉,既然是18个,我打算以四分之一圆为单位画图,第9个圆和第10个圆都是翻转了90度,这样看起来比较对称。
首先我们我们画左上角的半圆,恩,坐标点怎么算?嗯嗯,纵坐标是?横坐标是?
咦?是不是还有先算半径啊!
于是2*pi*r=?咦?卧草,周长和半径都不知道?怎么算?
看来是不能怎么搞了,怎么办?
我发现9个方块都有各自的角度,有角度,有斜边,卧草,直角三角形。可以用三角函数啊
那么底边依次是 width * sin80 ... width * sin0。高依次是 width * sin10 ... width * sin90.(底边和和高度和不一样,也就是不能单纯了用半径来算,需要分开算)
那么,我们先算角度:
angle = 180 / yNumber - angleTrim;
这里的angleTrim是为了我们可以在调用方法里面可以控制方块旋转的角度,比较灵活。
现在有了角度:
for (int i = 0; i < yNumber/2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); } rx = sinX * width; ry = sinY * width;
这里的yNumber指的是弧形的方块总数,在这里是18,作为参数传进来
在C#中 Math.Sin方法算的是弧度,我们要手动的把角度转换为弧度:
Math.PI * (角度) / 180.0
现在rx,ry都有了,我们也可以开始计算坐标了
/* * 左上角 **/ sinX = 0; sinY = 0; for (int i = 0; i < yNumber/2; i++) { sinX += Math.Sin(Math.PI * (90 - (i + 1) * angle) / 180.0); sinY += Math.Sin(Math.PI * ((i + 1) * angle) / 180.0); DoDrewTetragonum(startPointX + rx + height - width * sinX, startPointY + width * sinY, 0, 0, width, height, -angle * (i + 1), 0, height, key + number, brush); number = this.updateNumber(number, carCount); }
有了上一步的基础,我们几乎很容易就可以算出各个点的坐标。因为是从上往下画,所以第一个点的横坐标应该是起始宽度+x轴方向半径+方块高度-width * sin80,纵坐标是起始高度 + width * sin10了,之后以此类推。因为是逆时针旋转,旋转角度要加“-”,之后呢以此类推
其他组成部分原理基本相同,就不废话了,一个环画出来了,另外两个也就容易了。
顺便把更新编号的简单代码也贴出来
private int updateNumber(int number, int Max) { if (number < Max) { number++; return number; } else { return 1; } }
好,这样基本就已经完成了所有的构图。
然后我在构造函数中调用
DrewCar(70,130, 30, 30, 106,0,35,18,0,"",Brushes.Gray, 1,3);
接下来,我们想更新这个图,达到动态更改的效果,我又写了一个update方法:
注: 因为我是通过socket传输的协议来解析数据改动画面的,所以设计到另一个基本问题。就是c#的UI是由创建他的线程来更改的。其他线程想要更改,就要先invoke一下,总体代码如下:
private void UpdateCanvasContent(Canvas can, int currentPosition) { if (!this.CheckAccess()) { this.Dispatcher.Invoke(updateCanvas, can, currentPosition); } else { for (int i = 0; i < 106; i++) { TextBlock tb = can.FindName("number" + (i + 1)) as TextBlock; if (currentPosition > 106) { currentPosition = 1; } Rectangle rect = can.FindName("tetragonum" + (i + 1)) as Rectangle; tb.Text = (currentPosition++) + ""; } } }
定义一个委托
private delegate void UpdateCanvas(Canvas can, int currentPosition); private UpdateCanvas updateCanvas = null;
调用方法之前初始化
updateCanvas = new UpdateCanvas(UpdateCanvasContent);
总结,因为是菜鸟,所以一个简单的东西却花了不少的时间来研究,我的第一篇文章,很多地方也不知道怎么说,代码也很渣,希望大家多多提意见。
相关文章推荐
- 使用飞信VMDotNet使C#程序脱离.NET FRAMEWORK也能运行的一些心得
- C#中委托如何使用?一点学习心得
- c#下FormatMessage使用心得
- C# PictureBox 使用心得
- C# 中使用OPenCV(Emgu)心得
- C# Builder 使用心得
- 两点C#的propertyGrid的使用心得
- 在C#中使用证卡打印机的心得
- 每隔一段时间自动执行一次某个方法(使用线程)[C#]
- 每隔一段时间自动执行一次某个方法(使用线程)[C#]
- 使用飞信VMDotNet使C#程序脱离.NET FRAMEWORK也能运行的一些心得 ------(我复制别个的)
- 转载:使用飞信VMDotNet使C#程序脱离.NET FRAMEWORK也能运行的一些心得
- C#使用Webbrowser的一点心得体会
- C# 中使用OPenCV(Emgu)心得
- C#使用Webbrowser的一点心得体会
- 【心得】在C#中使用静态变量const和动态变量readonly的区别
- Excel对象模型的一些使用心得(C#)
- c# 文本框使用心得(TextBox)
- C# 中使用OPenCV(Emgu)心得
- 关于C#多线程、网络编程与计时器Timer的一点使用心得