Click here to Skip to main content
15,867,686 members
Articles / Multimedia / GDI+
Article

MdiClient Revisited

Rate me:
Please Sign up or sign in to vote.
4.53/5 (18 votes)
13 Oct 20043 min read 84.6K   2.2K   35   24
Drawing background of MDI window using 100% managed code, including design time support.

Sample Image - MdiBackground.jpg

Introduction

I read the article written recently by Jacob Slusser describing how to change the background of an MDI Form. It was very informative, but for me, a little bit complicated for what it should do. I am not a friend of using P/Invoke calls (for example, calling SetWindowLong function) instead of first trying managed methods and classes, such as SetStyle to set the control style. He was also asking how to support this in design time.

I have written just another “simple” form that can be visually inherited and does nearly the same what Jacob did. But I used a different approach which is 100% managed code. It also provides design time support to visually change the background colors and picture of a simple form or MDI form. You can extend it however you like using visual inheritance of the base form that I provided in my sample application.

The problem with the design time support of an MDI form is the different behavior of the actual form and MdiClient control in design and run time. When the IsMdiContainer property is set to true, the Paint event of the MdiClient has not been fired to draw its background in design time, but it does in run time. Opposite to that, in run time, the Paint event of the actual form has never been fired, but fired by the MdiClient.

What I did here to overcome this problem is handle both events in my code to support background drawing (also in design time). I only needed to shadow the IsMdiContainer property of the base form, hook on the Paint event of the MdiClient and the actual form, and do some drawing, that’s all.

C#
[DefaultValue(false)]
public new bool IsMdiContainer
{
    get{ return base.IsMdiContainer; }
    set
    {
        base.IsMdiContainer = value;

        if( ! value) return;

        for(int i = 0; i < this.Controls.Count; i++)
        {
            MdiClient mdiClient = this.Controls[i] as MdiClient;
            if(mdiClient != null)
            {
                mdiClient.Paint +=new PaintEventHandler(this.MdiClient_Paint);
                break;
            }
        }                                              
    }
}

protected override void OnPaint(PaintEventArgs e)
{
    // In design time the MdiClient_Paint has not been
    // called but OnPaint is called. Then...
    PaintBackground(e.Graphics);
}

private void MdiClient_Paint(object sender, PaintEventArgs e)
{
    PaintBackground(e.Graphics);
}

private void PaintBackground( Graphics g )
{
    // Create a brush
    Rectangle rect = this.ClientRectangle;
    rect.Inflate(2,2);// to completely fill the client area

    LinearGradientBrush filler = new LinearGradientBrush(
                        rect, 
                        this._backColor1, 
                        this._backColor2, 
                        this._angle);

    // Fill the client area
    g.FillRectangle(filler,rect);

    //          Draw image centered, 
    // We use here forms "BackgroundImage" property. Nothing special...
    if( this.BackgroundImage != null)
    {
        //Make it transparent if you like!!!
        //((Bitmap)this.BackgroundImage).MakeTransparent();
        int x= (this.ClientRectangle.Width/2)  - (this.BackgroundImage.Width/2);
        int y= (this.ClientRectangle.Height/2) - (this.BackgroundImage.Height/2);
        g.DrawImageUnscaled(this.BackgroundImage, x, y);        
    }

    filler.Dispose();
}

If you use the code above, you will see that the background flickers in run time when you set IsMdiContainer property to true. Here is an annoying design leak in the MdiClient control. It basically copies all styles from it parent (that is the actual form), except the ControlStyles.DoubleBuffer. Bizarre! Isn’t it? I don’t see any gut reason for that. To overcome this problem, I used reflection to set the ControlStyles.DoubleBuffer flag. To do this, change your shadowing IsMdiContainer property as shown below:

C#
[DefaultValue(false)]
public new bool IsMdiContainer
{
    get{ return base.IsMdiContainer; }
    set
    {
        base.IsMdiContainer = value;

        if( ! value) return;

        for(int i = 0; i < this.Controls.Count; i++)
        {
            MdiClient mdiClient = this.Controls[i] as MdiClient;
            if(mdiClient != null)
            {
                ControlStyles styles = ControlStyles.DoubleBuffer;

                try
                {
                    // Prevent flickering, only if our assembly
                    // have reflection permission.
                    Type mdiType = typeof(MdiClient);

                    System.Reflection.BindingFlags flags = 
                        System.Reflection.BindingFlags.NonPublic |
                        System.Reflection.BindingFlags.Instance;

                    System.Reflection.MethodInfo method = 
                               mdiType.GetMethod("SetStyle",flags);
                    object[] param = {styles, true};
                    method.Invoke(mdiClient,param);
                }
                catch ( System.Security.SecurityException)
                {
                    /*Don't do anything!!! This code is running under 
                                          partially trusted context*/
                }

                mdiClient.Paint += new PaintEventHandler(this.MdiClient_Paint);
                break;
            }
        }                                              
    }
}

As you see, my catch block does not do anything with the exception. The reason for this is, if my control has not sufficient permission, it should not crash. It will only flicker, if it hasn't got reflection permission to prevent this. I don’t see any problem here, because my control will run in nearly any security context provided. We can also make this optional, if you ask for optional reflection permission in the assembly (in the AssemblyInfo.cs) which contains your base form, as stated below:

C#
[assembly: ReflectionPermission(SecurityAction.RequestOptional, 
                                                  Unrestricted= true)]

When you do this, you will see an information (i) image in your system menu on the left-top corner of your form during design und run time. When you start the form, you will receive security information (as tool tip) that your application is running under partially trusted security context.

Conclusion

I think that this approach is better than sub classing MdiClient control using NativeWindow class which requires unmanaged code permission to run. If we do so, there will be no other option available except running the form, for example, under Full-Trust context. This is a very high expectation, from where the application -using our form- can be run. You need to ask a lot of questions to yourself: whether you have chosen the right approach to develop your control, will the program (which uses your control) run if the user executes it from a network drive or Internet zone, or in which circumstances will it execute without throwing a security exception etc.! Using this approach, my control is much simpler and more compatible and I can sleep well.

Complete Source Code of GradientForm

C#
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
 
public class GradientForm : System.Windows.Forms.Form
{
    private System.Drawing.Color _backColor1 = System.Drawing.Color.White;
    private System.Drawing.Color _backColor2 = System.Drawing.Color.CornflowerBlue;
    private int  _angle = 0;

    public GradientForm()
    {
        SetStyle(System.Windows.Forms.ControlStyles.DoubleBuffer, true);
        SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true);
        SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true);
    }

    [DefaultValue(false)]
    public new bool IsMdiContainer
    {
        get{ return base.IsMdiContainer; }
        set
        {
            base.IsMdiContainer = value;

            if( ! value) return;

            for(int i = 0; i < this.Controls.Count; i++)
            {
                MdiClient mdiClient = this.Controls[i] as MdiClient;
                if(mdiClient != null)
                {
                    ControlStyles styles = ControlStyles.DoubleBuffer;

                    try
                    {
                        // Prevent flickering, only if our assembly
                        // has reflection permission.
                        Type mdiType = typeof(MdiClient);

                        System.Reflection.BindingFlags flags = 
                          System.Reflection.BindingFlags.NonPublic |
                          System.Reflection.BindingFlags.Instance;

                        System.Reflection.MethodInfo method 
                               = mdiType.GetMethod("SetStyle",flags);
                        object[] param   = {styles, true};
                        method.Invoke(mdiClient,param);
                    }
                    catch ( System.Security.SecurityException)
                    {
                        /*Don't do anything!!! This code is running under 
                                              partially trusted context*/
                    }

                    mdiClient.Paint +=new PaintEventHandler(this.MdiClient_Paint);
                    break;
                }
            }                                              
        }
    }

    [DefaultValue(typeof(Color),"White")]
    [Category("Gradient")]
    public System.Drawing.Color BackColor1
    {
        get
        {
            return _backColor1;
        }
        set
        {
            if( _backColor1 == value ) return;
            _backColor1 = value;
            this.Invalidate();
        }
    } 

    [DefaultValue(typeof(Color),"CornflowerBlue")]
    [Category("Gradient")]
    public  System.Drawing.Color BackColor2
    {
        get
        {
            return _backColor2;
        }
        set
        {
            if( _backColor2 == value ) return;
            _backColor2 = value;
            this.Invalidate();
        }
    }

    [DefaultValue(0)]
    [Category("Gradient")]
    public int Angle
    {
        get{ return _angle;}
        set 
        { 
            if( _angle == value || value < 0 || _angle > 360) return;
            _angle = value;
            this.Invalidate();
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        // In design time the MdiClient_Paint has not been 
        // called but OnPaint is called. Then...
        PaintBackground(e.Graphics);
    }

    private void MdiClient_Paint(object sender, PaintEventArgs e)
    {
        PaintBackground(e.Graphics);
    }
 
    private void PaintBackground( Graphics g )
    {
        // Create a brush
        Rectangle rect = this.ClientRectangle;
        rect.Inflate(2,2);// to completely fill the client area

        LinearGradientBrush filler = new LinearGradientBrush(
                        rect, 
                        this._backColor1, 
                        this._backColor2, 
                        this._angle);

        // Fill the client area
        g.FillRectangle(filler,rect);

        // Draw image centered, 
        // We use here forms "BackgroundImage" property. Nothing special...
        if( this.BackgroundImage != null)
        {
            //Make it transparent if you like!!!
            //((Bitmap)this.BackgroundImage).MakeTransparent();
            int x= (this.ClientRectangle.Width/2)  - 
                                (this.BackgroundImage.Width/2);
            int y= (this.ClientRectangle.Height/2) - 
                                (this.BackgroundImage.Height/2);
            g.DrawImageUnscaled(this.BackgroundImage, x, y);        
        }

        filler.Dispose();
    }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect
Germany Germany
Erdal HALICI
Freelance IT-Consultant, Project Solution Architect
Hamburg-Germany
www.erdalhalici.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
izmael872-Aug-11 13:40
izmael872-Aug-11 13:40 
GeneralMy vote of 5 Pin
Santoshshrest7-Apr-11 22:09
Santoshshrest7-Apr-11 22:09 
GeneralGreate one and a thought. Pin
ccache4-Nov-09 9:01
ccache4-Nov-09 9:01 
GeneralThanks Pin
seyyedzadeh6-Aug-08 19:04
seyyedzadeh6-Aug-08 19:04 
GeneralNo repaint Pin
Johnny J.19-Dec-07 3:14
professionalJohnny J.19-Dec-07 3:14 
GeneralRe: No repaint Pin
Johnny J.19-Dec-07 3:17
professionalJohnny J.19-Dec-07 3:17 
GeneralRe: No repaint Pin
Rick Bogard22-May-13 4:00
Rick Bogard22-May-13 4:00 
GeneralMigrating to VS2005 Pin
Erdal HALICI13-Nov-06 2:10
Erdal HALICI13-Nov-06 2:10 
GeneralExcellent Pin
Bill Seddon21-Jul-06 0:57
Bill Seddon21-Jul-06 0:57 
GeneralMDiClient - Gradient fill Pin
Mick D11-Jul-06 3:27
Mick D11-Jul-06 3:27 
GeneralRe: MDiClient - Gradient fill Pin
Erdal HALICI11-Jul-06 3:39
Erdal HALICI11-Jul-06 3:39 
GeneralNow this is more like it... Pin
Prone2Moan17-May-06 8:49
Prone2Moan17-May-06 8:49 
GeneralJust what I was searching for Pin
tomcat000827-Feb-06 23:08
tomcat000827-Feb-06 23:08 
GeneralRe: Just what I was searching for Pin
Erdal HALICI28-Feb-06 9:48
Erdal HALICI28-Feb-06 9:48 
GeneralThis was nice! One issue on MdiClient Scroll Pin
Mike_in_Paradise11-Dec-04 7:12
Mike_in_Paradise11-Dec-04 7:12 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
Erdal HALICI12-Dec-04 17:00
Erdal HALICI12-Dec-04 17:00 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
Mike_in_Paradise12-Dec-04 17:18
Mike_in_Paradise12-Dec-04 17:18 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
Pedro Barreto4-Mar-05 2:08
Pedro Barreto4-Mar-05 2:08 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
darwind12-Nov-06 13:38
darwind12-Nov-06 13:38 
Help ... I'm a novice at this and can't seem to incorporate your sample code into the sample code that you posted here. I know your code will help me solve my problem, but I can't seem to get it to compile and run.

public class GradientForm: System.Windows.Forms.Form
{
    ...
    MDIClient _mdiClient = null; // Save MDIClient in this class variable

    ...

    private class SubclassMDIClient : NativeWindow  // Subclass for MDIClient
    {
        private const int WM_HSCROLL = 0x114;
        private const int WM_VSCROLL = 0x115;

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


Can you (or some kind sole) please incorporate this for me and email me the entire resulting sample application. Honestly I don't even know where to begin.

One specific question I have is should all the changes you suggest be made to "GradientForm.cs"? I should also mention that using (or trying to use) Visual Studio .NET 2005 (2.0 framework).

Thanks in advance.


Darwin
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
Erdal HALICI13-Nov-06 1:35
Erdal HALICI13-Nov-06 1:35 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
Erdal HALICI13-Nov-06 2:14
Erdal HALICI13-Nov-06 2:14 
GeneralRe: This was nice! One issue on MdiClient Scroll Pin
darwind13-Nov-06 4:37
darwind13-Nov-06 4:37 
GeneralVery Nice... Pin
Agent 8614-Oct-04 20:10
Agent 8614-Oct-04 20:10 
GeneralRe: Very Nice... Pin
Erdal HALICI15-Oct-04 3:25
Erdal HALICI15-Oct-04 3:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.