How to draw custom title bars on MDI child forms?

G

Guest

I am doing .Net Windows Forms development using VS 2005 and C#. The
application I am working on uses the MDI pattern. The original
implementation was done using .Net 1.1 and VS 2003.

How can I customize the title bar of the child forms such that the following
the following is achieved:
- The normal title bar background can be replaced with a bmp image.
- The normal title bar text is displayed with a specified pen color.
- The normal control box is still used.
- The normal popup menu is still used.
- The child forms can be moved by dragging from the title bar.

I have searched for information on the web, but all that I have found have
various limitations (they don't support all of the items above).

Hopefully, the solution would be confined to modifications to the solution's
ChildForm class (derived from Form).

Thanks,
Dave
 
L

Linda Liu [MSFT]

Hi Dave,
- The normal popup menu is still used.

Do you mean that when you right-click on the title bar of an MDI child
form, a system shortcut menu pops up?

I am performing research on the issue and will get the result back to you
ASAP.

I appreciate your patience!

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
G

Guest

Linda,

Yes, the title bar should provide access to the normal operation of a system
shortcut menu.

Regards,
Dave
 
L

Linda Liu [MSFT]

Hi Dave,

I spent several hours researching on this issue and come to realize that
the only way to get what you want is to draw the non-client area of the MDI
child form by ourselves.

To draw the non-client area of a form, we need to override the WndProc
method of the form and capture the WM_NCPAINT Windows message. I have
managed to accomplish the following tasks:

- The normal title bar background can be replaced with a bmp image.
- The normal title bar text is displayed with a specified pen color.
- The normal control box is still used.
- The child forms can be moved by dragging from the title bar.

As for showing a system menu when right clicking on the title bar, a normal
form has provided such a function internally. But for an MDI child form,
this funciton is not supported inherently. I have tried posting some
Windows messages such as WM_RBUTTONUP, WM_NCLBUTTONDOWN and etc to the MDI
child form to get the system menu to pop up, but without luck.

I think a possible solution may be to call the Win32 API GetSystemMenu and
then copy the system menu items into our own context menu. We could show
our own context menu at last. I haven't tried it out by now, but I'll do it
later.

The following is my sample code so far. There's one shortcoming in this
sample code that I haven't overcome until now, i.e. the intrinsic Minimize,
Maximize and Close buttons on the title bar don't appear when the MDI child
form shows for the first time, but appears when the user moves the mouse
into the MDI child form.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

int titleheight = 0;
int borderwidth = 0;

int WM_NCACTIVATE = 0x0086;
int WM_NCPAINT = 0x0085;
int WM_PAINT = 0x000F;
int WM_NCLBUTTONDOWN = 0xA1;

[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

Rectangle m_rect = new Rectangle(6, 6, 20, 20);

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if (m.Msg == WM_NCPAINT || m.Msg == WM_NCACTIVATE)
{
DrawFrame();
}
}

private void DrawFrame()
{
IntPtr hdc = GetWindowDC(this.Handle);
using (Graphics g = Graphics.FromHdc(hdc))
{
g.FillRectangle(Brushes.Blue, 0, 0, this.Width,
titleheight);
g.FillRectangle(Brushes.Blue, 0, titleheight, borderwidth,
this.Height - titleheight);
g.FillRectangle(Brushes.Blue, borderwidth, this.Height -
borderwidth, this.ClientSize.Width, borderwidth);
g.FillRectangle(Brushes.Blue, this.Width - borderwidth,
titleheight, borderwidth, this.Height - titleheight);
g.DrawString(this.Text, this.Font, Brushes.Coral, 35, 8);
g.FillRectangle(new LinearGradientBrush(m_rect, Color.Pink,
Color.Purple, LinearGradientMode.BackwardDiagonal), m_rect);
StringFormat strFmt = new StringFormat();

strFmt.Alignment = StringAlignment.Center;

strFmt.LineAlignment = StringAlignment.Center;

g.DrawString("¡Ì", this.Font, Brushes.BlanchedAlmond,
m_rect,strFmt);


}
ReleaseDC(this.Handle, hdc);
}

private void Form2_Load(object sender, EventArgs e)
{
titleheight = this.Height - this.ClientSize.Height -
borderwidth - 8;
borderwidth = (this.Width - this.ClientSize.Width) / 2;

}
}

I will go on the research and would get the result back to you ASAP.
I appreciate your patience!

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

Linda,

I tried the solution you provided so far and noted the same issues you
raised as well as some others. So I simplified the DrawFrame routine while
at the same time providing more contrast to what was happening. Here is my
DrawFrame method:

private void DrawFrame()
{
int frameWidth = this.Width;
int frameHeight = this.Height - this.ClientSize.Height - borderwidth;
int textWidth = frameWidth - 2 * borderwidth;
int textHeight = frameHeight - 2 * borderwidth;

IntPtr hdc = User32DllImports.GetWindowDC( this.Handle );
using ( Graphics g = Graphics.FromHdc( hdc ) )
{
g.FillRectangle( Brushes.DarkBlue, 0, 0, frameWidth, frameHeight );
g.FillRectangle( Brushes.LightBlue, borderwidth, borderwidth,
textWidth, textHeight );
g.DrawString( this.Text, this.Font, Brushes.Yellow, 28, 3 );
}
User32DllImports.ReleaseDC( this.Handle, hdc );
}


I observed the following while trying the solution in my application:

1. If the MDI application is iconified and restored, the child form's
original title bar is drawn, not the new title bar.
2. The new title bar is drawn if I move the child form up in the client area.
3. The title bar's control box buttons (Minimum, Close, etc.) are drawn one
by one as the mouse moves over their locations. However, they disappear if
the child form is moved up.
4. The icon associated with the child form is only drawn when the old title
bar is displayed. It does not appear when the new title bar is displayed and
the mouse is moved over its location, which is different than the control box
behavior.


As for the system menu not working, don't spend too much time on it. It is
just a want, not a must requirement.

Thanks,
Dave

--
Dave Leach
Agilent Technologies, Inc.


Linda Liu said:
Hi Dave,

I spent several hours researching on this issue and come to realize that
the only way to get what you want is to draw the non-client area of the MDI
child form by ourselves.

To draw the non-client area of a form, we need to override the WndProc
method of the form and capture the WM_NCPAINT Windows message. I have
managed to accomplish the following tasks:

- The normal title bar background can be replaced with a bmp image.
- The normal title bar text is displayed with a specified pen color.
- The normal control box is still used.
- The child forms can be moved by dragging from the title bar.

As for showing a system menu when right clicking on the title bar, a normal
form has provided such a function internally. But for an MDI child form,
this funciton is not supported inherently. I have tried posting some
Windows messages such as WM_RBUTTONUP, WM_NCLBUTTONDOWN and etc to the MDI
child form to get the system menu to pop up, but without luck.

I think a possible solution may be to call the Win32 API GetSystemMenu and
then copy the system menu items into our own context menu. We could show
our own context menu at last. I haven't tried it out by now, but I'll do it
later.

The following is my sample code so far. There's one shortcoming in this
sample code that I haven't overcome until now, i.e. the intrinsic Minimize,
Maximize and Close buttons on the title bar don't appear when the MDI child
form shows for the first time, but appears when the user moves the mouse
into the MDI child form.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

int titleheight = 0;
int borderwidth = 0;

int WM_NCACTIVATE = 0x0086;
int WM_NCPAINT = 0x0085;
int WM_PAINT = 0x000F;
int WM_NCLBUTTONDOWN = 0xA1;

[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

Rectangle m_rect = new Rectangle(6, 6, 20, 20);

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if (m.Msg == WM_NCPAINT || m.Msg == WM_NCACTIVATE)
{
DrawFrame();
}
}

private void DrawFrame()
{
IntPtr hdc = GetWindowDC(this.Handle);
using (Graphics g = Graphics.FromHdc(hdc))
{
g.FillRectangle(Brushes.Blue, 0, 0, this.Width,
titleheight);
g.FillRectangle(Brushes.Blue, 0, titleheight, borderwidth,
this.Height - titleheight);
g.FillRectangle(Brushes.Blue, borderwidth, this.Height -
borderwidth, this.ClientSize.Width, borderwidth);
g.FillRectangle(Brushes.Blue, this.Width - borderwidth,
titleheight, borderwidth, this.Height - titleheight);
g.DrawString(this.Text, this.Font, Brushes.Coral, 35, 8);
g.FillRectangle(new LinearGradientBrush(m_rect, Color.Pink,
Color.Purple, LinearGradientMode.BackwardDiagonal), m_rect);
StringFormat strFmt = new StringFormat();

strFmt.Alignment = StringAlignment.Center;

strFmt.LineAlignment = StringAlignment.Center;

g.DrawString("¡Ì", this.Font, Brushes.BlanchedAlmond,
m_rect,strFmt);


}
ReleaseDC(this.Handle, hdc);
}

private void Form2_Load(object sender, EventArgs e)
{
titleheight = this.Height - this.ClientSize.Height -
borderwidth - 8;
borderwidth = (this.Width - this.ClientSize.Width) / 2;

}
}

I will go on the research and would get the result back to you ASAP.
I appreciate your patience!

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

Linda,

What's up? I haven't heard from you for a couple days. Have you found
anything new on how to do the custom title bar?

Thanks,
Dave

--
Dave Leach
Agilent Technologies, Inc.


Linda Liu said:
Hi Dave,

I spent several hours researching on this issue and come to realize that
the only way to get what you want is to draw the non-client area of the MDI
child form by ourselves.

To draw the non-client area of a form, we need to override the WndProc
method of the form and capture the WM_NCPAINT Windows message. I have
managed to accomplish the following tasks:

- The normal title bar background can be replaced with a bmp image.
- The normal title bar text is displayed with a specified pen color.
- The normal control box is still used.
- The child forms can be moved by dragging from the title bar.

As for showing a system menu when right clicking on the title bar, a normal
form has provided such a function internally. But for an MDI child form,
this funciton is not supported inherently. I have tried posting some
Windows messages such as WM_RBUTTONUP, WM_NCLBUTTONDOWN and etc to the MDI
child form to get the system menu to pop up, but without luck.

I think a possible solution may be to call the Win32 API GetSystemMenu and
then copy the system menu items into our own context menu. We could show
our own context menu at last. I haven't tried it out by now, but I'll do it
later.

The following is my sample code so far. There's one shortcoming in this
sample code that I haven't overcome until now, i.e. the intrinsic Minimize,
Maximize and Close buttons on the title bar don't appear when the MDI child
form shows for the first time, but appears when the user moves the mouse
into the MDI child form.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}

int titleheight = 0;
int borderwidth = 0;

int WM_NCACTIVATE = 0x0086;
int WM_NCPAINT = 0x0085;
int WM_PAINT = 0x000F;
int WM_NCLBUTTONDOWN = 0xA1;

[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

Rectangle m_rect = new Rectangle(6, 6, 20, 20);

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if (m.Msg == WM_NCPAINT || m.Msg == WM_NCACTIVATE)
{
DrawFrame();
}
}

private void DrawFrame()
{
IntPtr hdc = GetWindowDC(this.Handle);
using (Graphics g = Graphics.FromHdc(hdc))
{
g.FillRectangle(Brushes.Blue, 0, 0, this.Width,
titleheight);
g.FillRectangle(Brushes.Blue, 0, titleheight, borderwidth,
this.Height - titleheight);
g.FillRectangle(Brushes.Blue, borderwidth, this.Height -
borderwidth, this.ClientSize.Width, borderwidth);
g.FillRectangle(Brushes.Blue, this.Width - borderwidth,
titleheight, borderwidth, this.Height - titleheight);
g.DrawString(this.Text, this.Font, Brushes.Coral, 35, 8);
g.FillRectangle(new LinearGradientBrush(m_rect, Color.Pink,
Color.Purple, LinearGradientMode.BackwardDiagonal), m_rect);
StringFormat strFmt = new StringFormat();

strFmt.Alignment = StringAlignment.Center;

strFmt.LineAlignment = StringAlignment.Center;

g.DrawString("¡Ì", this.Font, Brushes.BlanchedAlmond,
m_rect,strFmt);


}
ReleaseDC(this.Handle, hdc);
}

private void Form2_Load(object sender, EventArgs e)
{
titleheight = this.Height - this.ClientSize.Height -
borderwidth - 8;
borderwidth = (this.Width - this.ClientSize.Width) / 2;

}
}

I will go on the research and would get the result back to you ASAP.
I appreciate your patience!

Sincerely,
Linda Liu
Microsoft Online Community Support
 
L

Linda Liu [MSFT]

Hi Dave,

Sorry for my delayed reply!

I do more research and tests and find more questions. To prevent the
MinimizeButton, MaximizeButton and CloseButton from appearing when the
cursor enters into the MDI child form, we have to catch the WM_SETCURSOR,
WM_NCMOUSEMOVE and WM_NCLBUTTONDOWN Windows messages and process these
messages by ourselves, i.e. don't rely on the default process.

It means that we must implement the following functions by ourselves:
1. Detect when the user tries to click the icon,
MinimizeButton,MaximizeButton and CloseButton on the title bar, and
accomplish the corresponding task.
2. Move the MDI child form when the user drags the title bar.
3. Resize the MDI child form when the user drags one of the form's border.

I have managed to accomplish almost all the above tasks. But there're still
some shortcomings to overcome in my sample code. For example, the cursor
doesn't change its shape when the user moves it on any of the form's
border, because I have filtered the WM_SETCURSOR Windows message to prevent
the MinimizeButton, MaximizeButton and CloseButton from appearing. Another
shortcoming is that the form doesn't work well when the user is dragging
any of the form's border to resize it.

I send my sample project to your email box and I look forward to your
feedback.

I will continue to improve my sample code.

Thank you for your patience!

Sincerely,
Linda Liu
Microsoft Online Community Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top