一次使用c#的canvas心得

2016-08-25 19:14:18来源:CSDN作者:u014613150人点击

申明:本人刚开始学习c# ,难免有些写的不好的地方,欢迎大家指出。

前几天,我忽然收到了一个新需求,要求做一个window的桌面应用程序,用于监控机器状态,说明文档不便公开,效果图如下:

QQ图片20160428100243.png

首先,一看到这个的时候,心里想着,卧草,这么简单,分分钟给你弄出来啊(实际上并不是这么回事)。

于是开始写代码。新建一个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);


总结,因为是菜鸟,所以一个简单的东西却花了不少的时间来研究,我的第一篇文章,很多地方也不知道怎么说,代码也很渣,希望大家多多提意见。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台