Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  82 Posts | 5 Stories | 151 Comments | 380 Trackbacks

News

View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

Image Galleries

Blogs I Read

Tools I Use

Download source code from ProjectDistributor.net

 

Figure 1. Sample form with custom drawn border.

I have split the code into two primary classes. First one, FormWithNonClientArea, extends the standard Form class by adding support for non-client area messages (more on this shortly) and can be uses for various scenarios. Second one, CustomBorderForm, utilizes these messages and represents a base class for drawing graphical borders composed of several bitmap parts. It also draws a form header including icon, text and form buttons (more on this later). This way I can separate dirty plumbing required for enabling non-client drawing and actual drawing of the graphical elements. So lets see how it works.

Extending Form with Non-Client Area Painting

Each window we see on screen (be it a Form, UserControl or any other Control) is described by two rectangles: the bounds of the window and it's client area. Bounds specify the location and size of the window as a whole while the client area specifies the region inside the window that is available for client controls. By default Windows Forms allows us to access only the client part of the window. To gain access to the non-client part we need to intercept some additional windows messages. We can do this by overriding the WndProc message loop. For each message I defined dedicated method, so my WndProc method only redirects calls to this methods.

Positioning the Client Ractangle

If we are going to draw our custom borders good chances are that their size and proportions will differ from the standard ones. To correct this we need to specify a new client rectangle for the window. This is done in the WM_NCCALCSIZE message. This message can be raised in two ways. When WParam is equal to zero the LParam points to RECT structure with window bound that we should adjust to proposed client ractangle. Alternatively, when WParam value is one the LParam points to NCCALCSIZE_PARAMS strucure allowing to move the existing client area inside the window. For our purpose we will simply adjust the proposed rectangle to required coordinates.

private void WmNCCalcSize(ref Message m)
{
    if (m.WParam == NativeMethods.FALSE)
    {
        NativeMethods.RECT ncRect = (NativeMethods.RECT)m.GetLParam(typeof(NativeMethods.RECT));
        Rectangle proposed = ncRect.Rect;
        OnNonClientAreaCalcSize(ref proposed);
        ncRect = NativeMethods.RECT.FromRectangle(proposed);
        Marshal.StructureToPtr(ncRect, m.LParam, false);
        m.Result = IntPtr.Zero;
    }
    else if (m.WParam == NativeMethods.TRUE)
    {
        NativeMethods.NCCALCSIZE_PARAMS ncParams =
            (NativeMethods.NCCALCSIZE_PARAMS)m.GetLParam(typeof(NativeMethods.NCCALCSIZE_PARAMS));
        Rectangle proposed = ncParams.rectProposed.Rect;
        OnNonClientAreaCalcSize(ref proposed);
        ncParams.rectProposed = NativeMethods.RECT.FromRectangle(proposed);
        Marshal.StructureToPtr(ncParams, m.LParam, false);
    }

}

Note that this method calls a virtual OnNonClientAreaCalcSize method taking a Rectangle that you can overwrite in your code.

Painting the non-client area

The main message responsible for painting the non-client area is the WM_NCPAINT message. The WParam for this message contains the handle to a clip region or 1 if entire window should be repainted. So to paint anything we only need to create a Graphics object from the window handle and use it as we would in the typical OnPaint method.

private void WmNCPaint(ref Message msg)
{
    PaintNonClientArea(msg.HWnd, (IntPtr)msg.WParam);
    msg.Result = NativeMethods.TRUE;
}

Now is the tricky part; If you leave it that way you quickly notice that on some occasions you still get some parts of the standard border painted over your brand new framing. That indicates that there are some other messages that cause painting in the non-client area.

The first one is the WM_SETTEXT message that transports new title for the window (stored as Text property on the Form). Apparently it also repaints the border in order to update the title bar. Of course, we still want to send out the new title so we need to pass the message to the DefWndProc method. But we will handle painting on our own.

private void WmSetText(ref Message msg)
{
    DefWndProc(ref msg);
    PaintNonClientArea(msg.HWnd, (IntPtr)1);
}

The second culprit happens to be the WM_ACTIVATE message that is responsible for switching the window active state. Window is active when it is the top level window that you interact with and it has different border to show that. When you switch to another window the first one updates its border to indicate that it has lost the focus. The WParam of this messages holds the window active state and is 1 when border should be drawn as active and zero otherwise. We will handle the painting and skip to the DefWndProc only when window is minimized.

private void WmNCActivate(ref Message msg)
{
    bool active = (msg.WParam == NativeMethods.TRUE);
    if (this.WindowState == FormWindowState.Minimized)
        DefWndProc(ref msg);
    else
    {
        PaintNonClientArea(msg.HWnd, (IntPtr)1);
        msg.Result = NativeMethods.TRUE;
    }
}

I agree that this is big design inconsequence and all painting should be done in one place but it is around for a long time and we must live with it. Now that we cleared this out we can get down to actual painting.

The most important thing here is to get the correct hDC handle and we wil use native GetDCEx function for that. It takes three parameters: the window handle, the clip region and option. First two we got already from the messages. As for the options the MSDN states that only WINDOW and INTERSECTRGN are needed, but other sources confirm that CACHE is required on Win9x and you need CLIPSIBLINGS to prevent painting on overlapping windows.

If we get a valid hDC we can quickly create the Graphics object using Graphics.FromHdc() method, paint our stuff and dispose it. Here it is worth noting that when we dispose a Graphics instance it will also automatically free the hDC so there is no need for calling the ReleaseDC manually.

private void PaintNonClientArea(IntPtr hWnd, IntPtr hRgn)
{
    NativeMethods.RECT windowRect = new NativeMethods.RECT();
    if (NativeMethods.GetWindowRect(hWnd, ref windowRect) == 0)
        return;

    Rectangle bounds = new Rectangle(0, 0,
        windowRect.right - windowRect.left,
        windowRect.bottom - windowRect.top);

    if (bounds.Width == 0 || bounds.Height == 0)
        return;

    Region clipRegion = null;
    if (hRgn != (IntPtr)1)
        clipRegion = System.Drawing.Region.FromHrgn(hRgn);

    IntPtr hDC = NativeMethods.GetDCEx(hWnd, hRgn,
        (int)(NativeMethods.DCX.DCX_WINDOW | NativeMethods.DCX.DCX_INTERSECTRGN
            | NativeMethods.DCX.DCX_CACHE | NativeMethods.DCX.DCX_CLIPSIBLINGS));

    if (hDC == IntPtr.Zero)
        return;

    using (Graphics g = Graphics.FromHdc(hDC))
    {
        OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion));
    }
}

At the begining ot this method I use native GetWindowRect function to get the correct coordinates of the window. At this point the Bounds property is not accurate and especially during resizing seems to always stay behind. Next I validate window size as obviously no painting is needed when it is empty. The actual painting should be done in the virtual OnNonClientAreaPaint method.

Removing flicker with double-buffering

Unfortunatelly painting this way is fine only as long as you don't try to resize the window. When you do you will see very unpleasant flickering. Totally not cool. We need to apply double-buffering in order to fix it and I just found a cool mechanism in .NET Framework that should help with that.

There is a class called a BufferedGraphics buried in the System.Drawing namespace. It's the same class that is used when you set DoubleBuffered flag on any control. (To be honest I haven't checked if this class existed prior to .NET 2.0). There is also a factory class called BufferedGraphicsManager that we use to create such object. The Allocate method takes either an existing Graphics object or the targetDC handle. Having an instance of BufferedGraphics we obtain a real Graphics object, do the painting as usual, and then call the Render method to draw the buffered image to the screen (presumably using some form of bit blitting).

using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(hDC, bounds))
{
    Graphics g = bg.Graphics;
    OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion));
    bg.Render();
}

Whew, the above code looks to simple to possibly work. And indeed it doesn't. It all looks good when the window stays active, but when it gets covered by another window suddenly all of the client area gets painted in black. So there is something missing, like establishing a clip region to exclude this area from bliting. I hope that someone smarter then me could help and figure out a better way to fix this.

A not so scary ghost story

There are two more things that need to be done in order to get perfectly drawn custom border. First thing is to completely get rid of XP themes on our window. We have already taken over all painting but when themes are turned on they also might affect other aspects of window. For example thay would likely change the window shape to something non-rectangular (like adding round corners) and obviously we want to prevent this. We will use the SetWindowTheme native function with empty parameters to completely disable theming on the current window. Note however that this will only affect the window itself so you don't need to worry that you loose theming on the control placed in it's content area.

As for the second thing, I wonder how many of you heard about windows ghosting feature? I didn't know about it until recently. Quoting MSDN "Window ghosting is a Windows Manager feature that lets the user minimize, move, or close the main window of an application that is not responding." Basically when the process doesn't respond to window messages within designeted time (hangs) the Windows Manager will finally loose patience and draw the window frame by itself allowing the user to do something with the application. This can hapen when the process executes some long running task (like query or complex processing) in the same thread as the windows message loop.

In theory for a well written application that delegates all heavy processing to background workers this should never happen. But I haven't written such application yet. This feature can be disabled using DisableWindowGhosting native function but only for the entire application. Now it's your decision whether you want to present the users with consistent user experience even on these odd occasions or you can cope with some occasional quirks but let the user control the situation all the time.

protected override void OnHandleCreated(EventArgs e)
{
    NativeMethods.SetWindowTheme(this.Handle, "", "");
    NativeMethods.DisableProcessWindowsGhosting();
    base.OnHandleCreated(e);
}

Handling mouse actions

After we have positioned and painted the border it's time to make it behave like the normal one does. One such behavior is indicating whether the form is sizable when mouse moves on the border. Another is that when we double-click on the title bar the windows maximizes or restores respectively. And of course the window recognizes when mouse is over or user clicks on the form buttons (minimize, maximize, close, and help). To make this work properly we need to tell the border how all these elements are positioned on our form. This is done in the WM_NCHITTEST message. The LParam holds the screen coordinates of current mouse position. As a result we should return a hit-test code telling the system what part of window the mouse is over.

private void WmNCHitTest(ref Message m)
{
    Point clientPoint = this.PointToWindow(new Point(msg.LParam.ToInt32()));
    m.Result = new System.IntPtr(OnNonClientAreaHitTest(clientPoint));
}

When mouse is on the border edge and the window is resizable we should return values like HTLEFT, HTTOPRIGHT or HTBOTTOM . When mosue is over one of the window buttons we return code for that buton (HTMINBUTTON, HTMAXBUTTON, HTCLOSE). To indicate that mouse hovers over the title bar we can pass the HTCAPTION value. Finally when mouse is inside the client rectangle we should return the HTCLIENT value.

Capturing mouse move is quite simple. The WM_NCMOUSEMOVE message delivers new mouse position each time it is moved over the non client area. Here, I am using the standard MouseMoveEventArgs to pass this to the virtual method.

private void WmNCMouseMove(ref Message msg)
{
    Point clientPoint = this.PointToWindow(new Point(msg.LParam.ToInt32()));
    OnNonClientMouseMove(new MouseEventArgs(MouseButtons.None, 0, 
        clientPoint.X, clientPoint.Y, 0));
    msg.Result = IntPtr.Zero;
}

To capture mouse click we should intercept the WM_NCLBUTTONDOWN and WM_NCLBUTTONUP messages, for the left mouse button (and similar for the other two). For all these messages the WParam contains the hit-test value that we returned when processing the WM_NCHITTEST message, and the LParam contains the screen coordinates of the mouse cursor. I'm using extended NonClientMouseEventArgs to pass all this information to the virtual method. In return the method should set the Handled flag to indicate that our application processed this message.

private void WmNCLButtonDown(ref Message msg)
{
    Point pt = this.PointToWindow(new Point(msg.LParam.ToInt32()));
    NonClientMouseEventArgs args = new NonClientMouseEventArgs(
        MouseButtons.Left, 1, pt.X, pt.Y, 0, msg.WParam.ToInt32());
    OnNonClientMouseDown(args);
    if (!args.Handled)
    {
        DefWndProc(ref msg);
    }
    msg.Result = NativeMethods.TRUE;
}

Continued in part two, where you learn how to actually construct a border from bitmap parts and how to handle title bar buttons.

posted on Monday, July 04, 2005 9:00 PM

Feedback

# re: Drawing Custom Borders in Windows Forms 7/23/2005 2:29 PM Mick Doherty
Just had a look at this in .net 2.

I started something like this a while back and never finished it. Just thought I would point out one small problem. Set the forms Opacity to less than 100% and the left and bottom edges are not drawn.

This can be overcome by creating a compatible bitmap and using bitblt via interop to draw the window to the bitmap update and then bitblt back to the window, instead of using GDI+ on a graphics object created from the Window DC.

# re: Drawing Custom Borders in Windows Forms 7/23/2005 9:05 PM Andy J
This is awesome thanks for posting it... I've made some changes to put it in my .net 1.1 forms... Lovin it... I'm gonna try to figure out the problem with the double buffering to... Awesome work though... thanks again

# re: Drawing Custom Borders in Windows Forms 7/24/2005 2:00 PM Andy J
if you make the following adjustments the form will be double buffered...

inside the nonclientareaform draw do this

offScreenBmp = new Bitmap(bounds.Width, bounds.Height);
offScreenDC = Graphics.FromImage(offScreenBmp);

using (Graphics g = Graphics.FromHdc(hDC))
{
//cliping rect is not cliping rect but actual rectangle
OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion), ref offScreenDC, ref offScreenBmp);
}

then make overrides that pass the offScreenDC and offScreenBmp to the drawing methods in CustomBorderForm.cs... mine look like this...

protected override void OnNonClientAreaPaint(NonClientPaintEventArgs e, ref Graphics offScreenDC, ref Bitmap offScreenBmp)
{
OnNonClientAreaPaintBackground(e, ref offScreenDC, ref offScreenBmp);
OnNonClientAreaPaintForeground(e, ref offScreenDC, ref offScreenBmp);

e.Graphics.DrawImage(offScreenBmp, 0, 0);
}

then basically the OnNonClientAreaPaintBackground, and foreground paint to offScreenDC instead of e.Graphics... when they finish you paint the offScreenBmp and you got yourself easy double buffering without having to use win32 dll's...

I'm pretty sure that going the BitBlt route would be faster, but I see no effect on my application and it works beautifully...


Also of note I noticed that the method WmNCCalcSize when the NativeMethods.FALSE, was better left commented out because otherwise my version in .net 1.1 seemed to draw strange... it kept changing offsets depending on whethere that was true or false, so I just commented it out since the only time i could see it was being called was on the initial load... maybe that's a bad idea I don't really know, but for my .net 1.1 version I had to do it....

Anyways thanks again for the great code here Szymon



# re: Drawing Custom Borders in Windows Forms 7/24/2005 3:11 PM Andy J
actually with that double buffering I did, now when I move the window around it has a delay before it refreshes the non window area... may need to go the bitblt route...

# re: Drawing Custom Borders in Windows Forms 7/25/2005 3:57 AM Szymon
Thank you all for your kind comments. Mick I think I've seen your examples and I'm aware of this problem. I just didn't needed semi-transparent window yet ;-)

As for double-bufferring I guess it has to be done with the Bitblt and thats why I used the BufferedGraphics class which should encapsulate this. However this works strange so I have do this manually. Can anyone help with that?

# re: Drawing Custom Borders in Windows Forms 7/25/2005 8:21 AM Chris H
mm, Any .net 1.1 adaptation available ?
i tried to port it, but got stuck on some
System.OutOfMemoryException

# re: Drawing Custom Borders in Windows Forms 7/25/2005 8:36 AM Szymon
As I see there is great need for working .NET 1.x version. I will try to provide this as soon as possible (hopefully this evening). Please stay tuned.


# re: Drawing Custom Borders in Windows Forms 7/25/2005 8:03 PM Szymon
I have just finished porting this to .NET 1.x. As expected there werent many problems. The most troubles I had with resource files (I love the new resource handling tools in VS2005). I have published updated release on projectdistributor. Please read notes at the end of second part of the article for some details.

# Double Buffering Posted Above 7/25/2005 9:19 PM Andy J
Well I figured out why it was acting funny using the non BitBlt double buffer... that code works you still need the InvalidateWindow() method call though... but it gets rid of the flicker of the non client area...

# re: Drawing Custom Borders in Windows Forms 7/25/2005 9:25 PM Andy J
Sorry for the double post but I just checked out the issue Mick talked about and with the non BitBlt double buffering I put in mine, it still draws all the borders just fine with lower than 100% opacity... the non buffered is another story...

# re: Drawing Custom Borders in Windows Forms 7/29/2005 12:12 AM Andy J
I cleaned up my double buffering so you don't have to do all that overriding BS... here's the snippet I got it from another object I'm using in my form... only problem now is that the non client area is double buffered... but for some reason when things are dragged over the client area it messes up horribly drawing what has been dragged over it...

using (Graphics screenGraphics = Graphics.FromHdc(hDC))
{
using(Image i = new Bitmap(bounds.Width, bounds.Height))
{
using(Graphics g = Graphics.FromImage(i))
{
OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion));
}

// Now draw all the graphics at once.
screenGraphics.DrawImage(i, bounds);
}
}

# Big Problem 7/29/2005 4:31 PM Andy J
Just running some more tests on this, and double buffered or not, there is something missing in the code to redraw the client area when it needs to be invalidated while a window is dragged over it... the double buffer I just posted above does get rid of the flicker of the text, etc. in the titlebar and borders, but if anyone is going to use this as is, they better keep it with TopMost = true... I'd love to find someway to fix this I just don't even know what causes it...

# re: Drawing Custom Borders in Windows Forms 7/29/2005 5:27 PM Andy J
pretty sure I fixed that part aswell... you need to add this in the formwithnoborder.cs


case (int)NativeMethods.WindowMessages.WM_SYNCPAINT:
{
this.DefWndProc(ref m);
break;
}

then with the code I posted above it double buffers just fine, and the content repaints when it needs to instead of waiting for the on idle paint to refresh which is what I think it was doing...

# re: Drawing Custom Borders in Windows Forms 7/31/2005 3:41 AM Andy J
alright the code above doesn't work right either.... I changed it to this and it works... there is probably a way to determine the region of the form that needs to be invalidated but this is a crude way of making the controls repaint when something is dragged over the custom form, border or not...

case (int)NativeMethods.WindowMessages.WM_SYNCPAINT:
{
foreach(Control c in this.Controls)
{
c.Refresh();
}
this.DefWndProc(ref m);
break;
}

only way I know of fixing the window dragged over the top issue... and it still has a set back because if the form doesn't have any controls on it, the actual form itself still paints what was dragged over it... so hell I'm just gonna make sure I fill my forms with a panel ;)

I don't know if I've helped here or not but I hope I haven't been a pest

# re: Drawing Custom Borders in Windows Forms 8/12/2005 6:32 PM Enes M
Does anyone have vb.net version of this? I have successfully converted the project with the conversion tool and fixed the compile errors, but left some things unfunctional (borders not painting). Caption bar does paint. Also if you add a menu control to the form it does not show at runtime, and paints incorrectly in design time.

# re: Drawing Custom Borders in Windows Forms 8/13/2005 11:40 AM Enes M
I forgot to mention that i'm working on .net 1.1 version.

# re: Drawing Custom Borders in Windows Forms 8/21/2005 6:20 PM Szymon
Sorry Enes, to keep you waiting (I had vacations). I haven't tried this on VB and can't help you much as I don't know much of this language. The only thing I can suggest is that you compile the project in C# and then reference the assembly in your VB project. You could than just change inheritance parent of your forms.

# re: Drawing Custom Borders in Windows Forms 9/5/2005 4:33 AM Codeable
Most excellent source! You have saved me HOURS of work. I do, however, have something to point out: When you disable a caption button (say the maximize button) the event still fires when you click it. I simpy added the following code to the DepressButton function:

if(!currentButton.Enabled)
{
return false;
}

Also, has anyone figured out why transparent windows don't draw correctly? I've tried both methods by Andy J to no avail.. If anyone has, please e-mail me! will@codeable.net


# re: Drawing Custom Borders in Windows Forms 9/5/2005 5:10 AM Szymon
Hi Will,
I'm glad that my article could help you, and many others. I still intend to fix and update the sources (I need to find the cure for transparancy and double-buffering issues) and finish the article but right now my day-to-day schadule is packed to its limits. Still thanks for pointing the error. If you find anything else please send it here. I'm also curious what use have you found to this code and would be happy if you could send me some screenshots.

# re: Drawing Custom Borders in Windows Forms 9/5/2005 7:13 AM Codeable
After playing with it for a bit, (and failing) I've decided to simply go the BitBlt route. This fixes all previously mentioned clipping and transparency issues on v1.1 for me.(current application hasn't been ported to 2.0 yet.) The code update doesn't take much, just a few additions to the NativeMethods, and yet another version of the PaintNonClientArea event-raising block. Here is my quicky PaintNonClientArea code to get it started: [NativeMethod additions should be easy: copy n paste from msdn :)]

IntPtr CompatiblehDC = NativeMethods.CreateCompatibleDC(hDC);
IntPtr CompatibleBitmap = NativeMethods.CreateCompatibleBitmap(hDC,bounds.Width,bounds.Height);
NativeMethods.SelectObject(CompatiblehDC,CompatibleBitmap);
NativeMethods.BitBlt(CompatiblehDC,0,0,bounds.Width,bounds.Height,hDC,0,0,NativeMethods.TernaryRasterOperations.SRCCOPY);

using (Graphics g = Graphics.FromHdc(CompatiblehDC))
{
OnNonClientAreaPaint(new NonClientPaintEventArgs(g, bounds, clipRegion));
}

NativeMethods.BitBlt(hDC,0,0,bounds.Width,bounds.Height,CompatiblehDC,0,0,NativeMethods.TernaryRasterOperations.SRCCOPY);
NativeMethods.DeleteObject(CompatibleBitmap);
NativeMethods.DeleteDC(CompatiblehDC);

Not much to it eh? Just keep in mind to add error trapping for those interops. Probably gonna tcf that block.

I'm currently using your class to *skin* my secure chat app - and since it's in pieces at the moment I don't have any screen shots for ya. [Should be done in a week] Although, once I'm done - your class will get some solid alpha testing while I alpha the new version. ha. I'll post feedback as it develops.

# re: Drawing Custom Borders in Windows Forms 9/5/2005 7:22 AM Szymon
Thanks for the update. I should try this as the time allows. This code is already heavy tested for several months in the business project I work on. Till now it didn't cause any unexpected errors or memory leaks. You should be aware however that it doesn't work well on Windows 2003 Server (it doesn't crash but the border simply don't draw). I don't expect my customers to use it on this platform but you should at least provide some fallback solution (turn on the standard borders). Anyway, let me know if you have any other suggestions or requirements.

# re: Drawing Custom Borders in Windows Forms 9/5/2005 2:13 PM Andrew
Awsome stuff, I've been playing with it all night and see a lot of potential for it with my stuff. Hopefully we can get around that Windows 2003 Server issue though :)

Anyway, I have a problem I haven't been able to get around with the Maximize/Restore button. Try Maximize and Restore back-and-forth; the Restore button doesn't show like it should. this.WindowState keeps returning Normal...Or is it just me?

# re: Drawing Custom Borders in Windows Forms 9/5/2005 3:39 PM Andrew
OperatingSystem os = System.Environment.OSVersion;

if ((os.Version.Major == 5 && os.Version.Minor == 1) // WinXP
|| os.Version.Major >= 6) // Vista / XP Compatible
_TopForm = new XPCompatibleForm();
else
_TopForm = new TopForm(); // 95, 98, 2000, 2003, etc


# re: Drawing Custom Borders in Windows Forms 9/6/2005 6:34 PM Szymon
Actually, I think I haven't tested the code that changes appearance of maximize/restore button as I didn't have alternative graphics for it in my skin.

The problem was that I was checking the WindowsState during NC_CALCSIZE message and it was still in Normal state when Window was being maximized. To fix this I added yet another handling method for the WM_SYSCOMMAND message. Now I switch the button appearances on the SC_MAXIMIZE and SC_RESTORE commands. Strangely it occurs that when you double-click on the title bar the codes are slightly different.

Anyway, I just uploaded the updated code to ProjectDistributor. It also includes the solution to double-buffering issues provided by Codeable. Big thanks to all of you for helping me out with this project.

# re: Drawing Custom Borders in Windows Forms 9/6/2005 6:38 PM Szymon
Andrew,
Have you actually tested this on Windows Vista! ? I think I read somewhere that it is bassed on Windows 2003 kernel so there might be problems with that as well. For now it is safe to assume it only works on Windows XP. However, I think it could work on Windows 2000 as well.

# re: Drawing Custom Borders in Windows Forms 9/10/2005 12:07 AM Codeable
Hey - I was lucky enough to get the chance to look at this again. Have you implemented a custom MainMenu yet? Or are you working on one? I've managed to get the MainMenu bar working (draws, and supports skin switching on/off) with a bit of hacky gdi and extending the client area. It's only my development build though, and needs to be done properly (with bitmap support.. etc etc) -- I just don't want to write the additional code if it has already been done for this class. And if it hasn't - no prob. I'll get right on it. I'll be watchin' for a comment.

Again, thanks for this class. Can't count how many hours I've saved.

# re: Drawing Custom Borders in Windows Forms 9/10/2005 6:46 PM Enes M
I've been trying to port this to vb.net version, and now i'm just stuck with borders not always painting(mostly when resizing the form, borders just stay in the client area and don't resize with the form). Since i compared class by class, i couldn't find any difference, except that i don't know in C# what (IntPtr)1 is. Here's a sub as an example:

private void DrawButton(CaptionButton button)
{
IntPtr hDC = NativeMethods.GetDCEx(this.Handle, (IntPtr)1,
(int)(NativeMethods.DCX.DCX_WINDOW | NativeMethods.DCX.DCX_INTERSECTRGN
| NativeMethods.DCX.DCX_CACHE | NativeMethods.DCX.DCX_CLIPSIBLINGS));
if (hDC != IntPtr.Zero)
{
using (Graphics g = Graphics.FromHdc(hDC))
{
button.DrawButton(g);
}
}
}

I've changed (IntPtr)1 to New IntPtr(1) in vb.net, but i think that this i where the problem is (although not sure). Can someone explain what value (IntPtr)1 is holding in the sub above.

# re: Drawing Custom Borders in Windows Forms 9/10/2005 8:51 PM Enes M
A small fix: when you press the resize,close or minimize button and move the mouse while holding the button it causes the window to resize,close or minimize respectively. Also when you hold one of the buttons the cpu spikes to 100%, and we don't want this.
Here's the fix for both of these:
1. add done = false; to depressbutton function

case (int)NativeMethods.WindowMessages.WM_MOUSEMOVE:
{
Point clientPoint = PointToWindow(PointToScreen(new Point(m.LParam.ToInt32())));
if (currentButton.Bounds.Contains(clientPoint))
{
if (currentButton.State == CaptionButtonState.Normal)
{
currentButton.State = CaptionButtonState.Pressed;
DrawButton(currentButton);
}
}
else
{
if (currentButton.State == CaptionButtonState.Pressed)
{
currentButton.State = CaptionButtonState.Normal;
DrawButton(currentButton);
}
}
done = false; //added 9/6/05 - fixes the button behavior when pressed and cursor is moved - we still need to loop until the button is released
break;
}


2. Add Sleep(1) to the depressbutton function when the loop starts to control the cpu

while (!done)
{
System.Threading.Thread.Sleep(1); //added to control cpu

You can play with the buttons to see the effects i'm talking about. watch your cpu when holding the button for a few seconds.

# re: Drawing Custom Borders in Windows Forms 9/15/2005 3:48 AM Szymon
Codeable,
No, I haven't done any work with menus. In my current project we don't use traditional menus so there was no need to customize them. But you are right that that this would be huge benefit for anyone to make them consistent with borders and other screen elements. If you have started any work on this and would like to share it I would be happy to include it in this project and help you make them work. I think that we should include support for customized statusbar as well.

# re: Drawing Custom Borders in Windows Forms 9/15/2005 3:55 AM Szymon
Enes M.,
Thanks for your feedback and bug fixes. I will test this out and include with the next release. As for the IntPtr it is "A platform-specific type that is used to represent a pointer or a handle." In C# it can be cast to and from an integer. But it should be all the same if use a constructor like you did. Do you still have problems in VB? You can always try to compile this in C# and reference in your VB project as external assembly. Then you can override the CustomDrawnForm class as needed.

# re: Drawing Custom Borders in Windows Forms 9/15/2005 5:12 PM Codeable
Excellent, I shall finish the main menu functionality, and have some good stuff this weekend. (have to fix a few issues that arise when the form is smaller than the menu)

Enes.. instead of casting an integer, make a new one for that method call:

Dim hDC As IntPtr = NativeMethods.GetDCEx(Me.Handle, New IntPtr(1),....

That should work.

# re: Drawing Custom Borders in Windows Forms 9/17/2005 7:21 PM Andrew
Szymon, I just tested it on Vista Beta 1 and it's no good :(
The border comes up completely black. It sorta' renders if I maximize/restore once but it doesn't draw completely.

So unless something changes by final Vista release, remove from my previous post:
|| os.Version.Major >= 6) // Vista / XP Compatible

I guess this is XP only otherwise. That might change though considering that it "sorta' rendered", meaning that there is something there trying. Still pretty cool to have as an XP feature to my software either way!

# re: Drawing Custom Borders in Windows Forms 9/19/2005 6:08 PM Codeable
Hey Szymon - I've finished the main menu implementation. It's a healthy amount of code update so I don't want to post it here. E-mail me (william@codeable.net) or tell me how to get you the code.

# re: Drawing Custom Borders in Windows Forms 9/23/2005 3:50 PM Enes M
Codeable,
Can you send the source to enes@qwest.net?

# re: Drawing Custom Borders in Windows Forms 9/23/2005 10:12 PM Codeable
En route.

# re: Drawing Custom Borders in Windows Forms 9/24/2005 7:05 PM Louis
Wow this, like many others, has saved me a bunch of time. This really isnt my area of expertise so it has been a learning curve for me which is great. The main problem I'm having is probably really trivial as well. When I change the img's used for the borders they dont change the application. Sounds fairly silly, I know. Even If I remove them all together, its still the original appearence, not even a error. Any suggestions would be muchly appreciated.

cheers,
Louis

# re: Drawing Custom Borders in Windows Forms 9/24/2005 8:42 PM Louis
Alrighty :D, i fixed my simple little problem. It was my Resource1.resx file, I just recreated it using the link on the next page. Hopefully I can contribute to this little project as much as everyone else. I know i'll be ripping through the code here in the next little while.

# re: Drawing Custom Borders in Windows Forms 9/25/2005 3:45 PM Codeable
Enes, Szymon:

Boolean logic bug in that code I sent. "OR instead of AND"

((keyData & Keys.Menu) == Keys.Menu) should be: ((keyData | Keys.Menu) == Keys.Menu)


protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if(this.EnableNonClientAreaPaint && ((keyData | Keys.Menu) == Keys.Menu)) //No menu please. :)
{
//TODO: Add implementation of menu requests.
return true;
}
else
{
return base.ProcessCmdKey(ref msg, keyData);
}
}

# re: Drawing Custom Borders in Windows Forms 9/25/2005 4:02 PM Szymon
Louis,

I'm glad the code was useful for you. At first I though your problem was that you can't change the form's appearance at runtime. I never though this should be needed so the Appearance is read only once when form is constructed. But if you think such feature would be required we can try to add it. Now, I don't fully understand what your problem was and how you fixed. Could you explain?

# re: Drawing Custom Borders in Windows Forms 9/25/2005 4:05 PM Szymon
Codeable,

I'm really sorry but I still haven't got time to take a look at your code (I have hard time at work and was out of town for the weekend), but you are at top of my personal to-do list. I will try to include your code in next release.

# re: Drawing Custom Borders in Windows Forms 9/25/2005 4:44 PM Louis
Szymon,

I was trying to replace the images with my own. But the Resources1.resx was not updating. So It was still using the images you sent with your code. I fixed this by recreating the .resx file manualy using the tools you mentioned on the next page.

# re: Drawing Custom Borders in Windows Forms 9/26/2005 12:35 AM Codeable
As a side note: I've had no problems changing the appearance at runtime. (and when you run the code I sent - you'll see that in action!)

Don't have to put me at the top of the list. :) I already have the code.

# re: Drawing Custom Borders in Windows Forms 9/26/2005 8:10 PM thomas
Great job!!!!

Just missing the DrawUtil.DrawImageTransparent
in CustomBorderForm to paint the NonClientArea and it will be perfect.


# Transparent color 9/26/2005 10:36 PM thomas
Well for a reason i can't rely explain now it seams that to support
transparency the image need to be load from disk

internal static System.Drawing.Bitmap BorderTopLeft
{
get
{
//return ((System.Drawing.Bitmap)(ResourceManager.GetObject("BorderTopLeft", resourceCulture)));
return new System.Drawing.Bitmap("..\\..\\Resources\\BorderTopLeft.png");
}
}

can't explain rely what is the difference except that my screen pixel depth is 32 bits and the image 24 so don't know what does the resource builder when exposing internaly the image.



# Inheritence and designer pb 9/27/2005 5:30 PM thomas
From DemoForm
an exception occurred while trying to create an instance of ....CustomBorderForm. The exception was "Invalid parameter used.".

Any idea why i get this message.



# re: Drawing Custom Borders in Windows Forms 10/7/2005 12:11 AM Codeable
I think I will see if there is a way to tap into the Form's implementation of the main menu and shift it south a bit. It would be much nicer to simply override the onpaint methods of the menu's and items. Then you could include a CustomMenuItem object and have this thing be the "Class to rule them all" haha.

# re: Drawing Custom Borders in Windows Forms 10/21/2005 8:26 AM Robin
Great work! Is it also possible to change the transparency of the NC-Area only?
I'm looking for the new vista look.

# re: Drawing Custom Borders in Windows Forms 10/26/2005 6:40 AM Codeable
Success! I was able to move the windows generated MainMenu down to the "appropriate" area. I did it in a spike, so no code samples yet (but you won't believe how easy it was). I will write it up this weekend using performance friendly customdraw instead of the hacky virtualmenuitems. Who need sleep!?

# re: Drawing Custom Borders in Windows Forms 11/5/2005 3:07 PM some problems i have with this s
first of all , thank you for your good work.

here are the problems.
when i use it as a mdi child form and minimize it then it does not seem as it should.

when i use is as mdi child form and click the control buttons then they dont do what they are supposed to do.

in normal use when i change the title height , it seems again weird.

thankx

# re: Drawing Custom Borders in Windows Forms 11/5/2005 3:36 PM some problems i have with this s
hello again..

cant see the help button is a plus.

i am using net 2.0 beta 2 by the way.

# re: Drawing Custom Borders in Windows Forms 11/25/2005 12:25 AM Timothy Akehurst
Hi,
I am currently trying as a fun project more then anything else to code a 'areo glass' effect around the NC Area of a window, much like in the upcoming vista.
The goal is to use per pixel alpha blended bitmaps. First attemps show them as black, and not showing the background at all (go figure). Using UpdateLayeredWindow will allow us to update the regions we want to paint. The only problem with this apart from effeciency is the fact that you have to set the Extended style of the form to
cp.ExStyle |= 0x00080000; // WS_EX_LAYERED
This will prevent the rest of the form from painting.

This is where I am stuck right now, if anyone has any comments or wishes to see the source that I currently have, drop me a mail at tim.akehurst@noos.fr.

Doing this under XP/2000 is possible. . WindowsBlinds will support the glass look and feel for Windows XP in their next release.

Thanks for your time,

Timothy Akehurst.
Style Cafe SARL, Paris France.

# re: Drawing Custom Borders in Windows Forms 12/3/2005 11:45 AM Jelle van der Beek
Hi,

I've read your article and you mentioned a problem with the BufferedGraphics object:

"...It all looks good when the window stays active, but when it gets covered by another window suddenly all of the client area gets painted in black. So there is something missing, like establishing a clip region to exclude this area from bliting. I hope that someone smarter then me could help and figure out a better way to fix this."

I don't know whether you've resolved this issue yet. In any way, I think you are refering to an issue I've also stumbled upon. BufferedGraphics.Render uses BitBlt internally, which ignores GDI+ clipping regions. I've filed this issue on ProductSupport:

http://lab.msdn.microsoft.com/ProductFeedback/viewfeedback.aspx?feedbackid=9dd77247-66db-4fff-b30e-2a97ba72a89e

To perform clipping, you will have to use GDI regions. This piece of code will create a clipping rectangle which excludes the client area:

private void OnWMNCPaint( ref Message m )
{
IntPtr hDC = NativeMethods.GetWindowDC( m.HWnd );
if( hDC != IntPtr.Zero )
{
IntPtr hrgnWindow = IntPtr.Zero;
IntPtr hrgnClient = IntPtr.Zero;
try
{
// GetClientRectangle should return the clientrectangle's position relative to the control's upper-left corner
Rectangle rect = GetClientRectangle();
hrgnWindow = NativeMethods.CreateRectRgn( 0, 0, Width, Height );
hrgnClient = NativeMethods.CreateRectRgn( rect.Left, rect.Top, rect.Right, rect.Bottom );
NativeMethods.CombineRgn( hrgnWindow, hrgnWindow, hrgnClient, NativeMethods.RGN.RGN_DIFF );
NativeMethods.SelectClipRgn( hDC, hrgnWindow );

using( Graphics g = Graphics.FromHdc( hDC ) )
{
PaintNonClientArea( g );
}
m.Result = IntPtr.Zero;
}
finally
{
NativeMethods.ReleaseDC( m.HWnd, hDC );
if( hrgnWindow != IntPtr.Zero )
{
NativeMethods.DeleteObject( hrgnWindow );
}
if( hrgnClient != IntPtr.Zero )
{
NativeMethods.DeleteObject( hrgnClient );
}
}
}
}

Is that the solution to your clipping problems?

# re: Drawing Custom Borders in Windows Forms 1/12/2006 1:22 PM Martin
Hi, I have strange problem with your sample code... when I rebuild it while the Design of the DemoForm.cs is open, it changes its size! Something adds some width and height to the form. I am using VS 2005 with .net 2.0

# re: Drawing Custom Borders in Windows Forms 1/23/2006 7:06 PM Abe Heidebrecht
This is an excellent job, and has really helped me develop the custom look that my bosses require. Unfortunately, I have run into a strange problem that only occurs when setting the Theme in windows to be the Classic Windows theme. If this is set, and you disable custom borders, and then reenable them, the behavior shows up. What happens is that the default windows controls are drawn over the custom controls in the upper-right corner of the screen when you move the mouse over the resizable borders. Also, if you have a larger than normal title bar, a line is drawn where the bottom of the default titlebar would be. The controls are also drawn on minimize/maximize. I ran Spy++ on the form, and it seems that the windows controls are being drawn in WM_SETCURSOR when the mouse is moved over the border, but it must be drawn in WM_MAXIMIZE and WM_RESTORE for the other two messages. I am running VS 2005, btw.

# re: Drawing Custom Borders in Windows Forms 1/23/2006 7:51 PM Szymon
Abe,
Thanks for the excellent bug report. Following your instructions I was able to reproduce the problem. However currently I'm working on another projects and it would be hard for me to find time to look into this one. As I wrote in the article Windows tries to optimize paintinc non-client area and there are some undocumented places outside WM_NCPAINT where parts of borders are painted. I need to capture all these massages and handle on my own instead of DefProc.

# re: Drawing Custom Borders in Windows Forms 1/25/2006 8:47 PM VJ
I am seeing the same problem as Martin above. The DemonForm size changes each time I build project. ALso using VS2005. Cannot get fixed size for not sizable application Window.


# re: Drawing Custom Borders in Windows Forms 1/25/2006 9:52 PM VJ
Just Confirmed that the form resize problme does not happen in VS2003. There must be something VS2005 is doing in design mode to show the cool new non-client stuff that also causes the size of form to change. Too bad. If anyone finds a fix for this please post...

# re: Drawing Custom Borders in Windows Forms 1/26/2006 9:25 PM Chase
Great work!

It is obvious you put a good amount of time and research into this little project, and the result is very much appreciated.

I have noticed a few things with the project using the latest release under VS 2003 (Win XP SP2); maybe you could tell me what I am doing wrong or at least steer me in the right direction:

1.) If I add a menu to your form, it displays properly in the designer but is painted underneath the titlebar when compiled and executed.

2.) The transparent corners of the upperLeft and upperRight are painting white (not transparent) when compiled and executed.

I have not yet made any source changes, these are just observations from testing the functionality your demo offered.

Thanks for the hard work!

# re: Drawing Custom Borders in Windows Forms 1/28/2006 8:48 PM Joe
Hi
I tried the demo and it is awsome.
Actually I wanted to ask if you were aware of the redimensioning problem ocuring when you press on the Taskbar's application Icon several time.
In fact if you try to do so you'll see the form size increasing, and I couldn't figure out why.
Do you have some clue about the reason?

# re: Drawing Custom Borders in Windows Forms 1/28/2006 9:32 PM Szymon
Joe,
I'm glad you like it.

Regarding 'redimensioning' I'm only aware of some problems when designing forms in VS 2005. Please tell me more of the issue: what .NET and Windows version you use, and do you use XP themes or windows classic.

# re: Drawing Custom Borders in Windows Forms 1/28/2006 10:55 PM Joe
I use XP themes and Visual Studio 2005.
But it isn't happening at design time but at run time:
if I click on the icon in the task bar to reduce or restor the app window, and I repeat the operation several time, the window size increase.
I tried to modify your source code to set the caption height to SystemInformation.CaptionHeight to see if something would change, but without any success.

# re: Drawing Custom Borders in Windows Forms 2/4/2006 8:35 AM Szymon
Hi all,
In case you haven't noticed I just uploaded new release. It fixed several issues reported above (yes, it fixes the redmiensioning bug in designer). So go ahead, download it, and let me know what else needs to be done here.

Thanks for all bug reports.

# re: Drawing Custom Borders in Windows Forms 2/6/2006 7:29 PM Antoine
Hi szymon, great work!
I have a strange problem. When i use your form as mdi child, it seems that i can't touch the header of these forms, i can't move them, minimize or maximize them or even close them.

The only thing it does is when i am at the 3/4 (x-coordinate) at the header (just before the three buttons) the cursor transforms from pointer to WE-resizer...

Has anyone noticed that?!?!

Thanks in advance

# re: Drawing Custom Borders in Windows Forms 2/6/2006 7:54 PM Szymon
Antoine,
As far you are second person I know that want's to use it for MDI forms. To be honest I havent' tested it at all. If time allows I will so what I can do to fix it.

# Problem in rendering Menus 3/12/2006 11:13 AM Anindya Chatterjee
Really nice work!

But I found a littile bit of problem when designing windows form inheriting your class in .net 1.1.

When I add a mainMenu item it get rendered a liitle bit lower of its usual position, just underneath the titlebar! Most amazing thing is that when I run it the menu completely disappeared from the form though it is visible in design mode!!!!

What does the problem lies beneath? PLease any one give me any idea to solve it.
my email: anidotnet@gmail.com

Thnks again

# re: Menus not showing up in .net 1.1 2003 3/25/2006 11:34 AM wing
Hi, I have noticed a few people in here have come accross the same problem as me, main menus not showing up!


They are there, they are just underneath the title bar, which I guess means that the client area is not position in the correct place, or is too big?!?!

I am finding it hard to traverse this code as I do not know much about the windows message loop and underlying API's


If anyone has solved this or could point to the section of code that needs to be modified, could they pls put the answer in here or email me??

jimparslow@hotmail.com


Thanx in advance


Best Regards


wing

# re: Drawing Custom Borders in Windows Forms 3/26/2006 5:55 PM wing
Well... I found the section of code that controls the client area, and changing this had no effect on where the menu was placed....

The menu seems to be attached to the top of the border.


This is a major problem..... and I can't seem to find a work around...

If anyone has managed this... pls let us know!!!

# re: Drawing Custom Borders in Windows Forms 3/27/2006 4:01 AM Szymon
Wing,
This project was orignaly written for .NET 2.0 and I haven't encountered any issues with the new ToolStripMenus. So one solution I recommend is to migrate to 2.0. However, some time ago I recived code for improved menus from Codeable (see discussion above). I haven't included this in my project but if you send me your email I can post it for you.

# re: Drawing Custom Borders in Windows Forms 3/27/2006 8:19 AM wing
Hi Szymon.....

I t would be great if you could email me that code, I have searched for your address but cannot find it...

My email is

jimparslow@hotmail.com
or
jimparslow@gmail.com


either address is good to be sent to.

I eagerly await your response.

Thanx again

wing!!

# To all Having Main Menu issue in 2003 .net V1.1 3/27/2006 9:33 AM wing
I recieved Codeable's code from Szymon, it addresses the problems that I and others have had in using the main menu.


It has a small issue with the "&" before a letter no longer underlining it, so I am unsure if hotkeys will still work... I am going to look into this... but for all who want a copy:

ftp://www.parslow.plus.com/htdocs/VirtualMenu/

It can be found here.

THanx to Codeable for this, and much thanx to Szymon for the initial project!!!

# re: Drawing Custom Borders in Windows Forms 3/27/2006 10:36 AM wing
Scrub that address....

http://www.parslow.plus.com/VirtualMenu/

I forgot to remove the ftp part!! bah!!!

# Mdi Forms hav