侧边栏壁纸
  • 累计撰写 93 篇文章
  • 累计创建 11 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Winform绘图

祈安千
2026-05-15 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

Why

  • 主要是最近看到了很多有关于WPF的绘图的,主要是2D绘图,我在想可不可以用Winform绘制,查找资料发现不同于WPF,Winform没有WPF的样条元素,所以只能自己自定义然后实现,同时也意味着选中,高亮,平移旋转等基本所有的动作都需要自己写,同时也因为不是可识别的Winform控件,所以其实最难做的是选中。

How

  • 安装SkiaSharp
    skiasharp_nuget.png
  • 在页面设计的工具库添加一个SKControl控件,如果没有的话重新生成解决方案就可以了,取名skControl_Main.
  • 自定义控件如下
    shapes.png
  • Shape
public abstract class Shape : XtraUserControl
{
    protected Shape()
    {
        if (string.IsNullOrWhiteSpace(ID))
        {
            ID = Guid.NewGuid().ToString();
        }
    }

    public static float SKScale { get; set; } = 1;
    public string ID { get; set; }

    public SKColor Color { get; set; } = new SKColor(System.Drawing.Color.Green.R, System.Drawing.Color.Green.G, System.Drawing.Color.Green.B);

    public bool IsStroke { get; set; } = true;
    public float StrokeWidth { get; set; } = 10;

    public SKPoint LocationPoint { get; set; } = new SKPoint(0, 0);


    public int CanveWidth { get; set; }
    public int CanveHeight { get; set; }

    public SKPoint StartPoint { get; set; } = new SKPoint(0, 0);
    public SKPoint EndPoint { get; set; } = new SKPoint(0, 0);

    public SKPath path { get; set; } = new SKPath();
    public SKPath HintPath { get; set; } = new SKPath();

    public bool IsSelected { get; set; } = false;

    public SKRect pathBounds;
    public SKColor SelectedColor { get; set; } = new SKColor(System.Drawing.Color.Red.R, System.Drawing.Color.Red.G, System.Drawing.Color.Red.B);

    public abstract void Draw(SKCanvas canvas);


    public virtual void GetPathData(SKPaint paint)
    {
        paint.GetFillPath(path, HintPath);
        HintPath.GetBounds(out pathBounds);
    }
    public virtual bool IsHint(SKPoint point)
    {
        return HintPath.Contains(point.X, point.Y);
    }

    public virtual bool Intersects(SKRect rect)
    {
        return rect.Contains(pathBounds);
    }

}
  • LineShape
 public class LineShape : Shape
 {
     public override void Draw(SKCanvas canvas)
     {
         if (canvas == null)
         {
             return;
         }


         path.MoveTo(StartPoint);
         path.LineTo(EndPoint);
         path.Close();


         using (var paint = new SKPaint())
         {
             paint.Color = IsSelected ? SelectedColor : Color;
             paint.IsStroke = IsStroke;
             paint.StrokeWidth = StrokeWidth;
             paint.Style = SKPaintStyle.Stroke;
             //canvas.DrawLine(StartPoint.X, StartPoint.Y, EndPoint.X, EndPoint.Y, paint);
             canvas.DrawPath(path, paint);

             GetPathData(paint);
         }

     }


 }
  • ArcShape
public class ArcShape : Shape
{
    public float SweepAngle { get; set; } = 180;
    public new float StrokeWidth { get; set; } = 10;
    public override void Draw(SKCanvas canvas)
    {
        if (canvas == null)
        {
            return;
        }

        path.MoveTo(StartPoint);
        var rect = new SKRect(StartPoint.X, StartPoint.X, EndPoint.Y, EndPoint.Y);
        path.AddArc(rect, 0, SweepAngle);
        //如果是圆弧 则不需要闭合
        //path.Close();

        using (var paint = new SKPaint())
        {
            paint.IsAntialias = true;
            paint.Color = IsSelected ? SelectedColor : Color;
            paint.IsStroke = IsStroke;
            paint.StrokeWidth = StrokeWidth;
            paint.Style = SKPaintStyle.Stroke;
            canvas.DrawPath(path, paint);

            GetPathData(paint);
        }
    }


}
  • CircleShape
public class CircleShape : Shape
{
    public float Radius { get; set; } = 10;

    public SKPoint CenterPoint { get; set; } = new SKPoint(0, 0);
    public override void Draw(SKCanvas canvas)
    {
        if (canvas == null)
        {
            return;
        }

        path.MoveTo(StartPoint);

        path.AddCircle(CenterPoint.X, CenterPoint.Y, Radius);
        path.Close();

        using (var paint = new SKPaint())
        {
            paint.IsAntialias = true;
            paint.Color = IsSelected ? SelectedColor : Color;
            paint.IsStroke = IsStroke;
            paint.StrokeWidth = StrokeWidth;
            paint.Style = SKPaintStyle.Stroke;
            canvas.DrawPath(path, paint);

            GetPathData(paint);
        }
    }


}
  • 以下是主要的实现代码,包括高亮,平移,选择等
 public partial class TestControl : XtraUserControl
 {
     public TestControl()
     {
         InitializeComponent();
     }

     private void TestControl_Load(object sender, EventArgs e)
     {
         skControl_Main.MouseWheel += SkControl_Main_MouseWheel;

         InitShapes();
     }



     private void InitShapes()
     {
         var random = new Random();
         for (int i = 0; i < 10; i++)
         {
             var line = new LineShape();
             line.StartPoint = new SKPoint(i * random.Next(10, 100), i * random.Next(10, 100));
             line.EndPoint = new SKPoint(i * random.Next(10, 100) + random.Next(120), i * random.Next(10, maxValue: 100) + random.Next(120));
             shapes.Add(line);

             var arc = new ArcShape();
             arc.StartPoint = new SKPoint(i * random.Next(10, 100), i * random.Next(10, 100));
             arc.EndPoint = new SKPoint(i * random.Next(10, 100) + random.Next(120), i * random.Next(10, 100) + random.Next(120));
             arc.SweepAngle = random.Next(0, 360);
             shapes.Add(arc);

             var circle = new CircleShape();
             circle.CenterPoint = new SKPoint(i * random.Next(10, 100), i * random.Next(10, 100));
             circle.Radius = random.Next(10, 100);
             shapes.Add(circle);
         }
     }

     private List<Shape> shapes = new List<Shape>();
     private float _offsetX = 0;
     private float _offsetY = 0;
     private bool _isPanning = false;
     private Point _lastMousePoint;
     private bool _isSelecting = false;
     private SKPoint _selectStart;
     private SKPoint _selectEnd;

     private void skControl_Main_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
     {
         var canvas = e.Surface.Canvas;
         canvas.Clear();

         //坐标系从左上角转换到左下角
         canvas.Translate(0, e.Info.Height);
         canvas.Scale(1, -1);

         // 平移
         canvas.Translate(_offsetX, _offsetY);

         // 缩放
         canvas.Scale(Shape.SKScale);

         foreach (var shape in shapes)
         {
             shape.CanveWidth = e.Info.Width;
             shape.CanveHeight = e.Info.Height;
             shape.Draw(canvas);
         }

         if (_isSelecting)
         {
             SKRect rect = CreateRect(_selectStart, _selectEnd);

             using (var paint = new SKPaint())
             {
                 paint.Color = SKColors.DeepSkyBlue;
                 paint.Style = SKPaintStyle.Stroke;
                 paint.StrokeWidth = 1;
                 paint.PathEffect = SKPathEffect.CreateDash(new float[] { 10, 10 }, 0);
                 canvas.DrawRect(rect, paint);

             }
         }

     }
     private SKPoint ScreenToWorld(float x, float y)
     {

         float worldX = (x - _offsetX) / Shape.SKScale;

         float worldY = ((skControl_Main.Height - y) - _offsetY) / Shape.SKScale;
         return new SKPoint(worldX, worldY);
     }
     private void skControl_Main_MouseDown(object sender, MouseEventArgs e)
     {
         if (ModifierKeys == Keys.None && e.Button == MouseButtons.Left)
         {

             SKPoint worldPoint = ScreenToWorld(e.X, e.Y);
             var selectShapes = shapes.Where(p => p.IsHint(worldPoint)).ToList();


             selectShapes.ForEach(p =>
             {
                 p.IsSelected = !p.IsSelected;
             });

             skControl_Main.Invalidate();
         }
         else if (e.Button == MouseButtons.Middle)
         {
             _isPanning = true;

             _lastMousePoint = e.Location;

             skControl_Main.Cursor = Cursors.Hand;
         }
         else if (ModifierKeys == Keys.Control && e.Button == MouseButtons.Left)
         {
             //框选
             _isSelecting = true;

             _selectStart = ScreenToWorld(e.X, e.Y);
             _selectEnd = _selectStart;
         }
     }
     private void skControl_Main_MouseMove(object sender, MouseEventArgs e)
     {
         if (_isPanning)
         {
             float dx =
                 e.X - _lastMousePoint.X;

             float dy =
                 e.Y - _lastMousePoint.Y;


             _offsetX += dx;

             _offsetY -= dy;

             _lastMousePoint = e.Location;

             skControl_Main.Invalidate();
         }
         if (_isSelecting)
         {
             _selectEnd = ScreenToWorld(e.X, e.Y);

             skControl_Main.Invalidate();
         }
     }
     private void skControl_Main_MouseUp(object sender, MouseEventArgs e)
     {
         if (e.Button == MouseButtons.Middle)
         {
             _isPanning = false;

             skControl_Main.Cursor = Cursors.Default;
         }
         if (_isSelecting)
         {
             _isSelecting = false;

             SKRect rect = CreateRect(_selectStart, _selectEnd);

             foreach (var shape in shapes)
             {
                 shape.IsSelected = shape.Intersects(rect);
             }

             skControl_Main.Invalidate();
         }
     }
     private void SkControl_Main_MouseWheel(object sender, MouseEventArgs e)
     {
         float oldScale = Shape.SKScale;

         if (e.Delta > 0)
         {
             Shape.SKScale *= 1.1f;
         }
         else
         {
             Shape.SKScale /= 1.1f;
         }

         // 鼠标缩放中心
         float mouseX = e.X;
         float mouseY = e.Y;

         _offsetX =
             mouseX -
             (mouseX - _offsetX)
             * (Shape.SKScale / oldScale);

         _offsetY =
             mouseY -
             (mouseY - _offsetY)
             * (Shape.SKScale / oldScale);

         skControl_Main.Invalidate();
     }

     private void skControl_Main_MouseDoubleClick(object sender, MouseEventArgs e)
     {
         if (e.Button == MouseButtons.Left)
         {
             SKPoint worldPoint = ScreenToWorld(e.X, e.Y);
             var selectShapes = shapes.Where(p => p.IsHint(worldPoint)).ToList();
             if (!selectShapes.Any())
             {
                 //双击 取消所有的选中
                 shapes.ForEach(p =>
                 {
                     p.IsSelected = false;
                 });
                 skControl_Main.Invalidate();
             }
             else
             {
                 //todo 可以弹出类似于属性面板的
             }
         }

     }

     private SKRect CreateRect(SKPoint p1, SKPoint p2)
     {
         return new SKRect(
             Math.Min(p1.X, p2.X),
             Math.Min(p1.Y, p2.Y),

             Math.Max(p1.X, p2.X),
             Math.Max(p1.Y, p2.Y));
     }
 }

Tips

  • 看着很简单,但其实中间还是有很多坑。首先SKCanvas这个东西在每次绘制的时候都是新的,所以没有办法缓存,所以需要在事件PaintSurface中每次获取。
  • 绘制的默认坐标系是左上角,需要转换为左下角。
  • 更改了shapes需要手动调用绘制的时候,应该使用skControl_Main.Invalidate();
  • 绘制其实有两种方法,一种是canvas.DrawLine(StartPoint.X, StartPoint.Y, EndPoint.X, EndPoint.Y, paint);这种的,一种是 canvas.DrawPath(path, paint);,需要实现高亮的话,一定只能使用第二种方法,因为第一种是绘制了就绘制了,不会有任何的路径信息,后续无法检测。
  • 计算包围盒的时候需要使用HintPath.GetBounds(out pathBounds);
  • 最后InitShapes只是测试数据而已,后续我实现了导入DXF文件,然后解析绘制,效果还不错。以下是截图
    dxf_import.png
  • 测试发现读取速度很快,对于大型项目也是很不错的一个处理方式。
0

评论区