Click here to Skip to main content
4.33 / 5, 13 votes
1 vote, 7.7%
1

2
1 vote, 7.7%
3
2 votes, 15.4%
4
9 votes, 69.2%
5

Workaround for flicker/flashing when programmatically activating MDI child forms

By tonyt | 8 Jul 2007
This short class demonstrates how to activate an MDI child form without the fireworks that you see if you just set the ActiveMdiChild property of the MDI parent form.

Introduction

This article shows how to activate an MDI child form maximized, without the fireworks that erupt when you activate an MDI child form using the Active() or Select() methods of the child form.

Background

If you've used MDI in WinForms or Win32, you've probably come across this very annoying issue, where MDI child forms that are activated through code, paint themselves in the restored window state they always have when they are non-active, just before they are activated and maximized (this happens only when the current MDI child form is maximized).

Because this issue was so annoying, and resulted in a very unprofessional 'look and feel', I became determined to find a workaround. The one I found to work best involves no subclassing or message handling. I discovered this when I noticed that if I activate the next MDI child form via Ctrl+F6 or the MDI system menu, there was none of the usual fireworks that I was seeing when a child form is activated by calling its Activate() method.

That lead me to the WM_MDINEXT Windows message which, at first glance, seemed only useful for activating the next MDI child form in the sequence. However, it can be used to activate any MDI child form by passing the handle of the next or previous MDI child in the Z-order along with a flag telling it whether to activate the next or previous MDI child in the Z-order.

Using the code

The following sample MDI parent Form class implements the workaround via a method that can be called and passed the MDI child form to be activated:

//  
//  MyMDIParent.cs
//

using System;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MyNamespace
{
   public partial class MyMDIParent : Form
   {
      public MyMDIParent()
      {
         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;
   }
}

Points of interest

This solution works fine for me, when I need to activate a child window via code, but I haven't looked at how to get the MDI child form menu entries inserted into the menu by the framework, to use it. Any insight or advice on that would be welcome.

History

  • Initial submission - 7/09/07.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

tonyt




United States United States

Member


Sign Up to vote for this article
Add a reason or comment to your vote:

Comments and Discussions

You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 23 of 23 (Total in Forum: 23) (Refresh)FirstPrevNext
GeneralThank you Pinmemberajhuddy11:34 28 Oct '09  
GeneralMy vote of 1 PinmemberJon Burchel11:50 31 Aug '09  
General[My vote of 1] CRAP PinmemberJon Burchel11:50 31 Aug '09  
Questionchanging vs form look PinmemberMember 461164419:44 5 Mar '09  
GeneralSimilar Problem Pinmemberjohannesnestler23:13 7 May '08  
GeneralRe: Similar Problem Pinmembers.t.a.v.o4:48 8 Dec '08  
GeneralRe: Similar Problem Pinmemberjohannesnestler23:51 8 Dec '08  
QuestionRe: Similar Problem PinmemberMember 461164419:47 5 Mar '09  
AnswerRe: Similar Problem Pinmemberjohannesnestler6:25 6 Mar '09  
GeneralRe: Similar Problem Pinmemberjasonhh21:05 14 Apr '09  
QuestionI have a question please Pinmembersdev9:13 5 Oct '07  
GeneralFW: "A flicker issue in MDI applications" PinmemberMarcus Deecke12:54 17 Aug '07  
GeneralVery good - looking forward to the MDI menu working PinmemberMartin Berriman2:34 26 Jul '07  
GeneralRe: Very good - looking forward to the MDI menu working [modified] Pinmembertonyt15:37 18 Aug '07  
Generalstill MDI Questions PinmemberSHartmann15:59 22 Aug '07  
GeneralRe: Very good - looking forward to the MDI menu working Pinmemberjefrubio9:01 19 Feb '09  
Generalconverted to VB and adjusted Pinmemberdmbf1b511:27 17 Jul '07  
GeneralRe: converted to VB and adjusted PinmemberJason Newland0:52 17 Feb '09  
GeneralRe: converted to VB and adjusted [modified] PinmemberJordan Deyton4:38 15 Jul '09  
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

Generalhow to SendMessage in VB.net? Pinmemberdmbf1b56:21 17 Jul '07  
GeneralRe: how to SendMessage in VB.net? PinmemberCalifBreton9:33 17 Jul '07  
GeneralNever seen any fireworks.... PinmemberKellyLeahy7:21 9 Jul '07  
GeneralRe: Never seen any fireworks.... Pinmembertonyt8:26 9 Jul '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

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

PermaLink | Privacy | Terms of Use
Last Updated: 8 Jul 2007

Copyright 2007 by tonyt
Everything else Copyright © CodeProject, 1999-2010
Web23 | Advertise on the Code Project