您的位置:首页 > 编程语言 > C#

一次使用c#的canvas心得

2016-08-12 12:02 253 查看
申明:本人刚开始学习c# ,难免有些写的不好的地方,欢迎大家指出。

前几天,我忽然收到了一个新需求,要求做一个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);


总结,因为是菜鸟,所以一个简单的东西却花了不少的时间来研究,我的第一篇文章,很多地方也不知道怎么说,代码也很渣,希望大家多多提意见。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c# wpf