WinForms FAQ - Drawing Tips

Find answers for the most frequently asked questions
Expand All Collapse All

Handle the Paint event for your control or form.

  private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
  {
    Graphics g = e.Graphics;
    Pen pen = new Pen(Color.White, 2);
    SolidBrush redBrush = new SolidBrush(Color.Red);

    g.DrawEllipse(pen, 100,150,100,100);
    g.DrawString('Circle', this.Font, redBrush, 80, 150);

    g.FillRectangle(redBrush, 140, 35, 20, 40);
    g.DrawString('Rectangle', this.Font, redBrush, 80, 50);

    g.DrawLine(pen, 114, 110, 150, 110);
    g.DrawString('Line', this.Font, redBrush, 80, 104);
  }

Permalink
  private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
  {
    Graphics g = e.Graphics;

    g.TranslateTransform(100.0f, 100.0f);
    g.RotateTransform(-90.0f);
    g.DrawString('Vertical Text', Font, Brushes.Blue, 0.0f, 0.0f);
    g.ResetTransform();

    g.TranslateTransform(100.0f, 100.0f);
    g.RotateTransform(-45.0f);
    g.DrawString('Slanted Text', new Font(Font, FontStyle.Bold), Brushes.Red, 0.0f, 0.0f);
    g.ResetTransform();

  }

Permalink
AutoScrollingMinSize is setup in device coordinates. If you are using a world coordinate
system other than pixels this means that you have to translate between world and device
coordinates before you set these value. You can use the Graphics.TransformPoints API to
do this as shown below.
g.TransformPoints(CoordinateSpace.Device, CoordinateSpace.World, ptScrollSize);
All this does is go from the GraphicsUnit that you are using to pixels. For example if your
GraphicsUnit is Inch, then with a Graphics object that represents a monitor screen you will
have translated values that come to about 100 pixels for every logical inch. Using this API
ensures that you are insulated from the underlying device. The values will be much higher
for printers.
Another important point to remember is that your Transform in the painting code (please
refer earlier FAQ in this section on AutoScrolling implementation) will have to be in
logical coordinates and will hence have to translate between logical and device coordinates.
protected void OnHandlePaint(object sender, PaintEventArgs args)
{
  Graphics g = args.Graphics;
  g.PageUnit = GraphicsUnit.Inch;

  // this will always be in pixels
  Point[] ptAutoScrollPos = new Point[]{this.AutoScrollPosition};
        
  // We have to convert the pixel value (device) to inches (logical..world)
  g.TransformPoints(CoordinateSpace.World, CoordinateSpace.Device, ptAutoScrollPos);
        
  // set up a simple translation so that we draw with respect to the doc bounds
  // and not the physical bounds
  g.TranslateTransform(ptAutoScrollPos[0].X, ptAutoScrollPos[0].Y); 

  g.DrawEllipse(_pen, _rectEllipse);
}
Play around with the sample and all should be clear. When you run the sample nothing will
be seen on the screen. Scroll around. Remember the logical units are in inches. You will
be looking at a huge ellipse!

Source: Syncfusion Staff

Permalink

Check out the Painting techniques using Windows Forms by Fred Balsigerat gotnetdot.com. It is a good basic discussion of how to get the best performance from Windows Forms drawing. His hints include leveraging the power of the .Net Framework by using the proper controls and control styles as well as consolidating painting code in the OnPaint and OnPaintBackground methods.

Permalink

You can do so using the native GetGuiResources api. Here is a sample:


		/// 
		/// uiFlags: 0 - Count of GDI objects
		/// uiFlags: 1 - Count of USER objects
		/// - Win32 GDI objects (pens, brushes, fonts, palettes, regions, device contexts, bitmap headers) 
		/// - Win32 USER objects:
		///	  - WIN32 resources (accelerator tables, bitmap resources, dialog box templates, font resources, menu resources, raw data resources, string table entries, message table entries, cursors/icons) 
		///   - Other USER objects (windows, menus) 
		/// 
		[DllImport('User32')]
		extern public static int GetGuiResources(IntPtr hProcess, int uiFlags);

		public static int GetGuiResourcesGDICount()
		{
			return GetGuiResources(Process.GetCurrentProcess().Handle, 0);
		}

		public static int GetGuiResourcesUserCount()
		{
			return GetGuiResources(Process.GetCurrentProcess().Handle, 1);
		}

		’ uiFlags: 0 - Count of GDI objects
		’ uiFlags: 1 - Count of USER objects
		’ - Win32 GDI objects (pens, brushes, fonts, palettes, regions, device contexts, bitmap headers) 
		’ - Win32 USER objects:
		’	  - WIN32 resources (accelerator tables, bitmap resources, dialog box templates, font resources, menu resources, raw data resources, string table entries, message table entries, cursors/icons) 
		’  - Other USER objects (windows, menus) 
		’ 
		 _ 
		extern Public static Integer GetGuiResources(IntPtr hProcess, Integer uiFlags)
 
		Public Shared Function GetGuiResourcesGDICount() As Integer
			Return GetGuiResources(Process.GetCurrentProcess().Handle,0)
		End Function
 
		Public Shared Function GetGuiResourcesUserCount() As Integer
			Return GetGuiResources(Process.GetCurrentProcess().Handle,1)
		End Function
Permalink

Here is some code that will do it.


[C#]
	internal class NativeMethods
	{
		[DllImport('user32.dll')]
		public extern static IntPtr GetDesktopWindow();

		[System.Runtime.InteropServices.DllImport('user32.dll')]
		public static extern IntPtr GetWindowDC(IntPtr hwnd);

		[System.Runtime.InteropServices.DllImport('gdi32.dll')]
		public static extern UInt64 BitBlt
			(IntPtr hDestDC,
			int x,
			int y,
			int nWidth,
			int nHeight,
			IntPtr hSrcDC,
			int xSrc,
			int ySrc,
			System.Int32 dwRop);
	}

		// Save the screen capture into a jpg
		public void SaveScreen()
		{
			Image myImage = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
				Screen.PrimaryScreen.Bounds.Height);
			Graphics gr1 = Graphics.FromImage(myImage);
			IntPtr dc1 = gr1.GetHdc();
			IntPtr dc2 = NativeMethods.GetWindowDC(NativeMethods.GetDesktopWindow());
			NativeMethods.BitBlt(dc1, 0, 0, Screen.PrimaryScreen.Bounds.Width,
				Screen.PrimaryScreen.Bounds.Height, dc2, 0, 0, 13369376);
			gr1.ReleaseHdc(dc1);
			myImage.Save('screenshot.jpg', ImageFormat.Jpeg);
		}

[VB.Net]
Friend Class NativeMethods
      
        _
      Public Shared Function GetDesktopWindow() As IntPtr
        End Function

         _
        Public Shared Function GetWindowDC(ByVal hwnd As IntPtr) As IntPtr
        End Function

         _
        Public Shared Function BitBlt(ByVal hDestDC As IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As IntPtr, ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As System.Int32) As UInt64
        End Function
End Class ’NativeMethods

  ’Save the screen capture into a jpg
      Private Sub SaveScreen()
         Dim myImage = New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
         Dim gr1 As Graphics = Graphics.FromImage(myImage)
         Dim dc1 As IntPtr = gr1.GetHdc()
         Dim dc2 As IntPtr = NativeMethods.GetWindowDC(NativeMethods.GetDesktopWindow())
         NativeMethods.BitBlt(dc1, 0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, dc2, 0, 0, 13369376)
         gr1.ReleaseHdc(dc1)
         myImage.Save('screenshot.jpg', ImageFormat.Jpeg)
      End Sub ’SaveScreen
Permalink

Sometimes the framework will throw an exception if you try to set the bg color to be transparent. This is because the Control doesn’t support transparent colors. To work around this you should call this method from within the Control:


[C#]
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);


[VB.Net]
Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)

Depending on the Control, you might also have to perform some custom drawing, then.

Permalink

To draw on any hwnd, you need to get a Graphics object from that hwnd. Once you have the Graphics object, you can then use its methods to draw. Of course, since this drawing is not done in the Paint handler, it will not be automatically refreshed when the control is redrawn for any reason.

	Graphics g = Graphics.FromHwnd(this.Handle);
	SolidBrush brush = new SolidBrush(Color.Red);
	Rectangle rectangle = new Rectangle(25, 25, 100, 100);
	g.FillRectangle(brush, rectangle);
Permalink

The Window.Forms framework offers support for double buffering to avoid flickers through ControlStyles. Double buffering is a technique that attempts to reduce flicker by doing all the drawing operations on an off-screen canvas, and then exposing this canvas all at once.

To turn on a control’s double buffering, you need to set three styles.

	public UserPictureBox()  //derived from System.Windows.Forms.Control
	{
		// This call is required by the Windows.Forms Form Designer.
		InitializeComponent();

		// Activates double buffering
		SetStyle(ControlStyles.UserPaint, true);
		SetStyle(ControlStyles.AllPaintingInWmPaint, true);
		SetStyle(ControlStyles.DoubleBuffer, true);

		// TODO: Add any initialization after the InitForm call
	}
Permalink

AutoScrolling does not allow you to dynamically control the scrolling interval. If you are drawing a complex control such as grid then you want to be able to scroll based on the current row height, column width etc. With AutoScrolling you cannot do this. You have to implement Scrolling yourself in such cases.

Autoscrolling is also not very useful if you want multiple views to share a scrollbar. The most common place where you see this is with a workbook. There is no direct way in Winforms to hook up your own scrollbars with the AutoScrolling implementation.

Permalink

Share with

Couldn't find the FAQs you're looking for?

Please submit your question and answer.