|
|
Hi,Returns the memory usage after closing the child form.
Forms received prior to the child's memory performance.
Image 1
Forms received after the child's memory performance.
Image 2
From memory after closing the child form.
Image 3
|
|
|
|
|
Very helpful function. Thank you for sharing.
|
|
|
|
|
|
|
|
I had an similar Problem. I created an MDI-App with multiple child Forms with FormBorderStyle.None and ControlBox = false. I docked them with DockStyle.Fill to the MDI-Client. (If you wonder - this was a special Touchscreen app).
But when the Form was activated for the first time, I saw the title bar and the ControlBox, and the Form wasn't docked, for a moment. After some fiddling I came up with this solution: If I disabled the ControlBox before CreateControl fires, everything was fine, and no flickering occured...
/// <summary>
/// Base Form for MDI-child windows.
/// </summary>
public class BaseChildForm : Form
{
protected override void OnCreateControl()
{
ControlBox = false;
base.OnCreateControl();
}
}
|
|
|
|
|
Google says it's a known bug in the framework, I was searching for the solution and finally yours worked! Thanks buddy!
A man's got to know his limitations
Harry Callahan
|
|
|
|
|

Maybe you are interested in another solution I found somewhere while working on the same project.
This is to let the border of the MdiClient disapear (if you want to draw a nice background for yourself etc...) :
public partial class Form1 : Form
{
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int SetWindowLong(IntPtr hwnd, int iIndex, int iValue);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowLong(IntPtr hwnd, int iIndex);
[System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
public static extern int SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
public enum EWindowStyle : int
{
Border = 0x00800000,
ClientEdge = 0x00000200
}
public enum EGetWindowLongFlag : int
{
Style = -16,
ExStyle = -20
}
public enum ESetWindowPosFlag : uint
{
NoSize = 0x0001,
NoMove = 0x0002,
NoZOrder = 0x0004,
NoRedraw = 0x0008,
NoActivate = 0x0010,
NoCopyBits = 0x0100,
NoOwnerZOrder = 0x0200,
NoSendChanging = 0x0400,
FrameChanged = 0x0020,
ShowWindow = 0x0040,
HideWindow = 0x0080,
}
public Form1()
{
InitializeComponent();
this.IsMdiContainer = true;
#region Adjust MdiClient
MdiClient mdiclient = null;
foreach (Control control in Controls)
{
mdiclient = control as MdiClient;
if (mdiclient != null)
{
mdiclient.BackColor = SystemColors.Control;
int iStyle = GetWindowLong(mdiclient.Handle, (int)EGetWindowLongFlag.Style);
int iExStyle = GetWindowLong(mdiclient.Handle, (int)EGetWindowLongFlag.ExStyle);
iStyle &= ~((int)EWindowStyle.Border);
iExStyle &= ~((int)EWindowStyle.ClientEdge);
SetWindowLong(mdiclient.Handle, (int)EGetWindowLongFlag.Style, iStyle);
SetWindowLong(mdiclient.Handle, (int)EGetWindowLongFlag.ExStyle, iExStyle);
SetWindowPos(mdiclient.Handle, IntPtr.Zero, 0, 0, 0, 0,
(uint)(ESetWindowPosFlag.NoActivate | ESetWindowPosFlag.NoMove | ESetWindowPosFlag.NoSize | ESetWindowPosFlag.NoZOrder | ESetWindowPosFlag.NoOwnerZOrder | ESetWindowPosFlag.FrameChanged));
break;
}
}
#endregion // Adjust MdiClient
}
}
|
|
|
|
|
"if you want to draw a nice background for yourself etc..."
please rovide more details
thanks
Yosi
|
|
|
|
|
Hmm, maybe you tell me what you want to do, so I can try to help you.
The above statement means: If you want to draw the MdiClient for yourself in the Paint EventHandler it's maybe useful to get rid of the Border!
Try for yourself. Add a Paint-EventHandler to the MdiClient in the above code and draw something, then uncomment the adjustment of the border and look the difference.
As I mentioned in my original post, I did a Touchscreen-Application, where a "seamless" integration of child forms was essential - the MdiClient-Border disturbed this. 
|
|
|
|
|
I have the same problem, I try
Protected Overrides Sub OnCreateControl()
controlbox=false (or formborderstyle=none)
MyBase.OnCreateControl()
End Sub
and found that the painting of the childform (clientsize) would go wrong. so I track the source code of the form.cs, and found they call MyBase.UpdateStyles(), so the following would be better to remove the controlbox.
Protected Overrides Sub OnCreateControl()
MyBase.UpdateStyles()
MyBase.OnCreateControl()
End Sub
|
|
|
|
|
I copy the code to Parent form but it's not work for me the controle button show every time. the source code is :
using System;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace GainDrug
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
public void ActivateMdiChild(Form childToActivate)
{
if (this.ActiveMdiChild != childToActivate)
{
MdiClient mdiClient = GetMDIClient();
int count = this.MdiChildren.Length;
Control form = null; // next or previous MDIChild form
int pos = mdiClient.Controls.IndexOf(childToActivate);
if (pos < 0)
throw new InvalidOperationException("MDIChild form not found");
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);
// bada bing, bada boom
SendMessage(mdiClient.Handle, WM_MDINEXT, form.Handle, direction);
}
}
public MdiClient GetMDIClient()
{
foreach (Control c in this.Controls)
{
if (c is MdiClient)
return (MdiClient)c;
}
throw new InvalidOperationException("No MDIClient !!!");
}
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
public const int WM_MDINEXT = 0x224;
private frmMyMDIParent fCus;
private void testToolStripMenuItem1_Click(object sender, EventArgs e)
{
if ((fCus != null))
{
if (!fCus.IsDisposed)
{
fCus.WindowState = FormWindowState.Normal;
fCus.BringToFront();
}
else
{
fCus = new frmMyMDIParent();
fCus.MdiParent = this;
fCus.Show();
}
}
else
{
fCus = new frmMyMDIParent();
fCus.MdiParent = this;
fCus.Show();
}
if (fCus == null)
{
MessageBox.Show("Is Nothing");
}
if (fCus.IsDisposed)
{
MessageBox.Show("Is Disposed");
}
}
private void frmMain_Load(object sender, EventArgs e)
{
}
}
}
Where are wrong please ?
Tospon
|
|
|
|
|
|
The strange behaviour does indeed look bad expecially if the form takes a fraction of a second to draw itself.
Unfortunately I see this most often when accessing the MDI window list menu to change active window so I would be very interested to see a solution for that - the only one I can think of is to build the MDI window menu myself and then use your code to select the desired form...
Anyone have any better ideas?
|
|
|
|
|
The solution is easy, just subclass the MDIClient
window and handle the WM_MDIACTIVATE message, and
perform the activation yourself via the WM_MDINEXT
method used by the code postted, rather tha letting
the MDIClient window do it.
If you do that, you also eliminate the need to call
the dedicated ActivateMdiChild method provided by
the code, to activate the child window without the
flicker. IOW, you can use Activate() or Select(),
or any framework API to activate the window, as they
all generally work by sending WM_MDIACTIVATE to the
MDIClient.
-- modified at 20:42 Saturday 18th August, 2007
|
|
|
|
|
 I added the following code to my ChildForm.
There is no flickering anymore, but there is also no menu merging,
no drawing of minimize/maximize/close button
and the menu where the selected child is shown is also not updated ...
I think there needs a little bit more to be done ... any ideas
protected override void WndProc(ref Message m)<br />
{<br />
if (m.Msg == 0x0222)<br />
{<br />
if (this.WindowState == FormWindowState.Maximized)<br />
{<br />
<br />
NoFlickerActivate();<br />
<br />
return;<br />
}<br />
}<br />
base.WndProc(ref m);<br />
}<br />
<br />
public void NoFlickerActivate()<br />
{<br />
if (this.MdiParent == null) return;<br />
MdiClient mdiClient = GetMDIClient(); <br />
if (mdiClient == null) return;<br />
int ct = this.MdiParent.MdiChildren.Length;<br />
Control form = null;
int pos = mdiClient.Controls.IndexOf(this);<br />
if (pos < 0) throw new InvalidOperationException("MDIChild form not found");<br />
if (pos == 0)<br />
{<br />
if (ct == 1) form = this;<br />
else form = mdiClient.Controls[1];
<br />
}<br />
else<br />
form = mdiClient.Controls[pos - 1];
<br />
IntPtr direction = new IntPtr(pos == 0 ? 1 : 0);<br />
<br />
<br />
SendMessage(mdiClient.Handle, WM_MDINEXT, form.Handle, direction);<br />
<br />
}<br />
<br />
public MdiClient GetMDIClient()<br />
{<br />
if (this.MdiParent == null) return null;<br />
foreach (Control c in this.MdiParent.Controls)<br />
{<br />
if (c is MdiClient)<br />
return (MdiClient)c;<br />
}<br />
return null;<br />
}<br />
<br />
[System.Security.SuppressUnmanagedCodeSecurity]<br />
[DllImport("user32.dll", CharSet = CharSet.Auto)]<br />
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);<br />
public const int WM_MDINEXT = 0x224;<br />
|
|
|
|
|
As tonyt said the definitive solution is subclass the MDIClient. This way the MDI menu works perfectly. The main problem subclassing MDIClient is that this class is sealed. You can use MDIClientController by Jacob Slusser http://www.codeproject.com/KB/dialog/mdiclientcontroller.aspx[^]. In the WndProc overrided method of MDIClientController add the tonyt code:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MDIACTIVATE)
{
if (this.parentForm.ActiveMdiChild.Handle != m.WParam)
{
int pos = -1;
for (int i = 0; i < 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;
if (pos == 0)
form = mdiClient.Controls[1];
else
form = mdiClient.Controls[pos - 1];
IntPtr direction = new IntPtr(pos == 0 ? 1 : 0);
SendMessage(mdiClient.Handle, WM_MDINEXT, form.Handle, direction);
}
return;
}
base.WndProc(ref m);
}
and define the constant:
const int WM_MDIACTIVATE = 0x0222;
Attach the MDIClientControler to your parent MDI form by dragging the component in the designer or by code in the MDI form constructor:
private MDIClientController mdiClientController;
public MDIForm()
{
InitializeComponent();
this.mdiClientController = new MdiClientController(this);
}
|
|
|
|
|
Works a treat -- Thank you.
Bertram
|
|
|
|
|
Hi,
I believe I have this working now in VB. For some reason, specifying a 1 or 0 for the direction (last parameter of SendMessage) didn't seem to work - it always did the same thing, regardless of direction. So I made a small adjustment, and it appears to work great now.
Here's the pertinent VB code in case anyone is interested. BTW, if anyone sees a problem with this code, I'd appreciate it if you reply to this!
Thanks for putting me on the right track!
David
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
ByRef lParam As Integer) As Integer
Const WM_MDINEXT As Integer = &H224
Then, in the procedure to actually do the work (note that moMDIClient I get the same way as this article, but maintain it for the life of the mdiparent form instead of getting it every time):
Dim iPos As Integer = moMDIClient.Controls.IndexOf(oFrm)
Dim frmTemp As Form
Dim iLastIdx As Integer = moMDIClient.Controls.Count - 1
If iPos = iLastIdx Then
frmTemp = moMDIClient.Controls(0)
Else
frmTemp = moMDIClient.Controls(iPos + 1)
End If
SendMessage(moMDIClient.Handle, WM_MDINEXT, frmTemp.Handle, 0)
|
|
|
|
|
I couldn't get it to work initially, but with playing around with it i came up with the following - in a module put:
Private Const WM_MDINEXT As Integer = &H224
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByRef lParam As Integer) As Integer
'
'Flicker free form activation (MDI child forms)
Public Sub ActivateChild(ByVal chldForm As Form)
On Error Resume Next
Dim moMDIClient As MdiClient, iPos As Integer, frmTemp As Form, iLastIdX As Integer
'
moMDIClient = GetMDIClient(chldForm)
iPos = moMDIClient.Controls.IndexOf(chldForm)
iLastIdX = moMDIClient.Controls.Count - 1
'
If iPos = iLastIdX Then
frmTemp = moMDIClient.Controls(0)
Else
frmTemp = moMDIClient.Controls(iPos + 1)
End If
SendMessage(moMDIClient.Handle, WM_MDINEXT, frmTemp.Handle, 0)
End Sub
'
Private Function GetMDIClient(ByVal chldForm As Form) As MdiClient
On Error Resume Next
Dim c As Control
For Each c In chldForm.MdiParent.Controls
If c.ToString = "System.Windows.Forms.MdiClient" Then
GetMDIClient = c
Exit For
End If
Next
End Function
|
|
|
|
|
I had a look at this yesterday while trying to get rid of that annoying flicker and learned some things.
When you declare SendMessage, you need to use ByVal for all your parameters. In dmbf1b5 and Jason Newland's code, the lParam argument uses ByRef, causing it to be ignored. I tried running it with ByRef, and it always activated the child with one better z-order than what I wanted, i.e. the previous child. After changing ByRef to ByVal, you can use lParam as described in the MSDN article at http://msdn.microsoft.com/en-us/library/ms644918(VS.85).aspx[^].
In my case, I have two sets of "tabs" called collections and lists. Each list has its own MDI child, but you can only see one collection of lists at a time. Sometimes the z-orders of the lists in each collection get mixed up.
For example, suppose you have tabs A, b, C, d with indices 0, 1, 2, 3 in the MDIClient.Controls collection (the index corresponds to the z-order, with 0 being the active MDI child and 3 the last child). Suppose further that b and d are hidden via the form.Visible property. If b (or d) is sent to WM_MDINEXT with lParam 0 (or 1), b (or d) will be activated in the process.
So if you want to be sure no hidden MDI children are activated, use WM_MDINEXT with a reference to an MDI child that is visible and enabled. From what I can tell, the MDI system automatically ignores hidden MDI children when WM_MDINEXT is used, provided a hidden MDI child is not passed as wParam.
Here is some code I came up with. I borrowed heavily from Jason Newland but wound up changing things a bit to fit my problem. Here, MyMDIChild is the base form I use as MDI children. I use WM_MDINEXT with the "previous" visible tab so that no hidden forms will appear. The nice thing is that you can activate an MDI child simply by calling form.ActivateMe().
Class MyMDIChild
Protected Shared moMDIClient As MdiClient 'The MDIClient containing MyMDIChild objects.
Public Sub ActivateMe()
Dim MyIndex, j, count As Integer
Dim frmTemp As Form
If (moMDIClient Is Nothing) Then 'Get moMDIClient if necessary.
moMDIClient = GetMDIClient(Me)
End If
MyIndex = moMDIClient.Controls.IndexOf(Me) 'Get the z-order of the form-to-activate.
count = moMDIClient.Controls.Count 'Get the number of MDI forms.
If (MyIndex <> 0 And count > 1) Then 'If the form is the active form already or if it is the only form, quit.
j = (MyIndex - 1) Mod count 'Get the "previous" index.
frmTemp = Nothing
While (j <> MyIndex And frmTemp Is Nothing) 'Find the "previous" visible form.
frmTemp = moMDIClient.Controls(j)
If (frmTemp.Visible = False) Then
frmTemp = Nothing
End If
j = (j - 1 + count) Mod count
End While
If Not (frmTemp Is Nothing) Then
SendMessage(moMDIClient.Handle, WM_MDINEXT, frmTemp.Handle, 0)
End If
End If
End Sub
Private Function GetMDIClient(ByVal chldForm As Form) As MdiClient
On Error Resume Next
Dim c As Control
For Each c In chldForm.MdiParent.Controls
If c.ToString = "System.Windows.Forms.MdiClient" Then
GetMDIClient = c
Exit For
End If
Next
End Function
Private Const WM_MDINEXT As Integer = &H224
'For use in VB
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
'For use in VB.NET. Don't forget to import System.Runtime.InteropServices
'<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
'Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
'End Function
End Class
modified on Wednesday, July 29, 2009 9:33 AM
|
|
|
|
|
Hi,
This issue has been a complaint of mine for quite awhile - I'm excited to try out your idea. I'm trying to convert your code to VB.NET, but am having problems. I can find the previous or next form, but how do I call SendMessage? Even when I import all the namespaces/elements you are using, SendMessage is not a valid option. I get: "Name 'SendMessage' is not declared."
Thanks
David
|
|
|
|
|
here is the declaration I use in my VB.Net projects
Private Declare Auto Function SendMessage Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal wMsg As Int32, ByVal wParam As Boolean, ByVal lParam As Int32) As Int32
|
|
|
|
|
I haven't seen any fireworks, but just curious, do you see the fireworks when you use formToActivate.Activate()?
In any case, thanks for informing us of this approach...
Kelly Leahy
Milliman, Inc.
|
|
|
|
|