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

C# 的画布实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (27投票s)

2004年2月28日

4分钟阅读

viewsIcon

261721

downloadIcon

9670

C# 中画布的设计和实现

引言

在许多不同的应用程序中,都需要进行图形输入。除了创建新的图形对象(如线条、矩形和多边形)之外,还需要对对象进行编辑和以不同的比例进行查看。在某些情况下,图形事件(如形状创建、选择和移动)应与某些应用程序逻辑绑定。

画布或图形编辑器是一个为应用程序提供此类功能的组件。

背景

画布控件或小部件是一个包含不同类型图形对象的组件,负责其查看和编辑。

大多数 GUI 工具包都有自己的画布组件。例如,Qt 工具包、Borland C++ Builder 和 Tcl/Tk 中都有画布。然而,.NET 中却缺失了。

对于画布的实现和功能,我借鉴了 Qt 和 Tcl/Tk 工具包的经验。在这两个工具包中,画布都提供了丰富的功能来创建和操作图形对象。

在这两个工具包中,画布的缩放和平移会影响其中存储的图形对象的几何形状。当你在画布中存储“真实世界”的测量值时,这很不方便,因为每次你在画布中编辑它时,你都需要跟踪“真实世界”坐标系统的变化。

为了避免这种不便,此实现将逻辑上划分为几何模型和视图。当你在画布中编辑对象时,几何模型(以“真实世界”坐标表示)会发生变化,而视图(客户端坐标)保持不变;当你缩放和平移画布时,几何模型保持不变。

画布几何模型可以有多个视图,显示模型的不同部分。当模型发生变化时,所有视图都会自动更新,这意味着如果你在一个视图中移动或编辑形状,你会在其他视图中看到变化。

使用画布

CanvasCanvas.cs 文件中的 CanvasControl 命名空间中实现。

用法示例在 CanvasDemo.cs 文件中。

创建新的 Canvas 并连接到其事件

// create new canvas
canvas1 = new CanvasControl.Canvas();
// connect listeners to canvas events
canvas1.CoordChangeEvent += new
  CanvasControl.Canvas.CoordChangeEventHandler(OnCoordChainged);
canvas1.ItemNewEvent += new
  CanvasControl.Canvas.ItemEventHandler(OnNewCanvasItem);
canvas1.ItemEnterEvent += new
  CanvasControl.Canvas.ItemEventHandler(OnEnterCanvasItem);

其中

当光标在 Canvas 内移动时,会生成 CoordChangeEvent 事件。事件处理程序 CoordChangeEventHandler 的第二个参数包含光标的坐标和背景图像的 RGB 值。

public void OnCoordChainged(object sender, 
CanvasControl.CoordEventArgs e)
{
    textBox1.Text = e.X.ToString() + "," + e.Y.ToString();
}

ItemNewEventItemEnterEvent – 当光标进入和离开图形对象时会生成事件。第二个参数包含对象的 ID(索引)。可以通过其 ID 访问图形对象。

public void OnEnterCanvasItem(object sender, 
CanvasControl.ItemEventArgs e)
{
    textBox3.Text = e.indx.ToString();
}

为了创建现有 Canvas(其几何模型)的附加视图,我们应该创建第二个 Canvas,例如 canvas2,并将其附加到前一个。

canvas2 = new CanvasControl.Canvas();
canvas2.setGeomModel(canvas1.getGeomModel());

Canvas 本身不包含其他图形控件,它通过其 API 进行控制。但是,可以选择连接滚动条控件,以便在画布导航期间更新滚动条控件。

canvas1.connectScrolls(hScrollBar1,vScrollBar1);

要创建、移动或选择图形对象,我们需要按如下方式更改 Canvas 状态

// create graphical objects
canvas1.CreateRectangle();

canvas1.CreateLine();

canvas1.CreatePolygon();

// move objects
canvas1.StartMoving();
// select objects
canvas1.StartSelection();

可以通过 ID 访问图形对象,并且可以检索或更新其几何和其他属性。在下面的函数中,检索选定项目的 ID,并使用用户定义的颜色进行更新。

private void btnColor_Click(object sender, System.EventArgs 
e)
{
        ColorDialog colorDialog = new ColorDialog();
        if(colorDialog.ShowDialog() != DialogResult.OK)
                 return;

        Color color = colorDialog.Color;
        int [] selected = canvas1.GetSelected();
        foreach(int i in selected)
        {
                 CanvasControl.CanvasItem item = canvas1.GetItem(i);
                 item.Icolor = color;
                 item.select(false);
        }

        canvas1.Invalidate();
        canvas1.Update();
}

你可以创建自己的几何对象,为此,你应该实现 CanvasItem 接口(参见 Canvas.cs 文件)并使用 Canvas.AddShape 方法将其放入画布中。

设计问题

画布的设计中使用了几种设计模式。

模板模式用于 CanvasItem 类,以在基类中实现一些形状的通用功能。

观察者模式用于通知订阅者画布更改,它使用 C# 事件来实现。

外观模式用于隐藏用户内部的画布对象。

在上面的类图中,显示了创建画布控件的类。

  • Canvas - 封装了画布控件的视图(模型/视图模式)。Canvas 通过 C# 事件与几何模型 CanvasGeomModel 同步。每次模型更改时,模型会生成 ItemChangedEventModelChangedEvent,强制画布更新视图。每次视图 Canvas 需要显示模型时,它都会调用 CanvasGeomModeldraw 方法,指定图形上下文(或画家对象)Graphics 和一个在视图的“客户端”坐标系与模型的“真实世界”坐标系之间的 Matrix 变换。最终,从 CanvasGeomModel 将绘制请求委托给具体的形状,这些形状实现了 CanvasItem draw 接口,以在视图上绘制自身。
  • CanvasGeomModel - 封装了画布控件的几何模型。它负责存储、编辑和显示派生自 CanvasItem 类的不同形状。几何对象在模型中以“真实世界”坐标表示,独立于不同视图的“客户端”坐标。
  • CanvasItem - 封装了存储在几何模型 CanvasGeomModel 中的几何对象或形状的通用行为。它表示 Canvas 用于操作和显示不同类型形状的接口。要创建新形状,你应该实现 CanvasItem 接口中定义的方法。
      public class LineCanvasItem : CanvasItem
      {
        public float x1;
        public float y1;
        public float x2;
        public float y2;
        private bool is_selected = false;
    
        // construct from 2 points coordinates
        public LineCanvasItem(float x_1,float y_1,float x_2,float y_2)
        {
          x1 = x_1;
          y1 = y_1;
          x2 = x_2;
          y2 = y_2;
        }
        // returns true if item is selected
        public override bool isSelected()
        {
          return is_selected;
        }
        // select item
        public override void select(bool m)
        {
          is_selected = m;
        }
        // returns true if the item is within the distanse
        public override bool isCloseToPoint(PointF pnt,float dist)
        {
          double curr_dist = Geom.segmentToPointSqrDist(
            new PointF(x1,y1),new PointF(x2,y2),pnt);
    
          return Math.Sqrt(curr_dist) < dist;
        }
        // return bounding box of the item
        public override RectangleF boundingBox()
        {
          return new RectangleF(x1,y1,x2-x1,y2-y1);
        }
        // start point X coordinate of the shape
        public override float X()
        {
          return x1;
        }
        // start point Y coordinate of the shape
        public override float Y()
        {
          return y1;
        }
        // move shape to specified location
        public override void move(PointF p)
        {
          float dx = p.X-x1;
          float dy = p.Y-y1;
    
          x1 += dx;
          y1 += dy;
    
          x2 += dx;
          y2 += dy;
        }
        // move shape by specified shift
        public override void moveBy(float xs,float ys)
        {
          x1+=xs;
          y1+=ys;
    
          x2+=xs;
          y2+=ys;
        }
        // draw shape on the view
        public override void draw(Graphics graph,
            System.Drawing.Drawing2D.Matrix trans)
        {
          // transform points to "client" view coordinate system
          PointF [] points = {new PointF(x1,y1), new PointF(x2,y2)};
          trans.TransformPoints(points);
    
          // draw line in "client" coordinate system
          if(is_selected)
            graph.DrawLine(new Pen(Color.Cyan),
              (int)points[0].X,(int)points[0].Y,
              (int)points[1].X,(int)points[1].Y);
          else
            graph.DrawLine(new Pen(Icolor),(int)points[0].X,
              (int)points[0].Y,
              (int)points[1].X,(int)points[1].Y);
        }
      }
            
    然后应该使用 Canvas.AddShape 方法将新形状添加到画布中。
     canvas1.AddShape(new LineCanvasItem(10,10,100,100));        
  • Geom - Canvas 中使用的几何过程。

历史

  • 2004/02/28 初始版本。
  • 2004/03/17 从画布视图中分离几何模型 + 设计解释。
© . All rights reserved.