C++ C++ C# C# ASP.NET Security ASP.NET Security ASM ASM Скачать Скачать Поиск Поиск Хостинг Хостинг  
  Программа для работы с LPT портом...
Язык: .NET — ©Alexey...
  "ASP.NET Atlas" – AJAX в исполнении Micro...
Язык: .NET — ©legigor@mail.ru...
  "Невытесняющая" Многопоточность...
Язык: C/C++ — ©...
  Update World C++: Сборник GPL QT исходников
  Весь сайт целиком можно загрузить по ссылкам из раздела Скачать
Letyshops [lifetime]

 DrawTools / Graphics / C#

DrawTools

Introduction

DrawTools sample shows how to create a Windows Forms application for drawing graphic objects in a Windows client area using mouse and drawing tools. Drawing tools implemented in this sample are: Rectangle, Ellipse, Line, and Pencil. There are well-known techniques for creating such type of applications, like: interaction with mouse, flicker-free drawing, implementing of drawing and selection tools, objects selection, managing of objects Z-order etc. MFC developers may learn all this stuff from MFC sample DRAWCLI. DrawTools C# program reproduces some of DRAWCLI functionality and uses some design decisions from this sample.

DrawTools solution contains two projects: DrawTools Windows Forms application and DocToolkit Class Library. DrawTools implements specific application stuff, and DocToolkit contains some standard classes for file managing.

Main features of the DrawTools solution are described below.

DrawTools classes

Classes

  • DrawArea - user control which fills main application window client area. Contains instance of the GraphicsList class. Draws graphic objects, handles mouse input passing commands to GraphicsList.
  • GraphicsList - list of graphic objects. Contains ArrayList of graphic objects. Talks with each graphic object by generic way using DrawObject methods.
  • DrawObject - abstract base class for all graphic objects.
  • DrawRectangle - rectangle graphic object.
  • DrawEllipise - ellipse graphic object.
  • DrawLine - line graphic object.
  • DrawPolygon - polygon graphic object.
  • Tool - abstract base class for all drawing tools.
  • ToolPointer - pointer tool (neutral tool). Contains implementation for selection, moving, resizing of graphic objects.
  • ToolObject - abstract base class for all tools which create new graphic object.
  • ToolRectangle - rectangle tool.
  • ToolEllipse - ellipse tool.
  • ToolLine - line tool.
  • ToolPolygon - polygon tool.

DocToolkit Library

DocToolkit Library contains a set of classes which may be used for creation of document-centric Windows Forms applications. Instances of classes exported from the DocToolkit Library are kept in the main form of the DrawTools project and is used for general file-related operations.

Handling of Windows controls state at application idle time

Every Windows Forms application has a number of controls like menu items, buttons, toolbar buttons etc. Depending on current situation and user commands, these controls may have different states: enabled/disabled, checked/unchecked, visible/invisible etc. Every user action may change this state. Setting of controls' state in every message handler may be error-prone. Instead of this, it is better to manage controls' state in some function which is called after every user action. MFC has the great ON_UPDATE_COMMAND_UI feature which allows to update toolbar buttons' state at application idle time. Such a feature may be implemented also in .NET programs.

Consider the situation when user clicks the Rectangle toolbar button. This button should be checked, and previously active tool should be unchecked. Rectangle button message handler doesn't change form controls' state, it just keeps current selection in some variable. Idle message handler selects active tool and unselects inactive tool.

private void Form1_Load(object sender, System.EventArgs e)
{
    // Submit to Idle event to set controls state at idle time
    Application.Idle += new EventHandler(Application_Idle);
}

private void Application_Idle(object sender, EventArgs e)
{
    SetStateOfControls();
}

public void SetStateOfControls()
{
    // Select active tool
    tbPointer.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
    tbRectangle.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
    tbEllipse.Pushed  = (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
    tbLine.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
    tbPolygon.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);

    menuDrawPointer.Checked = 
                      (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
    menuDrawRectangle.Checked = 
                      (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
    menuDrawEllipse.Checked = 
                      (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
    menuDrawLine.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
    menuDrawPolygon.Checked = 
                      (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);

    // ...
}

// Rectangle tool is selected
private void CommandRectangle()
{
     drawArea.ActiveTool = DrawArea.DrawToolType.Rectangle;
}

Hit Test

DrawObject class has virtual HitTest function which detects whether a point belongs to graphic object:

public virtual int HitTest(Point point)
{
    return -1;
}

Derived classes use virtual PointInObject to make hit test. This function is called from HitTest. DrawRectangle class implements this function by a simple way:

protected override bool PointInObject(Point point)
{
    return rectangle.Contains(point);
    // rectangle is class member of type Rectangle
}

DrawLine implementation of this function is more complicated:

protected override bool PointInObject(Point point)
{
    GraphicsPath areaPath;
    Pen areaPen;
    Region areaRegion;

    // Create path which contains wide line
    // for easy mouse selection
    AreaPath = new GraphicsPath();
    AreaPen = new Pen(Color.Black, 7);
    AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
        // startPoint and EndPoint are class members of type Point
    AreaPath.Widen(AreaPen);

    // Create region from the path
    AreaRegion = new Region(AreaPath);

    return AreaRegion.IsVisible(point);
}

DrawPolygon function works by the same way, but AreaPath contains all lines in the polygon.

Serialization

GraphicList class implements ISerializable interface which allows to make binary serialization of the class object. DrawObject class has two virtual functions which are used for serialization:

public virtual void SaveToStream(SerializationInfo info, int orderNumber)
{
    // ...
}

public virtual void LoadFromStream(SerializationInfo info, int orderNumber)
{
  // ...
}

These functions are implemented in every derived class. Binary file has the following format:

Number of objects
Type name
Object
Type name
Object
...
Type name
Object

This allows to write generic serialization code in the GraphicList class without knowing any details about serialized objects:

private const string entryCount = "Count";
private const string entryType = "Type";


// Save list to stream
[SecurityPermissionAttribute(SecurityAction.Demand, 
                         SerializationFormatter=true)]
public virtual void GetObjectData(SerializationInfo info, 
                                     StreamingContext context)
{
    // number of objects
    info.AddValue(entryCount, graphicsList.Count);

    int i = 0;

    foreach ( DrawObject o in graphicsList )
    {
        // object type
        info.AddValue(
            String.Format(CultureInfo.InvariantCulture,
                "{0}{1}",
                entryType, i),
            o.GetType().FullName);

        // object itself
        o.SaveToStream(info, i);

        i++;
    }
}

// Load from stream
protected GraphicsList(SerializationInfo info, StreamingContext context)
{
    graphicsList = new ArrayList();

    // number of objects
    int n = info.GetInt32(entryCount);
    string typeName;
    object drawObject;

    for ( int i = 0; i < n; i++ )
    {
        // object type
        typeName = info.GetString(
            String.Format(CultureInfo.InvariantCulture,
                "{0}{1}",
            entryType, i));

        // create object by type name using Reflection
        drawObject = Assembly.GetExecutingAssembly().CreateInstance(
            typeName);

        // fill object from stream
        ((DrawObject)drawObject).LoadFromStream(info, i);

        graphicsList.Add(drawObject);
    }

}

C# 2005 Version

C# 2005 DrawTools version is built and tested with Microsoft Visual C# 2005 Beta 1 Express Edition and Microsoft .NET Framework 2.0 Beta. It uses most of the new features introduced in this version.

New Windows Forms Controls

MenuStrip and ToolStrip controls are used instead of MainMenu and ToolBar.

Generics

List<> class from System.Collections.Generic is used instead of ArrayList class from System.Collections namespace.

C# 2003 code:

using System.Collections;
...

    private ArrayList graphicsList;
    ...

        graphicsList = new ArrayList();
        ...

        public DrawObject this [int index]
        {
            get
            {
                return ((DrawObject)graphicsList[index]);
            }
        }

C# 2005 code:

using System.Collections.Generic;
...

    using DrawList = List<DrawObject>;
    ...

        private DrawList graphicsList;
        ...

        graphicsList = new DrawList();

        public DrawObject this [int index]
        {
            get
            {
                return graphicsList[index];
            }
        }

Creating numerable collections using yield keyword

Using new yield keyword, it is easy to expose numerable collections for a client. All we need to do is to enumerate all items we want to expose and execute yield return for each of them.

The following code fragment creates collection of selected graphic objects:

public IEnumerable<DrawObject> Selection
{
    get
    {
        foreach (DrawObject o in graphicsList)
        {
            if (o.Selected)
            {
                yield return o;
            }
        }
    }
}

Client code:

// Move all selected objects
foreach (DrawObject o in drawArea.GraphicsList.Selection)
{
    o.Move(dx, dy);
}

Anonymous methods

I found this feature useful to make inline event handling.

C# 2003 code:

Application.Idle += new EventHandler(Application_Idle);
...

private void Application_Idle(object sender, EventArgs e)
{
   SetStateOfControls();
}

C# 2005 code:

Application.Idle += delegate(object o, EventArgs a)
{
    SetStateOfControls();
};

Simplified event subscription

C# 2003 code:

ownerForm.Closing += new CancelEventHandler(OnClosing);

C# 2005 code:

ownerForm.Closing += OnClosing;

Nullable types

Nullable types are value types which have special value null. This makes them similar to database fields. To declare nullable type, add ? sign to any value type.

Properties dialog in the DrawTools program is a good place where nullable types may be used. It is shown for one or more selected graphic objects. If all selected objects have the same color, dialog shows this color. If objects have different color, dialog shows special undefined value. C# 2003 code solves this problem using boolean flags:

private Color color;
private bool colorDefined;

C# 2005 code uses nullable type:

private Color? color;

Instead of testing boolean flag: if ( colorDefined ) program tests whether Color? value is not null: if ( color.HasValue ).




Letyshops [lifetime]