Sunday, November 8, 2009

Please note I have decided to move my blog to a new server:
www.code-dragon.com

Thursday, October 29, 2009

Text Wrap Method

Although Graphics.DrawString() will wrap text if you give it a rectangle, it wont tell you the resulting height once its done. This makes it really difficult to resize the texts container or add a scrollbar.

My solution to this problem is a simple text wrap method, well two methods which work really well on a string which is all the same font. So far I haven't needed to work with any display strings more complicated than that.

The text wrap is split into two functions, the first takes a string and splits it on spaces. Then it creates lines from the string that fit within the given width restriction. The second simply wraps this and splits the original string by the new line character, passing in the resulting strings and adding the new results to its list of lines.


// Using the given graphics object and width restriction the provided
// string is split into lines

private List SplitLines(Graphics g, string text, int pWidth)
{
   string[] lines = text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
   List items = new List();
   foreach (string line in lines)
   {
      items.AddRange(this.SplitWords(g, line, pWidth, ref y));
   }
   return items;
}


The above method is the easy one that just puts the results together. Bellow is the method that will workout which line each word in the string should be on.


// This will split the given string into lines
// no longer then the provided width

private List SplitWords(Graphics g, string text, int pWidth)
{
   string[] items = text.Split(' ');
   float width = 0;
   string renderString = "";
   List renderList = new List();
   for (int i = 0; i < items.Length; i++)
   {
      SizeF stringLength = g.MeasureString(items[i], this.Font);
      if (width + stringLength.Width < pWidth)
      {
         // not too long yet
         if (renderString != "")
         renderString += " ";
         renderString += items[i];
         width += stringLength.Width;
      }
      else
      {
         // this will make it too long, render the current string
         // and start a new line

         renderList.Add(renderString);
         renderString = items[i];
         width = stringLength.Width;
      }
   }
   if (renderString != "")
   {
      // add any left over to the list
      renderList.Add(renderString);
   }
   return renderList;
}


Then to draw you just need to do a loop in the paint method and draw each line. When combined with the flicker free code posted before, this is really useful for large text display.

Later I will post the full code for a ScrollableLabel control which I find much more useful then a label, and better looking then a textbox.

Friday, October 23, 2009

100% Flicker Free Rendering in C#

There are a few different ways to do this, and here is another. What this method does is manually buffer to a bitmap first and then draw this to the screen. The trick that I haven't seen used is to pass the bitmap to the controls paint background method so all drawing is under your control.

Here are the sections of code you'll need to implement this. Most of my classes that use this just inherit from Control


// First we Declare the buffer
private Bitmap buffer = null;



// The buffer must be resized when the form changes size, this also
// initialises the buffer

protected override void OnResize(EventArgs e)
{
    base.OnResize(e);
   if (this.Width > 0 && this.Height > 0)
   {
       if (this.buffer != null)
       this.buffer.Dispose();
       this.buffer = new Bitmap(this.Width, this.Height);
   }
   // make sure it redraws on resize
   this.Invalidate();
}



// On the paint background we give it our buffer so it doesn't
// draw directly to the screen

protected override void OnPaintBackground(PaintEventArgs e)
{
   Graphics g = Graphics.FromImage(buffer);
   PaintEventArgs ex = new PaintEventArgs(g, e.ClipRectangle);
   base.OnPaintBackground(ex);
}



// Do your drawing on the buffer, then draw the buffer to the screen
protected override void OnPaint(PaintEventArgs e)
{
   Graphics gB = Graphics.FromImage(buffer);

    // do any drawing you need to here to gB

    // the clipping on the provided graphics object can create
   // some refresh artefacts so I always use a new graphics object

   Graphics gM = this.CreateGraphics();
   gM.DrawImage(buffer, new Point(0, 0));
   gM.Dispose();
   gB.Dispose();
}


And that's it, now you can draw some very complicated controls with absolutely no flicker :)