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

对 DrawTools 的扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (51投票s)

2007 年 3 月 6 日

CPOL

5分钟阅读

viewsIcon

622600

downloadIcon

17544

DrawTools 库扩展,包含图层、缩放、平移、旋转功能

更新于 2011/10/4

Draw Tools Redux Image

引言

Alex Fr 在他的 DrawTools 文章中提供了一套出色的绘图工具,这些工具构成了本文的基础。本文在原有的工具集上进行了扩展,方式如下:

  1. 除了基本的矩形、椭圆、线条和涂鸦工具外,此版本还增加了折线、填充椭圆、填充矩形、文本和图像工具。
  2. 多重绘图图层
  3. 缩放
  4. 平移
  5. 旋转

在本文中,我将描述图层的实现方式,以及文本和图像工具。

背景

有关基本应用程序的构建方式、类结构等详细信息,请参阅原始 DrawTools 文章。

另外,假定读者对 GDI+ 的基础知识有实际的理解,包括矩阵。有关 GDI+ 的出色介绍,请参阅 www.bobpowell.net

实现图层

为应用程序添加图层需要添加两个类:LayerLayers。其中 Layer 定义单个图层,而 Layers 则定义 ArrayList 中图层的集合。

每个 Layer 暴露以下属性:

 private string _name;
 private bool _isDirty;
 private bool _visible;
 private bool _active;
 private GraphicsList _graphicsList;

请注意,Layer 包含 GraphicsList——这是整个关键。每个 Layer 包含自己的绘图对象列表,而不是 DrawAreaDrawArea 被修改为声明一个 Layers 集合,而不是一个 GraphicsList 集合。

 // Define the Layers collection
 private Layers _layers; 

DrawArea 初始化时,Layers 会通过创建第一个 Layer 并将其设置为 Active(活动)和 Visible(可见)来初始化。

 public DrawArea()
 {
 // create list of Layers, with one default active visible layer
 _layers = new Layers();
 _layers.CreateNewLayer("Default");
 _panning = false;
 _panX = 0;
 _panY = 0;
 // This call is required by the Windows.Forms Form Designer.
 InitializeComponent();
 }

Layers 类中,CreateNewLayer() 方法实际创建新的 Layer

 /// <summary>
 /// Create a new layer at the head of the layers list and set it 
 /// to Active and Visible.
 /// </summary>
 /// <param name="theName">The name to assign to the new layer</param>
 public void CreateNewLayer(string theName)
 {
 // Deactivate the currently active Layer
 if(layerList.Count > 0)
 ((Layer)layerList[ActiveLayerIndex]).IsActive = false;
 // Create new Layer, set it visible and active
 Layer l = new Layer();
 l.IsVisible = true;
 l.IsActive = true;
 l.LayerName = theName;
 // Initialize empty GraphicsList for future objects
 l.Graphics = new GraphicsList();
 // Add to Layers collection
 this.Add(l);
 }

请注意,任何一个或所有 Layers 可以同时可见,但任何时候只能有一个 Layer 是活动的。

您可以通过单击应用程序窗口底部的“当前图层”名称来控制示例应用程序中的 Layers。单击名称(“Default”)即可打开 Layers 对话框。

在此对话框中,您可以添加新的 Layers,更改 Layer 的名称,并更改 Layer 的可见性和哪个 Layer 是活动的。每当您单击“添加图层”按钮时,“新图层”列将被选中。要删除图层,只需选中“删除”列,然后通过“关闭”按钮关闭对话框。请记住,任何时候只能有一个图层是活动的。如果您尝试使多个图层活动,系统会提醒您。另外请注意,活动的 Layer 必须是可见的。

应用程序运行时,每个绘制的对象都会被添加到活动 Layer 所维护的 GraphicsList 中。请注意,在保存和重新打开绘图文件时,此关系会得以保留。

当您想要在另一张图片“之上”绘制时,图层会非常方便。例如,本文顶部的图像包含两个图层。下图显示了同一张图片,但背景图层已关闭。

这是同一幅图,其中绘图图层不可见,背景图层可见。

位于可见但非活动图层上的对象无法被选中、移动、删除等。

每个绘图对象都通过 ToolObject 类中的 AddNewObject() 方法被添加到正确的 Layer 中。

protected void AddNewObject(DrawArea drawArea, DrawObject o)
{
     int al = drawArea.TheLayers.ActiveLayerIndex;
     drawArea.TheLayers[al].Graphics.UnselectAll();
     o.Selected = true;
   o.Dirty = true;
   drawArea.TheLayers[al].Graphics.Add(o);
     drawArea.Capture = true;
   drawArea.Refresh();
} 

实现缩放、平移和旋转

缩放、平移和旋转是通过向 MainFormDrawArea 类添加一些变量和代码来实现的。

缩放由窗体上的按钮控制,当按下 Ctrl 键时,也可以通过鼠标滚轮进行控制。

平移由窗体上的“手形”按钮控制,可以通过右键单击取消。

旋转由窗体上的按钮控制——请注意,旋转会影响整个绘图。

以下是这三者结合使用的示例:

Screenshot - DrawToolsRedux-3.png

此代码的核心是 BackTrackMouse() 方法,它接收“视在”鼠标位置,并根据当前的缩放级别、平移位置和旋转将其转换为有效点。

/// <summary>
 /// Back Track the Mouse to return accurate coordinates regardless of 
 /// zoom or pan effects.
 /// Courtesy of BobPowell.net <seealso cref="http://www.bobpowell.net/backtrack.htm"/>
 /// </summary>
 /// <param name="p">Point to backtrack</param>
 /// <returns>Backtracked point</returns>
 public Point BackTrackMouse(Point p)
 {
     // Backtrack the mouse...
     Point[] pts = new Point[] { p };
     Matrix mx = new Matrix();
     mx.Translate(-this.ClientSize.Width / 2, -this.ClientSize.Height / 2, 
			MatrixOrder.Append);
     mx.Rotate(_rotation, MatrixOrder.Append);
     mx.Translate(this.ClientSize.Width / 2 + _panX, this.ClientSize.Height / 2 + 
			_panY, MatrixOrder.Append);
     mx.Scale(_zoom, _zoom, MatrixOrder.Append);
     mx.Invert();
     mx.TransformPoints(pts);
     return pts[0];
 }

此例程来自 Bob Powell 的优秀网站。通过使用 GDI+ Matrix 类,传递给此方法的鼠标点会根据当前的 PanX、PanY、Zoom 和 Rotation 值进行移动(Translate)、旋转和缩放。要记住的重要一点是,任何时候您需要确定鼠标指针在绘图中的实际位置时,都必须调用此方法。您会在程序中,尤其是在 DrawArea 类以及其他类中看到此方法的用法。此处展示了其用法的一个示例:

private void DrawArea_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
     lastPoint = BackTrackMouse(e.Location);
     if (e.Button == MouseButtons.Left)
         tools[(int)activeTool].OnMouseDown(this, e);
     else if (e.Button == MouseButtons.Right)
     {
         if (_panning == true)
             _panning = false;
         ActiveTool = DrawArea.DrawToolType.Pointer;
     }
     //OnContextMenu(e);
}

当前的缩放级别由以下简单例程控制:

private void AdjustZoom(float _amount)
{
    drawArea.Zoom += _amount;
    if (drawArea.Zoom < .1f)
        drawArea.Zoom = .1f;
    if (drawArea.Zoom > 10)
        drawArea.Zoom = 10f;
    drawArea.Invalidate();
    SetStateOfControls();
}

然后在 DrawArea.Paint() 方法中,使用缩放、平移和旋转值来修改画布的绘制方式。

private void DrawArea_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    Matrix mx = new Matrix();
    mx.Translate(-this.ClientSize.Width / 2, -this.ClientSize.Height / 2, 
			MatrixOrder.Append);
    mx.Rotate(_rotation, MatrixOrder.Append);
    mx.Translate(this.ClientSize.Width / 2 + _panX, this.ClientSize.Height / 2 + 
			_panY, MatrixOrder.Append);
    mx.Scale(_zoom, _zoom, MatrixOrder.Append);
    e.Graphics.Transform = mx;

    SolidBrush brush = new SolidBrush(Color.FromArgb(255, 255, 255));
    e.Graphics.FillRectangle(brush,
        this.ClientRectangle);
    // Draw objects on each layer, in succession so we get the correct layering.
    // Only draw layers that are visible
    if (_layers != null)
    {
        int lc = _layers.Count;
        for (int i = 0; i < lc; i++)
        {
            if(_layers[i].IsVisible == true)
                if(_layers[i].Graphics != null)
                    _layers[i].Graphics.Draw(e.Graphics);
        }
    }
    DrawNetSelection(e.Graphics);
    brush.Dispose();
}

更新 - 2007/8/25 - 单个对象旋转和错误修复

此次更新的主要进展是能够旋转单个对象——当选中一个或多个对象时,点击旋转工具将旋转这些对象,而不是整个绘图表面。

然而,有一个注意事项——旋转对象的选择框不会被旋转。如果有人能在这方面提供帮助,我将不胜感激!

此次更新还包括用户报告的一些小型错误修复——感谢所有报告的用户!

历史

  • 3/6/2007
    • 原始文章已上传至 The Code Project。
  • 3/6/2007
    • 更新以包含更多关于缩放/平移/旋转的信息。
  • 8/25/2007
    • 更新了单个对象旋转功能。
  • 9/27/2007
    • 添加了指向新源代码的缺失链接。
  • 12/23/2009
    • 添加了鼠标悬停在对象上时出现的工具提示控件。工具提示显示 RectangleEllipseImage 对象的中心坐标。对于其他对象,工具提示显示起始和结束坐标。Text 对象不显示工具提示。
      这是通过将工具提示控件添加到 ToolPointer 类来实现的。每个 Draw Object 填充 TipText 属性,并且当工具提示显示和从画布移除时,ToolPointer 类中的 MouseMove 事件会触发。此实现并不完美,因为工具提示显示时会闪烁,因此仅作为一种显示对象信息方式的示例。
      也许更好的方法是显示对象信息在一个单独的“信息窗口”中,并且仅在对象被选中时显示。
    • 详情请参阅新源代码。
  • 6/23/2010
    • 更新了项目以包含对象排序修复,该修复更正了打开文件时对象的堆叠方式。
    • 将项目更新至 Visual Studio 2010。
    • 详情请参阅新源代码。
  • 10/4/2011
    • 修正了与图层和分层相关的几个问题。
© . All rights reserved.