Wednesday, October 7, 2009

Creating Transparent Controls in .NET Compact Framework

Recently I was working on improving the user interface of our windows mobile application - UnitConverter. It had plain solid background. I wanted to add background image to it. I found this video tutorial on how to display background image on a form. However, that tutorial told us if we need to have transparent controls on top of the image, we have to write our own user controls. That’s how I started researching on how to create transparent controls. I found an article by Per Ola Sæther which laid the foundation of my solution.

The basic ideas to create a transparent control is to override the OnPaintBackgound method of the control so it calls the parent form’s OnPaintBackgound to draw the background and then draw content of the control on top of it.

In that article, the author showed how to create a transparent label control. But the code can be simplified. I also did some refactoring work so it extends to not just label control.

First I created the same interface introduced in that article.

public interface IPaintControl
{
// have the background painted
void InvokePaintBackground(PaintEventArgs e);
}



 


Then I created a base form so I don’t have to write the same code for each form.



public class CcForm : Form, IPaintControl
{
public virtual void InvokePaintBackground(PaintEventArgs e)
{
OnPaintBackground(e);
}
}



 


A base control is created so we not only can have transparent label, but also other kinds of controls, such as radio button and checkbox. It has a property called “TransparentBackground”. If you don’t want your control to be transparent for some reason, you can change this property. In the “OnPaintBackground” method, it calls its parent’s “InvokePaintBackground” to draw the background.



public class CcTransparentControl : Control
{
private bool _transparentBackgound = true;
public bool TransparentBackground
{
get
{
return _transparentBackgound;
}
set
{
_transparentBackgound = value;
}
}

protected override void OnPaintBackground(PaintEventArgs e)
{
if (_transparentBackgound)
{
IPaintControl parent = (IPaintControl)Parent;
if (parent != null)
{
parent.InvokePaintBackground(e);
}
}
else base.OnPaintBackground(e);
}
}



 


Now we can create the transparent label control using the following code.



public class CcTransparentLabel : CcTransparentControl
{
private ContentAlignment textAlign = ContentAlignment.TopLeft;
public ContentAlignment TextAlign
{
get
{
return textAlign;
}
set
{
textAlign = value;
}
}

public CcTransparentLabel()
{

}

protected override void OnPaint(PaintEventArgs e)
{
Graphics gfx = e.Graphics;
if (this.TextAlign == ContentAlignment.TopLeft)
{
gfx.DrawString(this.Text, this.Font,
new SolidBrush(this.ForeColor), ClientRectangle);
}
else if (this.TextAlign == ContentAlignment.TopCenter)
{
SizeF size = gfx.MeasureString(this.Text, this.Font);
int left = this.Width / 2 - (int)size.Width / 2;
var rect = new Rectangle(ClientRectangle.Left + left,
ClientRectangle.Top, (int)size.Width,
ClientRectangle.Height);
gfx.DrawString(this.Text, this.Font,
new SolidBrush(this.ForeColor), rect);
}
else if (this.TextAlign == ContentAlignment.TopRight)
{
SizeF size = gfx.MeasureString(this.Text, this.Font);
int left = this.Width - (int)size.Width + this.Left;
var rect = new Rectangle(ClientRectangle.Left + left,
ClientRectangle.Top, (int)size.Width,
ClientRectangle.Height);
gfx.DrawString(this.Text, this.Font,
new SolidBrush(this.ForeColor), rect);
}
}

}



 


In our form, we change the form to inherit from CcForm and drag/drop a CcTransparentLabel onto it.



public partial class FormWithSolidColorBackground : CcForm



 


Here is the screen shot. As you can see, the label has transparent background now.


image


However, when I tried to add image background with gradient color, my label doesn’t look right anymore.


image


To understand the issue, we need to first take a look of how we display background image on a form.



public partial class FormWithImageBackground : CcForm
{
private Rectangle _backgroundRect;
private Bitmap _background;
private string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase.ToString());

public FormWithImageBackground()
{
InitializeComponent();

_background = new Bitmap(currentPath + @"\ImageBackground.jpg");
_backgroundRect = new Rectangle(0, 0, _background.Width, _background.Height);
}

protected override void OnPaintBackground(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(_background, this.ClientRectangle, _backgroundRect, GraphicsUnit.Pixel);
}
}



What happened is when the OnPaintBackground was called by the transparent label control, it re-draws the image inside of the label control. So it shows the top-left corner of the image on the label’s background. To fix this issue, I pass in the location of the label control to the form and use it to re-draw the image. For example, if the label is located at (10, 20), the image background would be displayed at (-10, –20). This may not be the best solution, but it worked. Here is revised interface and controls.



public interface IPaintControl
{
// have the background painted
void InvokePaintBackground(PaintEventArgs e, Point location);
}



 



public class CcForm : Form, IPaintControl
{
public virtual void InvokePaintBackground(PaintEventArgs e, Point location)
{
OnPaintBackground(e);
}
}



 



public class CcTransparentControl : Control
{
....
protected override void OnPaintBackground(PaintEventArgs e)
{
if (_transparentBackgound)
{
IPaintControl parent = (IPaintControl)Parent;
if (parent != null)
{
parent.InvokePaintBackground(e, this.Location);
}
}
else base.OnPaintBackground(e);
}
}



 


In the form, I then override the InvokePaintBackground method to draw the image at the desired location.



public override void InvokePaintBackground(System.Windows.Forms.PaintEventArgs e, System.Drawing.Point location)
{
Graphics g = e.Graphics;
Rectangle destRect = new Rectangle(-1 * location.X, -1 * location.Y, ClientRectangle.Width, ClientRectangle.Height);
g.DrawImage(_background, destRect, _backgroundRect, GraphicsUnit.Pixel);
}



 


Now we have a transparent label over image background.



image


I also created transparent radio button and checkbox controls. These are the controls commonly used in a form. So is the Panel control. The following example showed the transparent radio button and checkbox on a transparent panel. You can download the complete source code from here.


 image




 


 
















No comments:

Post a Comment