Click here to Skip to main content
Email Password   helpLost your password?

MdiClient Demo Application

Contents

Introduction

In a recent project, I decided that using a multiple-document interface (MDI) would be the best approach. I was pleasantly surprised by how easy creating an MDI application in Visual Studio and on the .NET platform is. Simply setting the IsMdiContainer property of the System.Windows.Forms.Form allows other forms to be hosted in the application workspace. If you're like me, however, you begin to wonder what that workspace would look like with a different color, custom painting, or maybe a different border style. I quickly found that the Form control exposed no such properties to control this behavior. A search of the web revealed that many others have desired to do the same and had various approaches on how to accomplish this. After using their suggestions successfully in my application and creating a few of my own, I decided to collect all such information into one place and perhaps develop a component that would allow easy setting of these properties.

The MdiClient Control

As it turns out, the MDI area of a Windows Form is just another control. When the IsMdiContainer property is set to true, a control of type System.Windows.Forms.MdiClient is added to the Controls collection of the Form. Iterating through the Form's controls after loading will reveal the MdiClient control and is also probably the best way to get a reference to it. The MdiClient control does have a public constructor and could be added to the Form's Controls collection programmatically, but a better practice is to set the Form's IsMdiContainer property and have it do the work. To set a reference to the MdiClient control, iterate through the controls until the MdiClient control is found:

MdiClient mdiClient = null;

// Get the MdiClient from the parent form.

for(int i = 0; i < parentForm.Controls.Count; i++)
{
    // If the form is an MDI container, it will contain an MdiClient control

    // just as it would any other control.

    mdiClient = parentForm.Controls[i] as MdiClient;
    if(mdiClient != null)
    {
        // The MdiClient control was found.

        // ...

        //


        break;
    }
}

Using the as keyword here is better than a direct cast in a try/catch block or using the is keyword, because if the type is a match, a reference to the control will result or null will be returned. It's like getting two calls for the price of one.

Note: In tests, I discovered that it is possible to add more than one MdiClient control to the Form's Controls collection. In such a case, only one of the MdiClient controls will act as the host and this code may fail by returning a reference to the MdiClient that is not performing the hosting of child forms. One more good reason to use the IsMdiContainer property rather than adding the control to the form manually.

What a Reference Can Do

Changing the Background Color

With a reference to the MdiClient control in hand, many of the common control properties can be set as you would expect. The most often requested of course is changing the background color. The default background color of the application workspace is global to all Windows applications and can be changed in the Control Panel. The .NET framework exposes this color in the System.Drawing.SystemColors.AppWorkspace static property. Changing the background color is done as you would expect, through the BackColor property:

// Set the color of the application workspace.

mdiClient.BackColor = value;

That as well as many properties common to other controls will work as expected with the MdiClient control.

What a Handle Can Do

Changing the Border Styles

What's absent from the MdiClient control, however, is a BorderStyle property. Gone are the typical System.Windows.Forms.BorderStyle enumeration options of Fixed3D, FixedSingle, and None. By default, the application workspace of a MDI form is inset with a 3D border equivalent to what would be Fixed3D. Just because this behavior is not exposed by the control does not mean it is not accessible. From this point forward, you will see that the Handle of the MdiClient becomes much more valuable than just a reference to it.

To change the appearance of the border requires the use of Win32 function calls. (More information on this can be gleaned from Jason Dorie's article: Adding designable borders to user controls.) Each window (i.e. - Control) in Windows has information that can be retrieved by using the GetWindowLong and set by using the SetWindowLong function. Both functions require a flag that specifies what information we would like to get and set. In this case, we are interested in the GWL_STYLE and the GWL_EXSTYLE, which get and set the window style and the extended window style flags, respectively. Because these changes are made to the non-client area of the control, calling the control's Invalidate method will not cause the borders to be repainted. Instead, we call the SetWindowPos function to cause an update of the non-client area. These functions and constants are defined like this:

// Win32 Constants

private const int GWL_STYLE   = -16;
private const int GWL_EXSTYLE = -20;

private const int WS_BORDER        = 0x00800000;
private const int WS_EX_CLIENTEDGE = 0x00000200;

private const uint SWP_NOSIZE           = 0x0001;
private const uint SWP_NOMOVE           = 0x0002;
private const uint SWP_NOZORDER         = 0x0004;
private const uint SWP_NOREDRAW         = 0x0008;
private const uint SWP_NOACTIVATE       = 0x0010;
private const uint SWP_FRAMECHANGED     = 0x0020;
private const uint SWP_SHOWWINDOW       = 0x0040;
private const uint SWP_HIDEWINDOW       = 0x0080;
private const uint SWP_NOCOPYBITS       = 0x0100;
private const uint SWP_NOOWNERZORDER    = 0x0200;
private const uint SWP_NOSENDCHANGING   = 0x0400;


// Win32 Functions

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int Index);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value);

[DllImport("user32.dll", ExactSpelling = true)]
private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
    int X, int Y, int cx, int cy, uint uFlags);

Note: The values of these constants are defined in the Winuser.h header file, which is usually installed by the Platform SDK or Visual Studio .NET.

We can adjust the border according to the BorderStyle enumeration by: including a WS_EX_CLIENTEDGE flag in the extended window styles (Fixed3D), a WS_BORDER flag in the standard window styles (FixedSingle), or removing both of these flags for no border (None). Then call the SetWindowPos function to cause an update. The SetWindowPos function has many options, but we want nothing more than to repaint the non-client area and will pass in the flags necessary to do this:

// Get styles using Win32 calls

int style = GetWindowLong(mdiClient.Handle, GWL_STYLE);
int exStyle = GetWindowLong(mdiClient.Handle, GWL_EXSTYLE);

// Add or remove style flags as necessary.

switch(value)
{
    case BorderStyle.Fixed3D:
        exStyle |= WS_EX_CLIENTEDGE;
        style &= ~WS_BORDER;
        break;

    case BorderStyle.FixedSingle:
        exStyle &= ~WS_EX_CLIENTEDGE;
        style |= WS_BORDER;
        break;

    case BorderStyle.None:
        style &= ~WS_BORDER;
        exStyle &= ~WS_EX_CLIENTEDGE;
        break;
}

// Set the styles using Win32 calls

SetWindowLong(mdiClient.Handle, GWL_STYLE, style);
SetWindowLong(mdiClient.Handle, GWL_EXSTYLE, exStyle);

// Update the non-client area.

SetWindowPos(mdiClient.Handle, IntPtr.Zero, 0, 0, 0, 0,
    SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
    SWP_NOOWNERZORDER | SWP_FRAMECHANGED);

What a Window Message Can Do

The NativeWindow Class

To move into the realm of customization beyond changing simple properties or making Win32 calls, we need to intercept and process window messages. Unfortunately, the MdiClient class is sealed, and therefore, can't be subclassed nor can its WndProc method be overridden. Thankfully, the System.Windows.Forms.NativeWindow class comes to the rescue. The intent of the NativeWindow class is to provide "a low-level encapsulation of a window handle and a window procedure". In other words, it allows us to tap into the window messages a control receives. To make use of NativeWindow, inherit from the class and override its WndProc method. Once a control's handle is assigned to the NativeWindow via the AssignHandle method, the WndProc method behaves just as if it was the control's WndProc method. With the ability to listen to the MdiClient control's window messages, a whole new range of customization is possible.

Hiding the Scrollbars

While making controls outside the application workspace accessible by scrollbars is a great feature, I personally can't remember an MDI application I've used that does the same. Turning off or hiding the scrollbars in the MdiClient is a feature that may be more often requested than changing its color.

The scrollbars of the MdiClient control are part of its non-client area (area outside the ClientRectangle) and are not themselves controls parented to the MdiClient. That rules out the possibility of changing the visibility of the scrollbars and leaves us with window messages and Win32 functions that affect the size of the non-client area. When the non-client area of a control needs to be calculated, the control is sent a WM_NCCALCSIZE message. In order to hide the scrollbars, we could tell Windows that the non-client area is a little bit smaller than it actually is and cover up the scrollbars. My first approach to this was a failed attempt at trying to determine what the size of the non-client area should be. A much better approach would be to hide the scrollbars when the non-client area is calculated using the ShowScrollBar Win32 function. The ShowScrollBar function requires the window handle, the scrollbars to be hidden, and a bool indicating its visibility:

// Win32 Constants

private const int SB_HORZ = 0;
private const int SB_VERT = 1;
private const int SB_CTL  = 2;
private const int SB_BOTH = 3;

// Win32 Functions

[DllImport("user32.dll")]
private static extern int ShowScrollBar(IntPtr hWnd, int wBar, int bShow);

protected override void WndProc(ref Message m)
{
    switch(m.Msg)
    {
        //

        // ...

        //


        case WM_NCCALCSIZE:
            ShowScrollBar(m.HWnd, SB_BOTH, 0 /*false*/);
            break;
    }

    base.WndProc(ref m);
}

After hiding the scrollbar, the WM_NCCALCSIZE message is processed as usual and calculates the non-client area less the recently hidden scrollbars. In case you're wondering, hiding the scrollbar via the ShowScrollBar function does not keep the scrollbar hidden and is immediately reset to visible. That is why it must be hidden every time the non-client area is calculated.

Advanced Painting

In .NET forums around the web, another common request I see is, "How do I put an image in the application workspace of an MDI form?" The easiest way is to listen to the Paint event once you have a reference to the MdiClient. For some situations this may work fine, but I noticed a very bad flicker every time the MdiClient is resized. This is a result of the painting not being double-buffered and painting calls being made in both the WM_PAINT and WM_ERASEBKGND messages. If we had been able to inherit from the MdiClient control, this could be easily remedied by using the control's protected method SetStyle with the flags System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, ControlStyles.DoubleBuffer, and ControlStyles.UserPaint. But as noted earlier, the MdiClient class is sealed and that is not an option. What is an option is listening to the WM_PAINT and WM_ERASEBKGND window messages and implementing our own custom painting. (More information on this is available in Steve McMahon's article: Painting in the MDI Client Area.)

The Win32 items we'll need are the functions BeginPaint and EndPaint, structs called the PAINTSTRUCT and RECT, and a few more constants:

// Win32 Constants

private const int WM_PAINT       = 0x000F;
private const int WM_ERASEBKGND  = 0x0014;
private const int WM_PRINTCLIENT = 0x0318;


// Win32 Structures

[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct PAINTSTRUCT
{
    public IntPtr hdc;
    public int fErase;
    public RECT rcPaint;
    public int fRestore;
    public int fIncUpdate;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)] 
    public byte[] rgbReserved;
}

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

// Win32 Functions

[DllImport("user32.dll")]
private static extern IntPtr BeginPaint(IntPtr hWnd, 
                                ref PAINTSTRUCT paintStruct);

[DllImport("user32.dll")]
private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT paintStruct);

The typical method of double-buffering is to do all painting to an Image, or rather, get the Graphics object from an Image instead of painting directly to the screen. When painting to the Image is complete then the Image itself is drawn to the screen. That way, all the control's painting is displayed at once instead of intermittent painting that may be in progress. With the MdiClient control's graphics being so simple, we could easily do all the painting ourselves, but a better practice is to not eliminate the base graphics from being drawn, but to incorporate them into our custom painting. That way, if the MdiClient were somehow changed in a way we did not expect, the painting should still be correctly displayed. This is achieved by creating our own window message (WM_PRINTCLIENT) and sending it to the base control using the DefWndProc (i.e. - Default WndProc) method. What we get back in the graphics buffer is a painting of the original control painted by the base control. (More on this can be learned from J Young's article: Generating missing Paint event for TreeView and ListView controls.) From there, any custom painting over top of it can be processed:

protected override void WndProc(ref Message m)
{
    switch(m.Msg)
    {
        //Do all painting in WM_PAINT to reduce flicker.

        case WM_ERASEBKGND:
            return;

        case WM_PAINT:

            // Use Win32 to get a Graphics object.

            PAINTSTRUCT paintStruct = new PAINTSTRUCT();
            IntPtr screenHdc = BeginPaint(m.HWnd, ref paintStruct);

            using(Graphics screenGraphics = Graphics.FromHdc(screenHdc)) 
            {
                // Double-buffer by painting everything to an image and

                // then drawing the image.

                int width = (mdiClient.ClientRectangle.Width > 0 ? 
                                   mdiClient.ClientRectangle.Width : 0);
                int height = (mdiClient.ClientRectangle.Height > 0 ? 
                                  mdiClient.ClientRectangle.Height : 0);
                using(Image i = new Bitmap(width, height))
                {
                    using(Graphics g = Graphics.FromImage(i))
                    {
                        // Draw base graphics and raise the base Paint event.

                        IntPtr hdc = g.GetHdc();
                        Message printClientMessage =
                            Message.Create(m.HWnd, WM_PRINTCLIENT, 
                                                 hdc, IntPtr.Zero);  
                        DefWndProc(ref printClientMessage);
                        g.ReleaseHdc(hdc);

                        //

                        // Custom painting here...

                        //

                    }

                    // Now draw all the graphics at once.

                    screenGraphics.DrawImage(i, mdiClient.ClientRectangle);
                }
            }

            EndPaint(m.HWnd, ref paintStruct);
            return;
    }

    base.WndProc(ref m);
}

Note: More information about the BeginPaint, EndPaint, PAINTSTRUCT, RECT, and WM_PRINTCLIENT can be found in the Platform SDK or MSDN library.

Notice that in this case, we do not let the WM_PAINT message fall through to be processed by the base WndProc because that would cause it to do its default painting right over what we had just done ourselves. The WM_ERASEBKGND message is ignored because we want to do all the painting at one time in the WM_PAINT message. Now, the MdiClient control's Paint event will no longer flicker and custom painting code can be placed in the processing of the WM_PAINT message above.

The MdiClientController Component

Rather than having to put this code into every project that is using a multiple-document interface, we could wrap this all up into a System.ComponentModel.Component that could be copied from project-to-project and dropped onto the design surface. Included in the source files is a component I call the MdiClientController and is found in the Slusser.Components namespace. The component inherits from NativeWindow and implements the System.ComponentModel.IComponent interface to give it its Component behavior. It incorporates all the functionality discussed previously with the addition of some properties that make it easy to place an Image in the application workspace.

To use the component with a MDI form, only the parent Form must be passed in to the constructor or set through the ParentForm property. To set the MdiClientController component's ParentForm property in the designer, we have to customize the Site property to determine if the component is dropped onto a Form. It helps here to have a knowledge of Designers. If indeed the component is dropped onto Form, we set the ParentForm property and it is properly serialized in the designer code:

public ISite Site
{
    get { return site; }
    set
    {
        site = value;

        if(site == null)
            return;

        // If the component is dropped onto a form during design-time,

        // set the ParentForm property.

        IDesignerHost host = 
          (value.GetService(typeof(IDesignerHost)) as IDesignerHost);
        if(host != null)
        {
            Form parent = host.RootComponent as Form;
            if(parent != null)
                ParentForm = parent;
        }
    }
}

One of the challenges in creating this component is knowing when the component will be initialized. Components dropped onto the designer are initialized in the InitializeComponent method of the Form's constructor. If you inspect the InitializeComponent method created by the designer, you'll note that the Form's properties are the last thing to be set. If the MdiClientController were to scan for the MdiClient control in the Form's Controls collection before the Form's IsMdiContainer property is set, no MdiClient control would be found. The solution is to know when the parent Form's Handle is created. This will surely indicate that all child controls and variables have been initialized and when we can start to look for the MdiClient. If the parent form does not have a Handle when the ParentForm property is set, the component will listen to the Form's HandleCreated event and get the MdiClient then:

public Form ParentForm
{
    get { return parentForm; }
    set
    {
        // If the ParentForm has previously been set,

        // unwire events connected to the old parent.

        if(parentForm != null)
            parentForm.HandleCreated -= 
              new EventHandler(ParentFormHandleCreated);

        parentForm = value;

        if(parentForm == null)
            return;

        // If the parent form has not been created yet,

        // wait to initialize the MDI client until it is.

        if(parentForm.IsHandleCreated)
        {
            InitializeMdiClient();
            RefreshProperties();
        }
        else
            parentForm.HandleCreated += 
              new EventHandler(ParentFormHandleCreated);
    }
}


private void ParentFormHandleCreated(object sender, EventArgs e)
{
    // The form has been created, unwire the event,

    // and initialize the MdiClient.

    parentForm.HandleCreated -= 
         new EventHandler(ParentFormHandleCreated);
    InitializeMdiClient();
    RefreshProperties();
}

Using the Component

Once the MdiClientController has been added to the toolbox, simply drag it onto the Form in the designer, or double-click it and it will be displayed in the component tray of the designer. The MdiClientController will not change the Form's IsMdiContainer property, so you must set it. All of the component's properties follow the .NET naming conventions. The border style functionality is wrapped up in the BorderStyle property. The hiding of the scrollbars, I thought, was best put in an AutoScroll property. The BackColor and Paint events are now accessible from the designer for your convenience. In addition, there are three properties that control the displaying of an Image in the client area. The Image property sets the Image to display, the ImageAlign property will place it in different locations of the client area, and the StretchImage property will stretch it to fill the entire client area. In addition, I've added a HandleAssigned event to indicate when the MdiClient has been found and its Handle assigned to the NativeWindow. Of course, all this can be done programmatically.

Conclusion

As with many projects that become articles, I had what I originally needed in about 30 minutes, but spent several days preparing something that I could share with my fellow programmers. The resulting component should suffice for the majority of requests regarding the appearance of MDI forms. It works nicely, it plays nicely, and it makes applications look nice[ly]. There is still a great deal more that could be added to the component if the need arises, which I'm sure for some programmers, it will. There is one feature, or hurdle rather, that I humbly admit I was not able to overcome: design-time preview. Using Reflector, I discovered a great number of roadblocks that prevent the design-time preview of the MDI area. I would welcome any suggestions on how to overcome this. Enjoy.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralGreat article but the double buffering does not work (here's code will make it work) [modified]
paulsasik
4:59 16 Mar '10  
The double buffering featured in this article did not work for me. If you're just interested in enabling double buffering in your MdiClient, I did find a workaround:
private void ForceDoubleBuffering(object o)
{
Control ctrl;
Reflection.MethodInfo method;
Reflection.BindingFlags flags;
ctrl = o as Control;
flags = Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic;
method = ctrl.GetType().GetMethod("SetStyle", flags);
method.Invoke(ctrl, new object[] { ControlStyles.OptimizedDoubleBuffer, true });
}
To use this code simply pass a reference to you MdiClient object into this method and it will be set to double buffer via a reflection method.
modified on Tuesday, March 16, 2010 10:12 AM

GeneralI think there is a bug
redstone_cn
22:22 9 Mar '10  
private void InitializeMdiClient()
{
// If the mdiClient has previously been set, unwire events connected
// to the old MDI.
if(mdiClient != null)
mdiClient.HandleDestroyed -= new EventHandler(MdiClientHandleDestroyed);

if(parentForm == null)
return;

// Get the MdiClient from the parent form.
for(int i = 0; i < parentForm.Controls.Count; i++)
{
// If the form is an MDI container, it will contain an MdiClient control
// just as it would any other control.
mdiClient = parentForm.Controls[i] as MdiClient;
if(mdiClient != null)
{
// Assign the MdiClient Handle to the NativeWindow.
ReleaseHandle();
AssignHandle(mdiClient.Handle);

// Raise the HandleAssigned event.
OnHandleAssigned(EventArgs.Empty);

// Monitor the MdiClient for when its handle is destroyed.
mdiClient.HandleDestroyed += new EventHandler(MdiClientHandleDestroyed);
//
// Smile here need return, I think
return;
}
}
}
GeneralVery good job thank you
redstone_cn
17:16 9 Mar '10  
Smile Very good job thank you
GeneralI have another solution
Chaser92
3:50 16 Feb '10  
Hi, thank you very much for your work, but apparently I did not manage to get so much better performance. here is my (quite lame) solution:
you make a childform with borderstyle=none, set the wallpaper on it and layout, of course doublebuffer=true
you display the childform with position = (0,0) relative to the MDIForm. you add a handler to the MDIForm.Resize event so that you resize the child form according to new MDI form size.
Disable the possibility of closing your wallpaper-child-form by using FormClosing event of it!
Add SendToBack() call to the Activated event and here you go !

Maybe it is lame but works for me and requires almost no time to do something like this, and you may want to go ahead and create objects (for example a bottom-right logo of your company)
I have hardly any flickering now so I hope you enjoy Smile

Greetings
GeneralThank you very much!
tomi_2
7:37 27 Mar '09  
I just wanted to say a big THANK YOU.

You deserve more than one Wink http://www.hallmark.com/wcsstore/HallmarkStore/images/products/ecards/nfg2687.swf

Tomi

GeneralMDI flicker problem fixed
jefrubio
9:37 19 Feb '09  
This component is a great piece of code. Subclassing the MDIClient you can fix a common flicker problem in MDI applications when you create or activate an MDI child form in maximized state.
In WndProc add an extra case:
protected override void WndProc(ref Message m)
{
switch(m.Msg):
{
// Existing code ...
case WM_MDIACTIVATE:
if (this.parentForm.ActiveMdiChild.Handle != m.WParam)
{
int pos = -1;
for (int i = 0; i &lt; this.mdiClient.Controls.Count; i++)
{
if (mdiClient.Controls[i].Handle == m.WParam)
{
pos = i;
break;
}
}
if (pos < 0)
throw new InvalidOperationException("MDIChild form not found");

Control form = null; // next or previous MDIChild form
if (pos == 0)
form = mdiClient.Controls[1]; // get next and activate previous
else form = mdiClient.Controls[pos - 1]; // get previous and activate next
// flag indicating whether to activate previous or next MDIChild
IntPtr direction = new IntPtr(pos == 0 ? 1 : 0);
SendMessage(mdiClient.Handle, WM_MDINEXT, form.Handle, direction);
}
return;
}
base.WndProc(ref m);
}
Also you need to add the following declarations in the MDIClientControler class:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

private const int WM_MDINEXT = 0x224;
private const int WM_MDIACTIVATE = 0x0222;
This code is extracted from Workaround for flicker/flashing when programmatically activating MDI child forms.
GeneralRe: MDI flicker problem fixed
stmarti
22:20 3 Nov '09  
This not works at all, because I get StackoverFlowException each time when I try to show an mdi child.
General5 is not enough.
MAHorn
6:05 4 Mar '08  
This is an excellent summary that points out numerous pesky MDI problems. Well done.
GeneralCauses conflicts with other windows
REA_ANDREW
3:52 1 Mar '08  
Hi,

Great code, but I think I may have found a small issue which could be large I am not sure. Basically I have implmented your code and great, everything works as I would like it to. While my app is running however, I goto IE7 -> Tools -> Internet Options and when click through the tabs they dissappear.

Same on Outlook, I have narrowed down an example to the following, I I try and create a new Account all the dialogue form controls like labels and eveyrything basically are all messed up and either not visible or all over the place.

What piece of your code could cause such a conflict?
Thanks

Andrew
Questionresize MdiClient
bb23
21:09 15 Oct '07  
Is it possible to change the size of an MdiClient and it's other properties like Dock? Getting a handle and setting values to the properties does not seem to work.
GeneralLife Saver
seanco
23:58 21 Jul '07  
just a quick thanks for the information provided regarding turning off scrollbars, this facet can be used to controll scrollbar visibility on a scrollable control and has saved me alot of contriving and hacking to produce a good result so thanks be to you...

Regards,
Sean

Protected Overloads Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_NCCALCSIZE
ShowScrollBar(m.HWnd, SB_BOTH, 0)
' Exit Select
End Select

MyBase.WndProc(m)
End Sub

Cool
GeneralRe: Life Saver
Jacob Slusser
5:55 26 Jul '07  
Big Grin Glad I could help. Big Grin

Jacob


Generalbugs
royan
2:20 4 Jun '06  
Hi very nice code
but there are some problems with the it
1)when the scroll are used the picture is not fully redraw
2)when the focus is on the scroll and you want to go back to a child window only the active window can be selected other are just not respanding.
GeneralMdi Within MDI
Maurice Flanagan
10:02 5 Apr '06  
Great article. Have you found a way to contain a MDI container within another MDI container by dropping down to the NativeWindow level? Windows Forms does not let you do this out of the box but I have a feeling it's possible if we wire up the native window handles appropriately.

Thanks

GeneralJust What We Needed!
Craig Hunter
15:32 20 Oct '05  
Great article Jacob and very well written.

Brought your component in, and it worked perfectly for Paint customisation.

Many thanks! Smile

Generalstrange exceptions
zu78
9:41 21 Aug '05  
After integrating your article in my mdi app, i got the following exception. This exception occours
in unpredictable way: the only common factor I've noticed is that it throws on gui repaint events.

Can you help me with this ?

The object is currently in use elsewhere.
at System.Drawing.Graphics.Dispose(Boolean disposing)
at System.Drawing.Graphics.Dispose()
at System.Windows.Forms.PaintEventArgs.Dispose(Boolean disposing)
at System.Windows.Forms.PaintEventArgs.Dispose()
at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.GroupBox.WndProc(Message& m)
at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.ComponentManager.System.Windows.Forms.UnsafeNativeMethods+IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at APGUI.MainForm.Main() in c:\progetti-miei\asoluzione\apgui\mainform.cs:line 412

GeneralRe: strange exceptions
Jacob Slusser
17:00 24 Aug '05  
Without seeing the source code (which, if you emailed me, I would be happy to review) there is no way I could say for sure. However, I'll venture to take a guess...

The exception you are receiving looks like it is being thrown when the Graphics object is going to be disposed of but there is still a handle to it somewhere which means either you or the framework is still using it. The stack trace doesn't reference anything in the MdiClient component and so my presumption is that the problem is not within the component itself.

Are you doing any custom painting of your own that might be the cause? Are you properly disposing of the Graphics object when you are done? In addition, improperly disposed Graphics object normally manifest itself as memory leaks that you can see if you check the memory being used by the process.
GeneralBad source code link
jim_mayer@cms.state.il.us
9:47 28 Jul '05  
If anyone has the download would you mind emailing it to me at jim_mayer@cms.state.il.us.

Thank you.
eom

GeneralUsed your code with ActiveX
mullala
4:09 10 May '05  
Thanks for the very useful article. I was attempting double buffering on a subclassed ActiveX control in C# (embedded on an MDI form). The only modification I had to make was to create a WM_PAINT message instead of WM_PRINTCLIENT, since WM_PRINTCLIENT does not seem to paint the window of an ActiveX control.

Your code made this almost trivial. Thanks!

Andrew

GeneralPerformance!
Fade (Amit BS)
3:47 22 Mar '05  
I have solved the performance issue, anyone who wants the code please mail me and get it.
It is BitBlt based, and uses complete double buffering, only now, the screen draws (ALOT) faster Cool
an article on the basics of BitBlt can be found in my artible Using BitBlt to compy and paste graphics
PS
this one's a 5!

Fade (Amit BS)
GeneralRe: Performance!
madval
7:20 9 Jun '06  
hi, can you send me your code? thanks
GeneralRe: Performance!
vinay.p
20:25 11 Jul '07  
Hi...

Can you send me the code...? I used resize part of WndMsg function... but still i am facing problem with performance... Any help from you will be helpful to me...

Thanks,
GeneralRe: Performance!
Patrick Sears
11:27 18 Nov '07  
Ok it's been a really long time, but do you still have your code for the MdiClientController using BitBlt? I'm making a nice MDI app, and everything is beautifully styled, except for that ugly grey background. I'd love to be able to replace it with a color more appropriate for my theme, but performance is a serious concern and I'd like to make sure it's as fast as possible.

Thanks!


It has become appallingly obvious that our technology has exceeded our humanity. - Albert Einstein

GeneralRe: Performance!
galbas
11:57 28 Apr '09  
Could someone post a link to the flicker free version.
I'd be gratefull.
GeneralRe: Performance!
carlos.alberto894
9:51 28 Jul '09  
hi, could you send me your that code?
Thanks man!


Last Updated 7 Oct 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010