C#绘图程序结题报告

(一)   设计思路:

基于课上完成的绘图工具改进,实现几种图形,因在日常学习实验中涉及到UML图的设计与绘制,标识工具的使用频率较高,于是参照ProcessOn连线设置形式,在原有的基础上添加指示箭头图形与折线。

l  可绘制图形工具及其属性

1.       直线:粗细设置,箭头或普通样式

2.       指示箭头:大小设置

3.       折线:粗细设置,箭头或普通样式,线上设置锚点,可调节折角

4.       矩形:边框粗细,样式,描边或填充

5.       圆角矩形:边框粗细,样式,描边或填充,圆角半径

6.       椭圆类:边框粗细,样式,描边或填充

l  按下选择按钮后进入选择状态,产生选框与可拖拽锚点,中心点,旋转点,图形可进行移动,拉伸,旋转

l  可多选,全选,删除所选图形

l  边框与填充,二者颜色设置

l  清空画板,保存图像,打开图像,退出程序

 

使用C# 窗体应用+GDI C#语言面向对象思想

将各种形状(线段、矩形、椭圆等)的起点坐标、大小、颜色等属性信息封装在内部程序。用Graphics类在panel上绘制,把所绘制的图形起点坐标、大小等数据记录在功能类中,使用户在控件上进行单击时,根据单击的坐标判断在矩形上单击或是空白处,在其坐标范围中,表示矩形被选中;用户进行移动鼠标操作,可则根据移动的距离重新计算矩形的起点坐标,得到重新绘制的图形。


将主要功能写入核心类,包括鼠标移动按下事件中的判断其所在位置,获取图形变换前后的坐标点,判断选中图形是否处于拖动选择状态,获取图像变化事件,最终图像,在选中图形的不同位置发生游标变化事件等。

为了程序显示清晰与功能的完整,使用System.Windows.FormsCursor类对不同状态下的鼠标指针样式进行设置;界面设计上对ToolStripLabelToolStripCombox的是否显示,对选中不同图形进行条件语句判断来设置。

 

寻找图形绘制属性的关系,对各种类进行继承关系的建立,重写方法,如:圆角矩形类、椭圆图形类继承于矩形类;填充描边类继承于描边类;直线、折线类继承于描边类。设定默认与各种控件传递来的属性。

 

实现图形变换功能,定义坐标点的有序对,根据不同图形性质,需定义23个成员变量,根据不同图形数学公式得到全部点的坐标数据,进行绘制与后续操作。

 

在绘制图形时,各个图形类中包含对轮廓和选中时的选框部分的绘制,选框与可拖拽锚点其成员变量的数据在类间得到传递。

 

 

 

(二)   核心代码:

 https://gitee.com/clematisWSheena/drawing-program.git

Kernel.cs

包含了绘图所需功能

鼠标移动按下事件中的判断其所在位置,获取图形变换前后的坐标点,判断选中图形是否处于拖动选择状态,选择工具事件,获取图像变化事件,最终图像,在选中图形的不同位置发生游标变化事件等

创建涉及到绘画过程私有变量,在后边写入所需函数

 private System.ComponentModel.EventHandlerList _events;
        private Bitmap finalBitmap;//结果图像
        private Graphics graphics;//画板

        private ToolCursorType cursorType;//使用的工具游标

        private SelectToolsLocation selectToolInWhere;//位置
        private bool isInSelecting;//判断是否选中

        private bool mousePressed;//判断鼠标过程
        private bool isEverMove;//判断
        private List<DrawableBase> shapesList;//可选图形
        private DrawableBase shapeInCreating;//
        private ToolType currentTool;//当前选择的绘图工具
        private List<int> selectedShapesIndexList;//当前选择的图形
        private Point lastMovePoint;//终点
        private Point mouseDownLocation;//鼠标点

        private int pressedHotSpotIndex;//选框

        private PropertyCollector proCollector;//样式

选定图形后变更样式

private void applyNewProperty()
        {
            if (SelectedShapesCount < 1)
                return;

            // 当前仅支持一个选定的形状
            if (SelectedShapesCount == 1)
            {
                DrawableBase shape = shapesList[selectedShapesIndexList[0]];
                switch (shape.ShapeProperty.PropertyType)
                {
                    case ShapePropertyType.IndicatorArrowProperty:
                        shape.ShapeProperty = proCollector.GetIndicatorProperty();
                        break;

                    case ShapePropertyType.StrokedProperty:
                        shape.ShapeProperty = proCollector.GetStrokedProperty();
                        break;

                    case ShapePropertyType.FilledProperty:
                        shape.ShapeProperty = proCollector.GetFilledProperty();
                        break;

                    case ShapePropertyType.RoundedRectProperty:
                        shape.ShapeProperty = proCollector.GetRoundedRectProperty();
                        break;
                }
            }
        }

        private void CheckShapeProperty()
        {
            if (currentTool != ToolType.ShapeSelect || SelectedShapesCount != 1)
                return;

            bool diff = false;
            BaseProperty basepro = shapesList[selectedShapesIndexList[0]].ShapeProperty;

            if (basepro is IndicatorArrowProperty)
            {
                IndicatorArrowProperty ip = (IndicatorArrowProperty)basepro;
                if (ip.LineColor != proCollector.StrokeColor ||
                    ip.LineSize != proCollector.IndicatorLineSize)
                {
                    diff = true;
                    proCollector.StrokeColor = ip.LineColor;
                    proCollector.IndicatorLineSize = ip.LineSize;
                }
            }

            if (basepro is StrokedProperty)
            {
                StrokedProperty sp = (StrokedProperty)basepro;
                if (proCollector.PenWidth != sp.PenWidth ||
                    proCollector.StrokeColor != sp.StrokeColor ||
                    proCollector.LineDash != sp.LineDash ||
                    proCollector.StartLineCap != sp.StartLineCap ||
                    proCollector.EndLineCap != sp.EndLineCap ||
                    proCollector.PenAlign != sp.PenAlign ||
                    proCollector.HowLineJoin != sp.HowLineJoin)
                {
                    diff = true;
                    proCollector.PenWidth = sp.PenWidth;
                    proCollector.StrokeColor = sp.StrokeColor;
                    proCollector.LineDash = sp.LineDash;
                    proCollector.StartLineCap = sp.StartLineCap;
                    proCollector.EndLineCap = sp.EndLineCap;
                    proCollector.PenAlign = sp.PenAlign;
                    proCollector.HowLineJoin = sp.HowLineJoin;
                }
            }

            if (basepro is FilledProperty)
            {
                FilledProperty fp = (FilledProperty)basepro;
                if (proCollector.PaintType != fp.PaintType ||
                    proCollector.FillColor != fp.FillColor ||
                    proCollector.FillType != fp.FillType)
                {
                    diff = true;
                    proCollector.PaintType = fp.PaintType;
                    proCollector.FillColor = fp.FillColor;
                    proCollector.FillType = fp.FillType;
                }
            }

            if (basepro is RoundedRectProperty)
            {
                RoundedRectProperty rp = (RoundedRectProperty)basepro;
                if (proCollector.RadiusAll != rp.RadiusAll)
                {
                    diff = true;
                    proCollector.RadiusAll = rp.RadiusAll;
                }
            }

            if (diff)
            {
                OnPropertyCollectorChanged(EventArgs.Empty);
            }
        }

 


 

        // 图像改变事件
        private static object Event_FinalBitmapChanged = new object();
        public event EventHandler FinalBitmapChanged
        {
            add { _events.AddHandler(Event_FinalBitmapChanged, value); }
            remove { _events.RemoveHandler(Event_FinalBitmapChanged, value); }
        }
        protected virtual void OnFinalBitmapChanged(EventArgs e)
        {
            EventHandler handler = (EventHandler)_events[Event_FinalBitmapChanged];
            if (handler != null)
            {
                handler(this, e);
            }
        }

        // 选中图形改变事件
        private static object Event_SelectedShapesChanged = new object();
        public event EventHandler SelectedShapesChanged
        {
            add { _events.AddHandler(Event_SelectedShapesChanged, value); }
            remove { _events.RemoveHandler(Event_SelectedShapesChanged, value); }
        }
        protected virtual void OnSelectedShapesChanged(EventArgs e)
        {
            CheckShapeProperty();
            EventHandler handler = (EventHandler)_events[Event_SelectedShapesChanged];
            if (handler != null)
            {
                handler(this, e);
            }
        }

        // 游标改变事件
        private static object Event_CursorTypeChanged = new object();
        public event EventHandler CursorTypeChanged
        {
            add { _events.AddHandler(Event_CursorTypeChanged, value); }
            remove { _events.RemoveHandler(Event_CursorTypeChanged, value); }
        }
        protected virtual void OnCursorTypeChanged(EventArgs e)
        {
            EventHandler handler = (EventHandler)_events[Event_CursorTypeChanged];
            if (handler != null)
            {
                handler(this, e);
            }
        }

        // 选择工具事件
        private static object Event_SelectToolInSelecting = new object();
        public event EventHandler SelectToolInSelecting
        {
            add { _events.AddHandler(Event_SelectToolInSelecting, value); }
            remove { _events.RemoveHandler(Event_SelectToolInSelecting, value); }
        }
        protected virtual void OnSelectToolInSelecting(EventArgs e)
        {
            EventHandler handler = (EventHandler)_events[Event_SelectToolInSelecting];
            if (handler != null)
            {
                handler(this, e);
            }
        }

        // 样式改变事件
        private static object Event_PropertyCollectorChanged = new object();
        public event EventHandler PropertyCollectorChanged
        {
            add { _events.AddHandler(Event_PropertyCollectorChanged, value); }
            remove { _events.RemoveHandler(Event_PropertyCollectorChanged, value); }
        }
        protected virtual void OnPropertyCollectorChanged(EventArgs e)
        {
            EventHandler handler = (EventHandler)_events[Event_PropertyCollectorChanged];
            if (handler != null)
            {
                handler(this, e);
            }
        }

 

 



 

图形绘制属性的关系,对各种类进行继承关系的建立,重写方法,如:圆角矩形类、椭圆图形类继承于矩形类;填充描边类继承于描边类;直线、折线类继承于描边类。设定默认与传递来的属性

StrokedProperty.cs

 

    public class StrokedProperty : BaseProperty
        //绘制样式属性
    {
        public override ShapePropertyType PropertyType
        {
            get { return ShapePropertyType.StrokedProperty; }
        }

        public float PenWidth { get; set; }
        public Color StrokeColor { get; set; }
        public LineDashType LineDash { get; set; }
        public LineCapType StartLineCap { get; set; }
        public LineCapType EndLineCap { get; set; }
        public PenAlignment PenAlign { get; set; }
        public LineJoin HowLineJoin { get; set; }

        public StrokedProperty()//默认
        {
            PenWidth = 1.0f;
            StrokeColor = Color.Black;
            LineDash = LineDashType.Solid;//线     
            StartLineCap = LineCapType.Square;//起始
            EndLineCap = LineCapType.Square;//结束
            PenAlign = PenAlignment.Center;//对齐方式
            HowLineJoin = LineJoin.Round;//连接方式
        }

        public StrokedProperty Clone()
        {
            StrokedProperty sp = new StrokedProperty();
            sp.PenWidth = this.PenWidth;//粗细
            sp.StrokeColor = this.StrokeColor;//边框颜色
            sp.LineDash = this.LineDash;//线样式
            sp.StartLineCap = this.StartLineCap;//起始
            sp.EndLineCap = this.EndLineCap;//结束
            sp.PenAlign = this.PenAlign;//对齐
            sp.HowLineJoin = this.HowLineJoin;//连接方式
            return sp;
        }
    }

 

 

 

 


StrokedShape.cs

继承于DrawableBase.cs

 

public abstract class StrokedShape : DrawableBase
    {       
        private StrokedProperty _property;
        private Pen pen;

        public StrokedShape(Kernel container, StrokedProperty property)
            : base(container, property)
        {
            _property = property;
            if (_property != null)
                SetNewPen();
        }

        protected override void AfterPropertyChanged(BaseProperty oldValue, BaseProperty newValue)
        {
            _property = (StrokedProperty)newValue;
            if (_property != null)
                SetNewPen();
        }
        //设置样式
        private void SetNewPen()
        {
            if (pen != null)
                pen.Dispose();

            float width = Math.Min(_property.PenWidth, Const.Max_Stroke_Width);
            pen = new Pen(_property.StrokeColor, width);
            pen.Alignment = _property.PenAlign;
            pen.LineJoin = _property.HowLineJoin;
            //线样式
            switch (_property.LineDash)
            {
                case LineDashType.Solid:
                    pen.DashStyle = DashStyle.Solid;
                    break;
                case LineDashType.Dot:
                    pen.DashStyle = DashStyle.Dot;
                    break;
                case LineDashType.DashedDot:
                    pen.DashStyle = DashStyle.DashDot;
                    break;
                case LineDashType.DashedDotDot:
                    pen.DashStyle = DashStyle.DashDotDot;
                    break;
                case LineDashType.Dash1:
                    pen.DashPattern = new float[] { 2, 2 };
                    break;
                case LineDashType.Dash2:
                    pen.DashPattern = new float[] { 4, 4 };
                    break;
            }
            //头部样式
            switch (_property.StartLineCap)
            {
                case LineCapType.Rounded:
                    pen.StartCap = LineCap.Round;
                    break;
                case LineCapType.Square:
                    pen.StartCap = LineCap.Square;
                    break;
                case LineCapType.LineArrow:
                    pen.CustomStartCap = new LineArrowCap();
                    break;
                case LineCapType.NormalArrow:
                    pen.CustomStartCap = new AdjustableArrowCap(5, 5, true);
                    break;
                case LineCapType.SharpArrow:
                    pen.CustomStartCap = new SharpArrowCap();
                    break;
                case LineCapType.SharpArrow2:
                    pen.CustomStartCap = new SharpArrowCap(8.0f, 6.4f, 4.2f);
                    break;
                case LineCapType.Rectangle:
                    pen.CustomStartCap = new RectangleCap();
                    break;
                case LineCapType.Circle:
                    pen.CustomStartCap = new CircleCap();
                    break;
                case LineCapType.Hip:
                    break;
            }
            //尾部样式
            switch (_property.EndLineCap)
            {
                case LineCapType.Rounded:
                    pen.EndCap = LineCap.Round;
                    break;
                case LineCapType.Square:
                    pen.EndCap = LineCap.Square;
                    break;
                case LineCapType.LineArrow:
                    pen.CustomEndCap = new LineArrowCap();
                    break;
                case LineCapType.NormalArrow:
                    pen.CustomEndCap = new AdjustableArrowCap(5, 5, true);
                    break;
                case LineCapType.SharpArrow:
                    pen.CustomEndCap = new SharpArrowCap();
                    break;
                case LineCapType.SharpArrow2:
                    pen.CustomEndCap = new SharpArrowCap(8.0f, 6.4f, 4.2f);
                    break;
                case LineCapType.Rectangle:
                    pen.CustomEndCap = new RectangleCap();
                    break;
                case LineCapType.Circle:
                    pen.CustomEndCap = new CircleCap();
                    break;
                case LineCapType.Hip:
                    break;
            }

 

 

FilledShape.cs

 


RectShape.cs


    ///<summary>

    ///结构体,用于表示一个矩形区域,可以是可拖动的线段顶点,虚线选择框上面锚点(拖动可对形状缩放)

    ///以及一个旋转形状的区域

    ///</summary>

    publicstructDraggableHotSpot

    {

        public Rectangle Rect;//选框

        public HotSpotType _type;//锚点类型

        publicint AnchorAngle;//角度

        publicbool Visible;//隐藏或显示属性

 

        publicDraggableHotSpot(HotSpotType type)

        {

            Rect = Rectangle.Empty;

            _type = type;

            AnchorAngle = 0;

            Visible = true;

        }

 

        publicDraggableHotSpot(Rectangle rect, HotSpotType type)

        {

            Rect = rect;

            _type = type;

            AnchorAngle = 0;

            Visible = true;

        }

 

        public HotSpotType Type { get { return _type; } }

    }

}

 

 

   ShapeHelper.cs

        //图形绘制实现工具,实现图形变换功能,定义坐标点的有序对,根据不同图形性质,需定义23个成员变量,根据不同图形数学公式得到全部点的坐标数据,进行绘制与后续操作


TransformHelper.cs

实现变换功能,根据鼠标移动位置和锚点坐标,数学公式计算得出图形变换后数据点

 

 public static class TransformHelper
    {
        /// <summary>
        /// 用于椭圆变换,返回按旋转前x, y坐标放大后的椭圆的数据点
        /// </summary>
        public static PointF[] GetScaledEllipsePathPoints(GraphicsPath path, int index, Point mousePos, bool shift)
            //路径中的点
        {
            PointF[] pf = path.PathPoints;
            if (index > 3)
            {
                int[] ex = new int[] { 9, 0, 3, 6 };
                return getScaledEllipsePathPoints_Sides(pf, ex[(index - 4)], mousePos);
            }
            else
            {
                return getScaledEllipsePathPoints_Corners(pf, index, mousePos, shift);
            }
        }

        private static PointF[] getScaledEllipsePathPoints_Corners(PointF[] pf, int index, Point mousePos, bool shift)
            //游标点
        {
            int[] ex = new int[] { 6, 9, 9, 0, 0, 3, 3, 6 };
            pf = getScaledEllipsePathPoints_Sides(pf, ex[index * 2], mousePos);
            return getScaledEllipsePathPoints_Sides(pf, ex[index * 2 + 1], mousePos);
        }

        private static PointF[] getScaledEllipsePathPoints_Sides(PointF[] pf, int index, Point mousePos)
            //
        {            
            int index2 = (index + 6) % 12;
            float dx = mousePos.X - pf[index].X;
            float dy = mousePos.Y - pf[index].Y;
            double r = Math.Sqrt(dx * dx + dy * dy);
            double angle = Math.Atan2(pf[index].Y - pf[index2].Y, pf[index].X - pf[index2].X);
            double mouseAngle = Math.Atan2(mousePos.Y - pf[index].Y, mousePos.X - pf[index].X);
            double length = r * Math.Cos(mouseAngle - angle);
            dx = (float)(length * Math.Cos(angle));
            dy = (float)(length * Math.Sin(angle));
            
            int[] indices = new int[] { 9, 11, 12, 13, 15 };
            foreach (int i in indices)
            {
                int j = (index + i) % 12;
                if (i == 11 || i == 12 || i == 13) 
                {
                    pf[j].X += dx;
                    pf[j].Y += dy;
                }
                else
                {
                    pf[j].X += dx / 2;
                    pf[j].Y += dy / 2;
                }
            }

            dx = pf[index].X - pf[index2].X;
            dy = pf[index].Y - pf[index2].Y;
            length = Math.Sqrt(dy * dy + dx * dx);
            float len = (float)(length / 2 * Const.MagicBezier);
            angle = Math.Atan2(pf[index].Y - pf[index2].Y, pf[index].X - pf[index2].X);
            int t1 = (index + 8) % 12;
            int t2 = (index + 9) % 12;
            int t3 = (index + 10) % 12;
            pf[t3].X = (float)(pf[t2].X + len * Math.Cos(angle));
            pf[t3].Y = (float)(pf[t2].Y + len * Math.Sin(angle));
            pf[t1].X = (float)(pf[t2].X + len * Math.Cos(angle + Math.PI));
            pf[t1].Y = (float)(pf[t2].Y + len * Math.Sin(angle + Math.PI));

            int s1 = (index + 4) % 12;
            int s2 = (index + 3) % 12;
            int s3 = (index + 2) % 12;
            dx = pf[s2].X - pf[t2].X;
            dy = pf[s2].Y - pf[t2].Y;
            pf[s3].X = pf[t3].X + dx;
            pf[s3].Y = pf[t3].Y + dy;
            pf[s1].X = pf[t1].X + dx;
            pf[s1].Y = pf[t1].Y + dy;

            pf[12] = pf[0];
            return pf;
        }

        /// <summary>
        /// 用于矩形变换,返回按旋转前x, y坐标放大后的数据点
        /// </summary>
        public static PointF[] GetScaledRectPathPoints(GraphicsPath path, int draggedPointIndex, 
            PointF draggedPoint, Point mousePos, bool shift)
        {
            if (draggedPointIndex > 3)
                return getRectScaledPoints_Sides(path, draggedPointIndex, draggedPoint, mousePos);
            else
                return getRectScaledPoints_Corners(path, draggedPointIndex, mousePos, shift);
        }

        private static PointF[] getRectScaledPoints_Sides(GraphicsPath path, 
            int draggedPointIndex, PointF draggedPoint, Point mousePos)
        {
            int targetPt1Index = draggedPointIndex - 4;
            int targetPt2Index = (targetPt1Index + 1) % 4;
            int anglePt2Index = targetPt2Index;
            int anglePt1Index = (anglePt2Index + 1) % 4;

            PointF[] pf = path.PathPoints;

            double angle = Math.Atan2(pf[anglePt2Index].Y - pf[anglePt1Index].Y,
                pf[anglePt2Index].X - pf[anglePt1Index].X);
            float dy = mousePos.Y - draggedPoint.Y;
            float dx = mousePos.X - draggedPoint.X;
            double mouseAngle = Math.Atan2(dy, dx);
            double r = Math.Sqrt(dy * dy + dx * dx);
            double length = r * Math.Cos(mouseAngle - angle);

            pf[targetPt1Index].X = (float)(pf[targetPt1Index].X + length * Math.Cos(angle));
            pf[targetPt1Index].Y = (float)(pf[targetPt1Index].Y + length * Math.Sin(angle));
            pf[targetPt2Index].X = (float)(pf[targetPt2Index].X + length * Math.Cos(angle));
            pf[targetPt2Index].Y = (float)(pf[targetPt2Index].Y + length * Math.Sin(angle));

            return pf;
        }

        private static PointF[] getRectScaledPoints_Corners(GraphicsPath path, 
            int draggedPointIndex, Point mousePos, bool shift)
        {
            int targetPt1Index = (draggedPointIndex + 3) % 4;
            int targetPt2Index = (draggedPointIndex + 1) % 4;
            int pinPtIndex = (draggedPointIndex + 2) % 4;

            PointF[] pf = path.PathPoints;

            double angle1 = Math.Atan2(pf[targetPt1Index].Y - pf[pinPtIndex].Y,
                pf[targetPt1Index].X - pf[pinPtIndex].X);
            double angle2 = Math.Atan2(pf[targetPt2Index].Y - pf[pinPtIndex].Y,
                pf[targetPt2Index].X - pf[pinPtIndex].X);
            float dy = mousePos.Y - pf[pinPtIndex].Y;
            float dx = mousePos.X - pf[pinPtIndex].X;
            double mouseAngle = Math.Atan2(dy, dx);
            double r = Math.Sqrt(dy * dy + dx * dx);
            double length1 = r * Math.Cos(mouseAngle - angle1);
            double length2 = r * Math.Cos(mouseAngle - angle2);

            float x1 = (float)(pf[pinPtIndex].X + length1 * Math.Cos(angle1));
            float y1 = (float)(pf[pinPtIndex].Y + length1 * Math.Sin(angle1));
            float x2 = (float)(pf[pinPtIndex].X + length2 * Math.Cos(angle2));
            float y2 = (float)(pf[pinPtIndex].Y + length2 * Math.Sin(angle2));

            pf[targetPt1Index].X = x1;
            pf[targetPt1Index].Y = y1;
            pf[draggedPointIndex].X = mousePos.X;
            pf[draggedPointIndex].Y = mousePos.Y;
            pf[targetPt2Index].X = x2;
            pf[targetPt2Index].Y = y2;

            return pf;
        }
    }
}

 

 

 


StrokedProperty.cs

描边属性类,直线折线类


FormMain.cs

主页面功能显示与实现

标记索引值,用于后续事件传递

选择语句

修改事件

菜单栏设置

加载主窗体 

菜单栏文件功能,编辑功能

调用Kernel类的方法

 

(三)   软件截图:

 

 

(四)   测试用例

 

1.       绘制直线,粗细为2,样式点线


2.       绘制直线,样式:线状箭头,实线,线状箭头


3.       绘制指示箭头,尺寸中

 

4.       绘制折线,粗细为6,红色,样式:圆角起点,实线,圆角终点

 

5.       绘制矩形,粗细为5,样式:圆角起点,实线,圆角终点,仅描边,颜色:蓝色


6.       绘制矩形,粗细为8,样式:圆角起点,虚线,圆角终点,

描边及填充,颜色:蓝色灰色


7.       绘制圆角矩形,仅填充,颜色:橙色


8.       绘制椭圆,粗细为14,仅描边,黑色,将其旋转90°


9.       移动图形从左到右


10.    拉伸图形


11.    将图像保存到桌面,文件名test1.jpg


 

 

 

 12.    打开图像test1.jpg


13.    全选图形


14.    选中矩形并删除


15.    清空画板


 

 

 

posted @ 2022-03-11 15:06  Clematis  阅读(168)  评论(0)    收藏  举报