TForm i TApplication
27.08.1999
  • Tworzenie formy MDI child w postaci zmaksymalizowanej.

       Gdy ustawisz WindowState formy MDI child na wsMaximized, zauważysz, że forma nie jest od razu zmaksymalizowana. Początkowo jest ona rysowana normalnie i dopiero potem maksymalizowana. Wygląda to dość dziwnie.

      Ogólne informacje

      Problem spowodowany jest kodem VCL wykonywanym w metodzie AfterConstruction klasy TCustomForm. AfterConstruction zawiera wyrażenie if, które ustawia właściwość Visible na true.

void __fastcall TCustomForm::AfterConstruction()
{
  ...
  ...
  if (FFormState.Contains(fsVisible))
    Visible = true;
}
       Visible jest ustawiane w zależności od prywatnej zmiennej FFormState. Dla formy MDI child VCL ustawi zawsze fsVisible, ustawienie właściwości Visible w kodzie nic tutaj nie zmieni (IDE nie pozwoli Ci ustawić właściwości Visible na false dla formy MDI child).

      Przypisanie wartości do Visible powoduje wysłanie komunikatu CM_VISIBLECHANGING. Komunikat ten obsługiwany jest przez TWinControl, następuje wywołanie funkcji UpdateControlState, ta zaś wywołuje UpdateShowing. TWinControl::UpdateShowing wysyła kolejny komunikat, CM_SHOWINGCHANGED. TCustomForm przechwytuje ten komunikat. Tutaj zaczyna się wszystko rozjaśniać...
void __fastcall TCustomForm::CMShowingChanged(TMessage &Message)
{
    ...
    ...
        if (FormStyle == fsMDIChild)
        {
          if (FWindowState == wsMaximized)
          {
              SendMessage(Application.MainForm.ClientHandle,
                          WM_MDIRESTORE, Handle, 0);
              ShowWindow(Handle, SW_SHOWMAXIMIZED);
          }
}
      Winowajcą wydaje się być polecenie SendMessage. Wysłanie WM_MDIRESTORE do klienta MDI powoduje narysowanie formy MDI child w stanie niezmaksymalizowanym. Kiedy klient MDI otrzymuje ten komunikat wysyła inne z powrotem do formy MDI child. Wśród nich jest WM_MDIACTIVATE i komunikaty rysujące, to one właśnie powodują przedwczesne rysowanie się formy w stanie niezmaksymalizowanym.

      Rozwiązanie

      Zmiana tego co VCL robi wewnątrz CMShowingChanged byłaby trudna, ale możemy kazać Windows, żeby nie przerysowywał obszaru klienta MDI kiedy TCustomForm wysyła kłopotliwy komunikat WM_MDIRESTORE. Możemy zmusić okno klienta MDI, żeby zaprzestało rysowania, dopóki nie pozwolimy na to. Funkcja API LockWindowUpdate pozwoli nam zatrzymać rysowanie klienta MDI aż do powrotu z konstruktora formy. Oto przykład kodu:
// ustaw WindowState na wsMaximized w fazie projektowania.
    LockWindowUpdate(Application->MainForm->ClientHandle);
    TMDIChild *child = new TMDIChild(Application);
    child->Caption = "Zmaksymalizowane MDI";
    LockWindowUpdate(NULL);
Uwaga: Można zablokować jedno okno w danym czasie. Argument HWND określa które okno zablokujemy. Przekazanie NULL powoduje dezaktywację blokady.

Uwaga: Zauważ, że blokujemy okno klienta MDI, a nie główną formę programu. Właściwość ClientHandle TForm zwraca HWND okna klienta MDI. Właściwość ClientHandle jest ważna jedynie dla form, które mają ustawiony FormStyle na fsMDIForm.

Uwaga: Jeżeli tworzysz okno MDI child z wnętrza MDI parent, możesz pominąć przedrostek Application->Mainform przed ClientHandle. Innymi słowy, możesz użyć funkcji LockWindowUpdate w ten sposób:
 LockWindowUpdate(ClientHandle);
Uwaga: Kiedy forma MDI jest początkowo rysowana w postaci niezmaksymalizowanej, rozmiar okna nie jest określony przez właściwości Width i Height. System operacyjny sam określa rozmiar okna. Analizując TCustomForm::CreateHandle zobaczysz, że rozmiar i pozycja struktury TMDICreateStruct są inicjalizowane do CW_USEDEFAULT. Tak naprawdę właściwości Width, Height, Top, Left są ponownie obliczane po wysłaniu komunikatu WM_MDICREATE. Jeżeli chcesz zmienić te początkowe wartości możesz nadpisać CreateParams.

Uwaga: Kilka innych funkcji jest również odpowiedzialnych za rysowanie MDI child w niezmaksymalizowanej postaci. Są one wywoływane po wysłaniu komunikatu WM_MDIRESTORE z TCustomForm::CMShowingChanged. Te funkcje to: WMMDIActivate, SetActive i MergeMenu. WM_MDIRESTORE wywołuje komunikat WM_MDIACTIVATE. Funkcja obsługująca WMMDIActivate wywołuje SetActive. SetActive wywołuje MergeMenu, ta zaś robi to:


    // przypuścimy, że MergeState ma wartość true
    int Size;
    if(MergeState && (FormStyle == fsMDIChild) &&
	                    (WindowState = wsMaximized)
    {
      //{ zmusza MDI do przywrócenia systemowego menu
      // zmaksymalizowanej formy MDI child }
      Size = ClientWidth + (int(ClientHeight) << 16);
      SendMessage(Handle, WM_SIZE, SIZE_RESTORED, Size);
      SendMessage(Handle, WM_SIZE, SIZE_MAXIMIZED, Size);
    }

      Po wykonaniu tego kodu, okno MDI child jest cały czas w postaci niezmaksymalizowanej. Pierwszy komunikat WM_SIZE powoduje zakończenie rysowania okna w tej postaci. Cały ten kod jest wywoływany po pierwszym wykonaniu SendMessage w CMShowingChanged.