Search Forum
(57415 Postings)
Search Site/Articles

Archived Articles
712 Articles

C# Books
C# Consultants
What Is C#?
Download Compiler
Code Archive
Archived Articles
Advertise
Contribute
C# Jobs
Beginners Tutorial
C# Contractors
C# Consulting
Links
C# Manual
Contact Us
Legal

GoDiagram for .NET from Northwoods Software www.nwoods.com


              
Printable Version

A Primitive Window System Using GDI+ and C#
By Narendra Maharaj

Using low-level 2D GDI+ elements, a C# application is built to mimic the movement of a windows form control.

A Tiny Introduction To GDI+
The basic purpose of the Graphical Device Interface (GDI) is to abstract away the video and print sub-systems. It was based on the Windows C-style API, GDI+ adds a layer between GDI and your programs. The mechanism that implements this abstraction is the Device Context in GDI, and is encapsulated in the GDI+ Graphics class. The client area of an application (window) consists of a set of discrete points, a point has a x coordinate and a y coordinate along with a color. Any point represented (x,y) is measured relative to the top-left corner of the window, the point (0,0).
Default Window Coordinates.
There are several basic drawing components that will be used in the program: They are defined by the .NET classes, Pen, SolidBrush, Font, Point, Rectangle, Size, GraphicsPath. (Note: this is not an exhaustive list). Worthy of special mention is the GraphicsPath class, it consists of an array of points that defines a closed polygon. The following GrpahicsPath describes a triangle:
      
      Point[] ptArr = new Point[] {new Point(10,10), new Point(20,10), new Point(10,20)};
      GraphicsPath gp = new GraphicsPath();
      gp.AddPolygon(ptArr);
	    
From this GraphicsPath, a region can be easily declared: Region r = new Region(gp);
Outline Of the Program
We begin by creating a standard Windows-Application with a standard Form class, since a device context and client area is needed on which to draw, simple test applications should put this.Show(); in their constructor before any drawing is done. We won't use this method here since our "window" is launched from the menu File -> New Win after the application starts running. We will also add to the standard Form1 class, event handlers for the OnPaint(), MouseDown(), MouseUp(), and MouseMove() events:
      
    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
      if (w != null)
        w.OnPaint(this, e);
      base.OnPaint(e);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
      base.OnMouseDown(e);
      if (w != null)
        w.OnMouseDown(this, e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
      base.OnMouseUp(e);
      if (w != null)
        w.OnMouseUp(this, e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);
      if (w != null)
        w.OnMouseMove(this, e);
    }
	    
Since Form1 derives from the Form class: public class Form1 : System.Windows.Forms.Form it already has event handlers, we simply override them and call our own methods. Note it is very important to invoke the base class methods, (base.OnPaint(e); etc,) so Windows can also respond to the event. All of the primitive window construction, destruction and behavior is implemented in the separate win class in the module win.cs. The win class constructs the window in several pieces: A top rectangle mTopRect used the move the window around the Form1 client area, its own client rectangle mClientRect used for writing stuff, a large X on the top rectangle mDelXRect used for destroying the window, and a traingle shaped polygon mgpResizeTab used for resizing the window. The various movements are shown by the three figures below. First, the window after construction (yellow), second, the window while moving (red) and third the window while being re-sized (blue).
In order to move the window from one place to another we must do a few things. Capture where the mouse down event occured, if it was on the top rectangle, prepare for a move. If it was on the resize tab prepare for a resize, an destroy the window if the large X is clicked. It is the mouse-move event that actually trigers most of the work. In order to give a visual feedback to the user the window is continously destroyed and re-created on the mouse move event. The mouse up event simply re-draws the window and the program goes dormant until the next event. The drawing pen is changed to red for moving and to blue for resizing.
The key to all of the movements is a bit of high-school geometry. Recall that if you have two points on the Cartesean Plane (x1, y1) and (x2, y2) the linear distance between them is the pair of numbers x2 - x1 and y2 - y1. This is NOT a point, it is the distance between (x1, y1) and (x2, y2). For convenience and ease of use it is represented by a point in the program. The great thing about this is the sign (positive or negative) takes care of itself. So all we have to do in order to move the window is to hold some point of the old window position and calculate the distance the mouse moved relative to the old point based on the distance formula just described.
      
    // p1 = starting point,  p2 = ending point of the transformation
    private Point _delta(Point p1, Point p2)
    {
      return (new Point(p2.X - p1.X, p2.Y - p1.Y));
    }
	    
Finally while the window is being moved the (x,y) coordinates of its top-left corner is displayed in the client area, and when a re-size is occuring the width and height if the window is diaplayed in the client area.
Although this is just a demonstration program, the possibilities are quite interesting. You are no longer bound by a rectangular shape for your control. Any irregular shape is now possible. I have not limited the resizing of the window, for example try resizing past the top-left point and window takes on a bizzare shape!

Download gdi1.zip