Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Special 'Graphics' Objects to Draw Anywhere on Your Window

0.00/5 (No votes)
15 Aug 2020 1  
Creating special 'Graphics' objects to draw anywhere on your window, including non-client area
In this article, you will learn how to create special Graphics objects in C# that you can use to draw anywhere on your window.

Preface

About four months ago, when I was working on a project, I wanted to add some buttons on the title bar of windows, near the standard Minimize/Maximize/Close buttons. To do that, I had to draw on the title bar, that is, the non-client area of the window. Although my work was useless on that project and I just wasted my time, at least I am now writing this article about what I have done. :-)

In this article, I'll show you how to create special Graphics objects in C# that you can use to draw anywhere on your window, and in my next article, I will explain how to add buttons on the title bar.

Background

Well, as you already know, in Microsoft .NET, you need a Graphics object whenever you want to draw something. The Graphics object must belong to the object you want to draw on. Usually, you have two options to obtain a Graphics for a control (Form or whatever derived from System.Windows.Forms.Control class). First, you can handle the Paint message of the control, and second, you can call the CreateGraphics method of the object. In the former case, your drawing will be always on the object! This is because the Paint event is always raised by the .NET subsystem (wrapped on win32 WM_PAINT message) whenever any portion of your window needs to be drawn (i.e. is invalidated - in terms of C++ win32 programming). In the latter case, you draw on your window whenever you want, not whenever your OS wants. But the drawback is that your painting is there, until something passes over your window!

I think I am going too far from the subject. For more information about painting mechanism on .NET and Windows, refer to the MSDN Library (Pain event, CreateGraphics method, and Painting and Drawing subsection of Windows GDI in Windows SDK Documentation).

Whichever of the two methods you use to obtain the graphics object, you can only draw on the working area of the window (called client area), that is, anywhere except the border and title bar of your window.

In this article, I am designing a class called WindowGraphics, that creates a Graphics object for your entire window, that contains client and non-client area of the window; and by using that, you can draw wherever you want on your window. You can also use it to draw on portions of some controls that you had no control over before. For example, any place on a TextBox (with usual Graphics objects, you cannot draw on borders of a TextBox).
The class also handles a problem on normal Graphics objects when the RightToLeft and RightToLeftLayout properties of your form are both true. Details of this problem are explained later on.

Using the Class

After downloading the source files attached to the article, you can easily start using the WindowGraphics class by including the WindowGraphics.cs file into your project. It is very simple. You start by creating an instance of the WindowGraphics class, passing to the constructor the control you want to draw on.

// ...

// create a Graphics for entire form

WindowGraphics wg = new WindowGraphics( this ); // I assume that this line 
					  // is put somewhere in your form class,
					  // so the 'this' keyword refers 
                      // to an instance of the Form class.

Then, simply use the Graphics property of the newly created object to do your drawing:

//.....
wg.Graphics.DrawLine( Pens.Blue, 0, 0, 100, 100 );

//.....

// or if you have to call many drawing functions, here is the way to reduce
// your typing. This is what I always do...
Graphics g = wg.Graphics;
g.DrawString( "I am on the title bar!", 
	new Font( "Tahoma", 10, FontStyle.Bold ), Brushes.Black, 0, 4 );
g.FillEllipse( Brushes.Black, this.Width - 40, this.Height - 40, 80, 80 );

// .... other drawing commands...

Finally, call the Dispose method of the object to free any resources it is using. Because this class uses unmanaged resources, it is highly recommended that you do not forget to call the Dispose method!

....
wg.Dispose();
....

You can also use the using block of C# that calls the Dispose method automatically for you. It is the recommended way:

using ( WindowGraphics wg = new WindowGraphics( this ) )
{
	Graphics g = wg.Graphics;

	// ...

	// do your drawing with 'g'
	// ...
}

Draw anywhere on your window

Also note that, when you work with this Graphics, the origin is the upper left corner of the whole form rectangle, not its client area.

The sample project included in the source code is created using Visual Studio 2008, but the project file should also open in Visual Studio 2005 with no problem because it is configured to use .NET Framework 2.0.

If you are not interested in details, you can leave the article here, and use the class in your project. But if you are, continue reading.

How Does It Work?

Creating this kind of Graphics object is not a trivial task in .NET. You have to use native methods and win32 calls.

As some of you know, the easiest way to do this is to call the GetWindowDC function, passing the handle of your window. The GetWindowDC function belongs to Windows user32 library. You must import this function first:

using System;
using System.Runtime.InteropServices;


//...

[DllImport( "user32" )]
private static extern IntPtr GetWindowDC( IntPtr hwnd );

// you also need ReleaseDC
[DllImport( "user32" )]
private static extern IntPtr ReleaseDC( IntPtr hwnd, IntPtr hdc );

Then, call the method to create a DC* for the entire window, and then create a Graphics object from the DC:

* DC: Device Context - the objects that in the GDI world are used to draw things. Somehow equivalent to Graphics in the GDI+.

IntPtr hdc = GetWindowDC( this.Handle );
Graphics g = Graphics.FromHdc( hdc );

// ....
// do your drawing
// ....

// free the resources
g.Dispose();
ReleaseDC( this.Handle, hdc );

I had used this way in my project at first. Now the problem...

The Problem of RightToLeftLayout

If you will never use right-to-left forms, you can skip this section, but if you make software for a right to left language (as in my case), or are interested in the subject, read this section.

Although this problem is not related to this kind of Graphics, I am explaining it because I encountered it here.

You can see that when you set the RightToLeftLayout and RightToLeft properties both to true, the whole coordinate system at the top level gets mirrored. The origin is no longer the upper left corner of the client area of the form; it is upper right corner instead. I said at the top level because it occurs only in the form itself, not for the child controls. For example, when you put a Panel in your form, inside the Panel the origin is still upper left corner of the panel.

Well, you may guess that when this happens, any Graphics objects you construct for the form must be mirrored. Yes, that's true, but not completely! Try this:

  • Create a new Windows Forms Application in Visual Studio.
  • Select the From and set the RightToLeft and RightToLeftLayout properties to true.
  • Handle the Paint event, by double-clicking the Paint event in the Events tab of the Properties panel.
  • Draw a simple line:
    private void Form1_Paint( object sender, PaintEventArgs e )
    {
        e.Graphics.DrawLine( Pens.Blue, 0,0, 100, 100 );
    }

By running the project, you see that the line is started from the upper right corner of the form, as expected.

RightToLeft Form - Really right to left!

Now move another window over the Form. What do you see? The line will be drawn from the upper left corner, as if the Graphics are not right to left.

RightToLeft Form - Sometimes left to right!

Now when the form is behind another window on your desktop, click its icon on the taskbar to bring it to front (or minimize the form, and restore it). The line again is drawn from the upper right corner!

I have done a lot of investigations and tried whatever you thought of to find the reason, and I didn't find anything! It may be a bug in the .NET Framework or Windows. I am using the latest version of .NET Framework and Windows XP at the time of writing this article (Service Pack 3 of XP, and .NET 3.5 SP1 which includes .NET 2 Service Pack 2), and the problem is still there. It happens even in Windows Vista. So I decided to do something else. Here is my solution.

Again, How Does It Work?

I tried to create the Graphics from something that never is right to left – the whole Desktop.

First, I get the DC for the entire screen, and then do the required transformations and clippings to fit the DC on the visible region of the window. Here is the step-by-step explanation:

IntPtr hdc = GetDC( IntPtr.Zero ); 	    // get DC for the entire screen

By passing zero to the GetDC function as the window handle, we get a DC for the entire screen.

IntPtr hrgn = GetVisibleRgn( hWnd );	// obtain visible clipping region for the window
SelectClipRgn( hdc, hrgn ); 	        // clip the DC with the region

We must retrieve the clipping region of the form, and clip the DC with that. Without doing this, we may draw on other windows - places that we do not own.
The GetVisibleRgn method is a private method of our class. It returns a handle to the region that the window is currently clipped to. I'll explain the method later. The SelectClipRgn is a Win32 API function. It clips the given DC to the given region.

Now the origin of the DC is upper left corner of the screen, but we want it to be upper left corner of our form. So we must move the origin:

Rect rect = new Rect();
GetWindowRect( hWnd, rect );

// move the origin from upper left corner of the screen,
// to the upper left corner of the form

SetWindowOrgEx( hdc, -rect.left, -rect.top, IntPtr.Zero );

Finally, create your Graphics from the DC:

Graphics graphics = Graphics.FromHdc( hdc );

You're done. Now the GetVisibleRgn method:

private IntPtr GetVisibleRgn( IntPtr hWnd )
{
    IntPtr hrgn, hdc;
    hrgn = CreateRectRgn( 0, 0, 0, 0 );
    hdc = GetWindowDC( hWnd );
    int res = GetRandomRgn( hdc, hrgn, 4 ); 	// the value of SYSRGN is 4. 
					                            // Refer to Windows SDK Documentation.

    ReleaseDC( hWnd, hdc );
    return hrgn;
}

We create an empty region, get the DC of the window, pass the DC to a special function to retrieve the visible region associated with the window, and release the DC. That special function is GetRandomRgn. I don't know what the philosophy behind the name of the function is, but I think at first, it supposed to do a lot more than just retrieving the clipping region of the window. By the way, it works for us.

The final thing is wrapping them all up in a reusable class. You can see the final class in the code attached to the article.

Why I Wrote this Article, and What You Can Learn From It?

The first reason for me was that I wanted to write something! And I found this class simple enough and well designed. I hope that you learn some tips about Object Oriented design, in addition to a few things about Windows GDI.

This is my first article on CodeProject. It has been a long time since I wanted to write about my projects (almost a few years!). I think I have a lot of good experiences to share with others. This is my first step, and I hope that it will continue well enough. I don't know when I can write my next article, but I think it is about buttons on the title bar.

If you liked the article, please vote for it, and leave some comments. So I can understand the good points, and correct my mistakes.

History

  • 18th September, 2008 - Initial issue
  • 19th September, 2008 - Fixed a very small bug! Source code updated:
    • Line 79 of the WindowGraphics.cs, hWnd changed to IntPtr.Zero.
      This is because when retrieving the DC, we pass IntPtr.Zero to GetWindowDC to get the DC for the entire screen, and when releasing the DC we should also pass IntPtr.Zero.
  • 21st September, 2008 - In CreateGraphics, GetWindowDC is replaced with GetDC, to work with Windows Vista

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here