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)
{
PaintBackground(e.Graphics);
}
private void MdiClient_Paint(object sender, PaintEventArgs e)
{
PaintBackground(e.Graphics);
}
private void PaintBackground( Graphics g )
{
Rectangle rect = this.ClientRectangle;
rect.Inflate(2,2);
LinearGradientBrush filler = new LinearGradientBrush(
rect,
this._backColor1,
this._backColor2,
this._angle);
g.FillRectangle(filler,rect);
if( this.BackgroundImage != null)
{
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
{
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)
{
}
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
{
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)
{
}
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)
{
PaintBackground(e.Graphics);
}
private void MdiClient_Paint(object sender, PaintEventArgs e)
{
PaintBackground(e.Graphics);
}
private void PaintBackground( Graphics g )
{
Rectangle rect = this.ClientRectangle;
rect.Inflate(2,2);
LinearGradientBrush filler = new LinearGradientBrush(
rect,
this._backColor1,
this._backColor2,
this._angle);
g.FillRectangle(filler,rect);
if( this.BackgroundImage != null)
{
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();
}
}