65.9K
CodeProject 正在变化。 阅读更多。
Home

交互式添加多个形状,使用链表 GDI+ SVG

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2016年3月3日

CPOL

4分钟阅读

viewsIcon

16422

downloadIcon

1259

教程 2 (如何使用链表交互式添加多个 GDI 形状)

这是本教程系列的第二篇,旨在创建一个功能齐全的图形程序,该程序可以导出其艺术作品为svg、png格式。我们还可以将其实现为电路程序,并以verilog格式输出其工作成果,我建议您阅读我的第一篇教程 https://codeproject.org.cn/script/Articles/MemberArticles.aspx?amid=10828711

您将能够构建什么

引言

今天,我们将讨论如何让用户向程序中添加不同的形状。我们将使用C#中的链表来实现这一点,这比普通列表更实用……仅仅因为它不需要限制用户可添加形状的数量。

总体计划

  1. 添加不同的形状(就像我们在教程1中所做的那样)
  2. 链表快速概述
  3. 一种非常简单的方法,让用户添加多个对象

首先:添加不同的形状

就像在tut1中一样,我们将创建一个新的形状……我们正在使用一个名为SVG渲染库的库。

只需在“管理NuGet程序包”中键入svg,它就会显示为第一个结果

  1. 首先,打开 http://editor.method.ac/ 或您喜欢的任何图形程序
  2. 从形状库中……绘制任何形状(我画了一颗心!!)
  3. 然后保存您的艺术作品(在此网站上,文件>>保存图像将导出为svg格式)

  4. 在记事本中打开您下载的svg文件。
  5. 注意path标签,它将包含一个d属性……复制里面的内容。

  6. 创建一个继承自基类形状的类。
  7. 添加draw_svg 虚函数,如教程1中所述。
  8. 将从记事本中复制的d属性放入一个string变量中

最终的类应该是这样的

class heart : shapes
    {
        private string sData = "m114.071045,61.254959c40.765015,-116.94968 200.484436,
                    0 0,150.36232c-200.484055,-150.36232 -40.763611,-267.312 0,-150.36232z";

        private Region region;
        public override Svg.SvgPath draw_svg()
        {
            Svg.SvgPath pa = new Svg.SvgPath();
            Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList = new Svg.Pathing.SvgPathSegmentList();
            var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
            pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);

            Svg.ISvgRenderer render = null;

            region = new Region(pa.Path(render));
            return pa;
        }
    }

第二部分:链表快速概述

链表就像一串连接在一起的对象。链中的每个对象(称为节点)都指向其他对象……通过这种方式,可以向列表中添加多个对象,而无需从一开始就定义列表的大小……这将让您的用户绘制大量对象。

定义列表

List<int> list = new List<int>();

or for an object of a class
List<class> list = new List<class>();

在末尾添加新项

list.Add(class_object);

在指定索引处插入

list.Insert(index, class_object);

删除

list.RemoveAt(index);

选择特定索引处的特定对象

class_name a= list.ElementAt<class_name>(index);

第三部分:处理我们的项目

首先,我们正在处理form.cs文件

  1. 让我们定义一个名为shape_list的私有数据成员
    List<shapes> shape_list = new List<shapes>();

    现在,为了使我们的程序具有交互性,我们需要捕获按键,例如“s”代表星星(star)和“h”代表心(heart)。这将修改一个动作字符串,我们需要它来识别要绘制的对象。

    然后,我们将需要捕获鼠标单击到所需位置以绘制所需的形状。

  2. 所以,让我们开始定义动作字符串作为一个私有数据成员
    string action = "";
  3. 定义捕获鼠标单击和键盘的方法

    从窗体的设计器>>>属性 >>>事件 >>> 在“操作”(action)下>>双击鼠标单击区域。

    这将在后端代码中创建private void Form1_MouseClick(object sender, MouseEventArgs e)

    从窗体的设计器>>>属性 >>> 事件 >>> 在“键”(key)下>>双击“键按下”(key down)。

    这将在后端代码中创建private void Form1_KeyDown(object sender, KeyEventArgs e)

    keydown方法中,输入:

    private void Form1_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyData == Keys.S)
                {
                    action = "star";
                }
                else if (e.KeyData == Keys.H)
                {
                    action = "heart";
                }
            }

    现在,在鼠标单击方法中,写入两个条件来处理两种操作

      private void Form1_MouseClick(object sender, MouseEventArgs e)
            {
                if (action.Equals("star"))
                {
                    shapes sh = new shapes(); //define base class
                    star st = new star(); //define derived class
                    sh = st; //put derived inside base 
    
                    sh.translateX = e.X;//capture location of mouse in x axis
                    sh.translateY = e.Y;//capture location of mouse in y axis
    				    //and put them into translateX and translateY variables 
                                        //in shape variable
    				    //in shapes base class
    
                    shape_list.Add(st);// add this shape to the shape_list
                    Invalidate();//call the on paint function
                }
    
    
                else if (action.Equals("heart"))
                {
                    shapes sh = new shapes();  //define base class
                    heart h = new heart();     //define derived class
                    sh = h;                    //put derived inside base 
    
                    sh.translateX = e.X;       //capture location of mouse in x axis
                    sh.translateY = e.Y;       //capture location of mouse in y axis
    				           //and put them into translateX and translateY variables 
                                               //in shape variable
        				           // in shapes base class
    
                    shape_list.Add(h);         // add this shape to the shape_list
                    Invalidate();              //call the on paint function
                }
            }
  4. 但是派生类尚未理解translateX和translateY……因为drawSvg取决于派生类,所以我们必须修改派生类中的draw_svg(不要忘记draw svg是一种方法,可以将svg文件中定义的路径数据用于GraphicsPath,然后绘制到您的Windows窗体中)。

    所以,我们将不得不修改我们旧的star 类,使其变为

     class star :shapes
        {
            private string sData = "m1.212486,47.649818l47.846649,0l14.78479,
                                   -45.453926l14.784615,45.453926l47.846909,0l-38.708832,
                                   28.091873l14.785934,45.454201l-38.708626,-28.09272l-38.708832,
                                   28.09272l14.785782,-45.454201l-38.708389,-28.091873l0,0z";
    	// i just modified the sData to make the star smaller and to be more precise
    
            private Region region;
            public override Svg.SvgPath draw_svg()
            {
                Svg.SvgPath pa = new Svg.SvgPath();
                Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList = 
                                               new Svg.Pathing.SvgPathSegmentList();
                var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
                pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);
    
    
                Svg.ISvgRenderer render = null;
    
    	    //---------modification
                GraphicsPath alu = new GraphicsPath();//define a Graphics path
                alu = pa.Path(render);// way to connect between graphics path and svg path 
    				  // as this code can only apply translate on graphics path
    				  // and we need to draw the defined svg
    				  // so we need to make a connection between 
                                      // GraphicsPath and svgPath
    
                Matrix m = new Matrix();//define matrix to apply translation
    
    	    //apply translation using the translateX and translateY defined in 
                //base class on the MATRIX
    	    //we still need to apply this translation on graphics path itself
               
                m.Translate(translateX, translateY, MatrixOrder.Append);
                alu.Transform(m);//apply translation on graphics path itself
    
                region = new Region(pa.Path(render));//svg library needs a Renderer to convert vectors
    	    //---------modification
    
                return pa;
            }
        }

    同样的操作也会在heart 派生类上完成

    class heart : shapes
        {
            private string sData = "m114.071045,61.254959c40.765015,-116.94968 200.484436,
                                    0 0,150.36232c-200.484055,-150.36232 -40.763611,
                                    -267.312 0,-150.36232z";
    
            private Region region;
            public override Svg.SvgPath draw_svg()
            {
                Svg.SvgPath pa = new Svg.SvgPath();
                Svg.Pathing.SvgPathSegmentList svgSvgPathSegmentList = 
                                                new Svg.Pathing.SvgPathSegmentList();
                var converter = TypeDescriptor.GetConverter(typeof(Svg.Pathing.SvgPathSegmentList));
                pa.PathData = (Svg.Pathing.SvgPathSegmentList)converter.ConvertFrom(sData);
    
                Svg.ISvgRenderer render = null;
    
                GraphicsPath alu = new GraphicsPath();
                alu = pa.Path(render);
                Matrix m = new Matrix();
                m.Translate(translateX, translateY, MatrixOrder.Append);
                alu.Transform(m);
                region = new Region(pa.Path(render));
    
                return pa;
            }
        }
  5. 现在,我们只需要更改form.cs中的onpaint函数

    只需进行一个简单的foreach循环(就像一个普通的for循环,但不需要索引或停止条件……它只是遍历列表中的项)。

    protected override void OnPaint(PaintEventArgs e)
            {
                Graphics g = e.Graphics;
    
                foreach (shapes sh in shape_list)//just loop on each item of the list 
                                                 //and name this item sh
                {
                    e.Graphics.DrawPath(new Pen(Brushes.Black, 2), sh.draw_svg().Path(render));
                    e.Graphics.FillPath(Brushes.Khaki, sh.draw_svg().Path(render));
                }
            }

现在,当您运行程序时,您将能够

  • 如果您想绘制星星,请按键盘上的s键绘制星星,然后单击鼠标以绘制星星的位置
  • 如果您想绘制心,请按键盘上的h键绘制心,然后单击鼠标以绘制心

对我来说,了解您对这些教程的看法非常重要……它们是否达到了您的预期??……您的反馈对我来说真的非常重要。

© . All rights reserved.