Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MdiClient Revisited

0.00/5 (No votes)
13 Oct 2004 1  
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.

[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:

[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:

[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

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