Click here to Skip to main content
Licence 
First Posted 13 Oct 2004
Views 52,682
Bookmarked 36 times

MdiClient Revisited

By | 13 Oct 2004 | Article
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

About the Author

Erdal HALICI

Web Developer

Germany Germany

Member

Erdal HALICI
Managing Consultant
camalon IT-Solutions
Hamburg-Germany
www.camalon.de

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 Pinmemberizmael8713:40 2 Aug '11  
GeneralMy vote of 5 Pinmembersantoshshrest22:09 7 Apr '11  
GeneralGreate one and a thought. Pinmemberccache9:01 4 Nov '09  
GeneralThanks Pinmemberseyyedzadeh19:04 6 Aug '08  
GeneralNo repaint PinmemberJohnny J.3:14 19 Dec '07  
GeneralRe: No repaint PinmemberJohnny J.3:17 19 Dec '07  
GeneralMigrating to VS2005 PinmemberErdal HALICI2:10 13 Nov '06  
GeneralExcellent PinmemberBill Seddon0:57 21 Jul '06  
GeneralMDiClient - Gradient fill PinmemberMick D3:27 11 Jul '06  
GeneralRe: MDiClient - Gradient fill PinmemberErdal HALICI3:39 11 Jul '06  
GeneralNow this is more like it... PinmemberProne2Moan8:49 17 May '06  
GeneralJust what I was searching for Pinmembertomcat000823:08 27 Feb '06  
GeneralRe: Just what I was searching for PinmemberErdal HALICI9:48 28 Feb '06  
GeneralThis was nice! One issue on MdiClient Scroll PinmemberMike_in_Paradise7:12 11 Dec '04  
GeneralRe: This was nice! One issue on MdiClient Scroll PinmemberErdal HALICI17:00 12 Dec '04  
GeneralRe: This was nice! One issue on MdiClient Scroll PinmemberMike_in_Paradise17:18 12 Dec '04  
GeneralRe: This was nice! One issue on MdiClient Scroll Pinmemberpxb2:08 4 Mar '05  
GeneralRe: This was nice! One issue on MdiClient Scroll Pinmemberdarwind13:38 12 Nov '06  
GeneralRe: This was nice! One issue on MdiClient Scroll PinmemberErdal HALICI1:35 13 Nov '06  
GeneralRe: This was nice! One issue on MdiClient Scroll PinmemberErdal HALICI2:14 13 Nov '06  
GeneralRe: This was nice! One issue on MdiClient Scroll Pinmemberdarwind4:37 13 Nov '06  
GeneralVery Nice... PinmemberAgent 8620:10 14 Oct '04  
GeneralRe: Very Nice... PinmemberErdal HALICI3:25 15 Oct '04  

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

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 13 Oct 2004
Article Copyright 2004 by Erdal HALICI
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid