|
Introduction
Windows has long had the ability to specify a region or transparency key allowing you to define an arbitrary Window border. This is often used with a background image to define an image outline as a Window frame. However, this border is composited with the desktop as a one bit mask giving you a pixelated boundary. It is especially unattractive with curvilinear borders which really need antialiasing and per pixel compositing. Aside from the unsightliness, it's not easy to define the region and/or transparency key to achieve a complex image based Window frame.
This is a Windows Forms control that works with Win32 APIs and without WPF. The control allows you to layout a 32 bit image with an alpha channel in the Forms designer and arrange additional controls within user specified areas of the image. At runtime, the control will generate a per pixel alpha composited Form with the desktop. The Form's Region property defines areas of the Form to host other controls, and it's calculated on the fly from the image's alpha channel. This control also supports runtime changing of the image. Before we discuss some of the code specifics, let's run through how you use it.
How To Use
- There are two controls:
AlphaFormTransformer and a helper control AlphaFormMarker. Reference the AlphaFormTransformer.dll in your project and add them to your Toolbox.
- Start with a Form in the designer, and set its
FormBorderStyle to None.
- Drag an instance of the
AlphaFormTransformer control (AFT) onto the Form and dock it to Fill. The AFT must be the same size as the Form.
- In the Form's
Load event handler (or OnLoad override), add a call to TransformForm().
alphaFormTransformer1.TransformForm();
- Select a 32 bit alpha channel PNG or TIF image as the AFT control's background image. This image needs to have the desired Window frame elements masked by its alpha channel and interior transparent areas that will hold other controls.
- Select the form, and set its width and height equal to the width and height of the image. This is an important prerequisite. The form, AFT control, and the AFT's background image must all have the same dimensions.
- By itself, the alpha channel is not enough information for the AFT to calculate the main form's Region necessary to show the controls you will be adding. But we make it easy. Drag an
AlphaFormMarker (AFM) control into each transparent region that will show controls. These areas must be completely bounded by a non-transparent border. Each marker simply needs to be anywhere in the transparent region, and its center (shown by the crosshairs) must fall on a transparent pixel. Once positioned, make them as small as desired, or send them to the back to get them out of the way. It's important to note that they must be added to the AFT and not to any other container control. Be mindful of this if you decide to add them later. Finally, they are hidden at run time.
The marker's FillBorder property specifies how far into the non-transparent pixel border the region will be constructed. The composited image will typically have semitransparent edges. Therefore the region that's built from the marker position needs to expand some number of pixels into (and *under* from the compositing point of view) the semitransparent area, otherwise you will see through to the desktop along these borders. You want the border value to be large enough to cover the thickness of the semitransparent edge (typically a couple pixels), but not too large which might cause it to extend past the other side of the image frame.
- Now add whatever other controls you like to the interior transparent regions of the image. The AFT is a container control (descends from
Panel), so you will be adding the controls to it. The image will always mask out the control area(s) at run time, but not necessarily at design time. If you don't set a control's background color to transparent, it may cover the masked borders of the image while in the designer because it's on top whereas at run time it's beneath the alpha channel image. By setting a control's background color to transparent, you can essentially fake the runtime behavior. And doing this can help you lay things out even if you later change it to a non-transparent color. Not all controls support a non-transparent background color, so in some cases your controls may overlap the image borders in the designer.
- That's it, let's run and see what we have.
- Optionally, we can set the background image property of the main Form and set each control's background color to transparent. This allows us to have a custom background like you see below. You could also achieve this by using additional controls like panels each with their own background.
Runtime Skinning
At run time you can change the form's alpha channel image by calling UpdateSkin passing a new bitmap for the window frame. However if the new bitmap has a different alpha channel than the current one, then you must do the following *before* calling this method:
- If the size of the image has changed, resize the main Form and the AFT to match (if the AFT is docked then just the form).
- Reposition all AFM markers so that the form's region can be calculated.
- Reposition all other controls as needed.
On the other hand if your new image has the same size and alpha but different RGB, then this method is all you need to call. The source solution has a sample project illustrating how to change the image at runtime.
How it Works
As you might have guessed, what we're really doing is creating a second Window which hosts the composited image on top of the main Form. This all happens in TransformForm() which does the following:
- A layered Window Form is created. This is a Window with the
WS_EX_LAYERED extended style bit set. Windows will alpha channel composite layered windows on the desktop once the window contents are set with the Win32 call UpdateLayeredWindow(). We've wrapped a class around this called LayeredWindowForm.
m_lwin = new LayeredWindowForm();
m_lwin.TopMost = ParentForm.TopMost;
m_lwin.ShowInTaskbar = false;
m_lwin.MouseDown += new MouseEventHandler(LayeredFormMouseDown);
m_lwin.MouseMove += new MouseEventHandler(LayeredFormMouseMove);
m_lwin.MouseUp += new MouseEventHandler(LayeredFormMouseUp);
ParentForm.Move += new EventHandler(ParentFormMove);
m_lwin.Show(ParentForm);
m_lwin.Location = ParentForm.Location;
- A bitmap is extracted from the AFT's background image property. It may be scaled in some situations (see the comments here on DPI support). After this
UpdateSkin() is called.
if (BackgroundImage != null)
{
if (BackgroundImage.Size != ParentForm.Size)
{
aphaBmap = new Bitmap(ParentForm.Width, ParentForm.Height);
Graphics gr = Graphics.FromImage(aphaBmap );
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.CompositingQuality = CompositingQuality.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.DrawImage(BackgroundImage, new Rectangle(0, 0, ParentForm.Width,
ParentForm.Height),
new Rectangle(0, 0, BackgroundImage.Width, BackgroundImage.Height),
GraphicsUnit.Pixel);
gr.Dispose();
}
else
aphaBmap = new Bitmap(BackgroundImage);
}
UpdateSkin(aphaBmap);
- In
UpdateSkin, an array from the image's alpha channel is created. This is used in the next step to construct the main form's Region.
byte[] mngImgData = new byte[m_alphaBitmap.Height * bData.Stride];
Marshal.Copy(bData.Scan0, mngImgData, 0, mngImgData.Length);
for (int j = 0; j < m_alphaBitmap.Height; j++)
{
int ai = j * bData.Stride + 3;
for (int i = 0; i < m_alphaBitmap.Width; i++, ai += 4)
{
alphaArr[i, j] = mngImgData[ai];
}
}
- Still within
UpdateSkin, the main Form's Region is constructed. The Region will define the visible areas of the Form where you place your controls (the controls are hosted in the AFT, but everything is clipped by the main Form's region). The Region is constructed on the fly by performing a seed fill type operation from the user specified AFM markers which occurs in UpdateRectListFromAlpha(). These markers are little more than special helper controls which are dragged onto the Form in design mode. In essence we're filling specific transparent areas of the image's alpha channel. For each such "filled" pixel, we generate a rectangle to be used to construct the main Form's Region.
Rectangle bounds = new Rectangle();
ArrayList rectList = new ArrayList();
foreach (Control cntrl in Controls)
{
if (typeof(AlphaFormMarker).IsInstanceOfType(cntrl))
{
AlphaFormMarker marker = (AlphaFormMarker)cntrl;
UpdateRectListFromAlpha( rectList, ref bounds,
new Point(marker.Location.X + marker.Width / 2,
marker.Location.Y + marker.Height / 2), alphaArr,
m_alphaBitmap.Width, m_alphaBitmap.Height,
(int)marker.FillBorder);
marker.Visible = false;
}
}
ParentForm.Region = RegionFromRectList(rectList, bounds);
- Finally in
UpdateSkin, the alpha channel image bitmap is set to the layered form.
m_lwin.SetBits(m_alphaBitmap);
- At the bottom of
TransformForm(), the AFT's background image and tiling are copied from the main Form. This allows us to have a custom background image if desired.
BackgroundImage = ParentForm.BackgroundImage;
BackgroundImageLayout = ParentForm.BackgroundImageLayout;
Points of Interest
Dragging the Window is accomplished by dragging any visible part of the layered Window (our Window frame). When a mouse drag event in LayeredFormMouseMove() occurs, it moves the main Form but lets the Move event handler ParentFormMove() move the layered window. This handles cases where the Form is moved outside of a drag operation. For example, when the taskbar's autohide is unchecked, Windows may cause the Form to be relocated.
Also on Window XP, ParentFormMove() tries to help the desktop and underlying Windows catch up to redrawing invalidated areas (remember we're actually moving two Windows although the layered Window of the pair is double buffered). If it detects we're dragging the Form, it sleeps the main thread according to the DragSleep property. A reasonable value like 30 milliseconds makes for less distracting redrawing of the invalidated parts of the desktop. Under Vista this property is ignored as the DWM double buffers everything.
Finally, PInvoking is unavoidable for both for the layered Window APIs and ExtCreateRegion() which has to be used because of the poor performance of building a Region from a GraphicsPath or Region.Union().
Summary
In this article we've presented a Windows Forms control that allows you to build arbitrarily complex image based Window elements with designer level support and runtime alpha channel compositing.
History
- Version 1.0 (initial release)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 53 (Total in Forum: 53) (Refresh) | FirstPrevNext |
|
 |
|
|
Hey, I have successfully used Flash.ocx control in C# to play SWF movie file but I have faced some limitations in it. I jumped to any specific frameNo by using gotoframe() method given in this control, It do jumped but the problem arise when I try to jump on frame which is greater than 12,500 i.e: It only understands starting 12,500 frames of the movie although it do play frame grater than 12,500 but the problem is to jump. Do anyone has idea about this problem.
Plus I also want to play the movie at 1/2X,2X,4X,... speed, Is it possible using this control or I should look for another and what should that ANOTHER(remember I need it on windows form).
regards.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hallo, I want to use this great idea in VB. I tried it to use it at an info-form. I wrote the following code in the forms code:
Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Data Imports System.Drawing Imports System.Text Imports System.Windows.Forms
Partial Public Class frmCustomForm Inherits Form Public Sub New() InitializeComponent() End Sub
Private Sub frmCustomForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load AlphaFormTransformer1.TransformForm() End Sub
Private Sub AlphaFormTransformer1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AlphaFormTransformer1.Click Me.Close() End Sub End Class frmCustomForm is my form, I have a fine picture made with Gimp as png. When I start the project, a great custom form is shown! But one thing functions not: The Click on the AlphaFormTransformer...
What can it be?
Greetings from Austria Dietrich
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
thank you, but yes, i know it now. but i tried in design time the doubleclick at the image frame, it opened the click-event-routine and i wrote the code into it. but... now i use the click on a label...
greetings- dietrich
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
First, have to say it's a nice article, have been looking for something like this. I got it to work now, but still have a bit of a problem. I want to use the KeyDown events from the form, but they now don't seem to be raised anymore. Any idea how I can still get an event when a key is pressed when the form is active?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi,
This is a great article. But when i add windows controls (ie button, editbox, ...). it does not show. Any suggestion?
Thanks, PheroSiden
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
It could be because your image doesn't have an alpha channel, or you didn't add a marker to the transparent areas.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hey, just thought I throw this out there.
I have an application where I have a flash activeX control that show my flash animation. The flash activeX control is rendered in transparent window mode which makes the background of the flash animation transparent. And what I got at the moment is a form with a TransparencyKey. This will of course leave a lot of jagged edges and other ugly bits of pixels where there isn't 100% transparency. So I really need blend the alpha channel of the flash animation with the desktop background.
Any ideas if your technique could be applicable to this problem or any other solution that might work.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your animation would need to produce frames with an eight bit alpha channel (I don't know if this is possible from Flash). Then you would need to use the Win32 UpdateLayeredWindow API to set the transparent window content on a frame by frame basis.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
In the Form1_Load I added the following code
private void Form1_Load(object sender, EventArgs e) { this.BackgroundImage = new Bitmap("c://photo.jpg"); alphaFormTransformer1.BackgroundImage = new Bitmap("c://frame.png"); alphaFormTransformer1.TransformForm(); } I want to extract the new background image What can I do?
Or I want to put a "a photo frame" for this photo, What can I do?
Mostafa Sayed Junior Software Developer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I know .... But you do not understand what I want .... I want to save the resulting image from the form that will appear.
How ...?
Mostafa Sayed Junior Software Developer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Without too much trouble one could re-write this component in C++/CLR or for that matter simply use it from a C++/CLR project.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Thanks for your reply.
From C++/MFC. I used CImage class with AlphaBlend function but doesn't work fine. My image border cannot smooth. Any suggestion? 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't know much about MFC or CImage, but there is nothing .NET exclusive in what I've done. The crux of this control is use of windows with the WS_EX_LAYERED style along with calculating the window region from an alpha channel. All of this could be done with basic Win32 calls (and by extension MFC).
That said I'm sorry but I'm not going to be writing this in straight Win32 any time soon
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Hi, first of all, congratulations for your work, it´s really excelent... I just want to ask you if you know about a way to change the look of a form (like this) but also be able to resize it (something like realplayer). Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well first we have to keep in mind this component is used to create image based window borders. In theory you could scale the image and reestablish the alpha channel on the fly, but the degree to which you could scale the image is limited (before it just looks bad).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
This is some real good sh*t. My forms look really good thanks to you! There is one problem thou..
I would like a fading form to appear when I start my program. As I have learned from earlier discussions it should be possible using layered window opacity. But when I use the paint.net code you prescribe (e.g. SetFormOpacity(this, 0.50) I get a faded control window, i.e. my imaged border is still solid. I use your code described on your homepage under "Form with controls on top of image".
Have you encountered this problem before?
thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You'll need to modify the layered window SetBits() call to take an opacity argument in addition to setting the opacity of the main form.
cheers,
jeff
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks Jeff! That solved my problem.
However, have you tested this in windows 2000? I get strange errors when running my app on a 2K machine. I guess this has to do with the transparancy since you state on your homepage that the alphaform lib should work with win2K, and I think I managed to see the form for a sec after dismissing some error messages. (After which the program crashed.)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have a Win 2k server box and it works, but I've received several emails of the same problem you're reporting.
I'm not going to try and solve it at this point. I've removed the Win2K support statement from the shareware site.
thanks, jeff
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|