Layout di dialogo dinamico per MFC in Visual C++ 2015

 C Programming >> Programmazione C >  >> Tags >> MFC
Layout di dialogo dinamico per MFC in Visual C++ 2015

In Visual Studio 2015 MFC viene fornito con una nuova funzionalità (cosa che si è verificata raramente negli ultimi anni):supporto per il layout dinamico delle finestre di dialogo. Ciò significa il supporto della libreria per lo spostamento e il ridimensionamento dei controlli in una finestra di dialogo. In questo articolo mostrerò come funziona questa funzione.

Supponiamo di avere la seguente finestra di dialogo:

Quello che vogliamo è che i controlli sulla finestra di dialogo si muovano (i pulsanti) o si ridimensionino (la casella di gruppo, la modifica e l'elenco) quando la finestra di dialogo viene ridimensionata:

L'editor di risorse fornisce supporto per questo, ma può anche essere fatto a livello di codice. Se apri le proprietà di un controllo c'è una nuova categoria chiamata Layout dinamico che consente di selezionare un tipo di trasloco e un tipo di dimensionamento.

Le opzioni disponibili per lo spostamento e il ridimensionamento sono:Nessuno , Orizzontale Verticale e Entrambi . Queste opzioni dovrebbero essere autoesplicative. Tuttavia, la cosa importante da notare è il valore per lo spostamento e il ridimensionamento degli assi X e Y:questi sono rapporti, non unità di dialogo o pixel, hanno un valore compreso tra 1 e 100 e definiscono quanto un controllo viene spostato o ridimensionato quando l'host la finestra di dialogo cambia dimensione.

Ora, per abilitare il layout mostrato nell'esempio sopra, dobbiamo fare quanto segue:

  • ridimensiona completamente (100%) la casella di gruppo e la casella di riepilogo sia orizzontalmente che verticalmente
  • ridimensiona completamente il controllo di modifica in orizzontale
  • sposta completamente (100%) il pulsante OK in verticale
  • sposta completamente il pulsante Aggiungi in orizzontale
  • sposta completamente i pulsanti Cancella e Annulla sia in orizzontale che in verticale


È piuttosto semplice inserire valori nelle impostazioni di layout dinamico per ciascun controllo. Quando si compila, si esegue e si ridimensiona la finestra di dialogo, i controlli si spostano o si ridimensionano di conseguenza.

Queste impostazioni di layout dinamico vengono inserite nello script delle risorse (file .rc) dell'applicazione. Per l'esempio sopra appare così:

/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//

IDD_MFCDYNLAYOUTDEMO_DIALOG AFX_DIALOG_LAYOUT
BEGIN
    0,
    0, 100, 0, 0,
    100, 100, 0, 0,
    0, 0, 100, 100,
    0, 0, 0, 0,
    0, 0, 100, 0,
    0, 0, 100, 100,
    100, 100, 0, 0,
    100, 0, 0, 0
END

In questa definizione IDD_MFCDYNLAYOUTDEMO_DIALOG è l'identificatore della finestra di dialogo per la quale sono definite le impostazioni ei numeri in BEGIN-END blocco rappresentano:

  • la prima riga è un'intestazione contenente il numero di versione sulla struttura (0 in questa versione)
  • le righe consecutive sono le impostazioni di layout dinamico (rapporti di spostamento e dimensione) per ciascun controllo nella finestra di dialogo, corrispondenti all'ordine in cui i controlli sono stati definiti per la finestra di dialogo nel file di script della risorsa.

Queste impostazioni vengono caricate in un CMFCDynamicLayout oggetto (vedi afxlayout.h/cpp). Questo viene fatto in OnInitDialog metodo del CDialog classe come mostrato di seguito:

BOOL CDialog::OnInitDialog()
{
	// execute dialog RT_DLGINIT resource
	BOOL bDlgInit;
	if (m_lpDialogInit != NULL)
		bDlgInit = ExecuteDlgInit(m_lpDialogInit);
	else
		bDlgInit = ExecuteDlgInit(m_lpszTemplateName);

	if (!bDlgInit)
	{
		TRACE(traceAppMsg, 0, "Warning: ExecuteDlgInit failed during dialog init.\n");
		EndDialog(-1);
		return FALSE;
	}

	LoadDynamicLayoutResource(m_lpszTemplateName);

Nota:per CPaneDialog , Barra CDialog e CFormView d'altra parte questo viene fatto in HandleInitDialog .

Questa LoadDynamicLayoutResource è in realtà un membro di CWnd che contiene altri metodi per lavorare con i layout dinamici:

  • Abilita layout dinamico :abilita o disabilita il layout manager per una finestra
  • IsDynamicLayoutEnabled :indica se la gestione del layout è abilitata per una finestra
  • GetDynamicLayout :recupera un puntatore al layout manager
  • ResizeDynamicLayout :riadatta la posizione dei controlli gestiti dal layout manager dinamico in risposta a WM_SIZE
  • InitDynamicLayout :inizializza il gestore del layout dinamico in risposta a WM_CREATE messaggio
	// controls dynamic layout:

	/// <summary>
	/// Enables or disables layout manager for a window.</summary>
	/// <param name="bEnable"> TRUE - enable layout management, FALSE - disable layout management.</param>
	void EnableDynamicLayout(BOOL bEnable = TRUE);

	/// <summary>
	/// This function returns TRUE, if layout management is enabled for a window; otherwise FALSE.</summary>
	/// <remarks>
	/// Call EnableDynamicLayout in order to enable or disable layout management for a window.</remarks>
	/// <returns> 
	/// TRUE, if layout management is enabled for a window; otherwise FALSE.</returns>
	BOOL IsDynamicLayoutEnabled() const { return m_pDynamicLayout != NULL; }

	/// <summary>
	/// Call this function to retrieve a pointer to layout manager.</summary>
	/// <remarks>
	/// Call EnableDynamicLayout in order to enable or disable layout management for a window.</remarks>
	/// <returns> 
	/// Returns a pointer to the window layout manager or NULL if layout wasn't enabled.</returns>
	CMFCDynamicLayout* GetDynamicLayout() { return m_pDynamicLayout; }

	/// 
	/// The method is called to adjust positions of child controls. 
	/// It recalculates positions of child controls if layout management is enabled for a window.
	virtual void ResizeDynamicLayout();
	void InitDynamicLayout();
	BOOL LoadDynamicLayoutResource(LPCTSTR lpszResourceName);

Questi metodi consentono di abilitare o disabilitare al volo la gestione dinamica del layout.

  1. Inizialmente la gestione dinamica del layout è impostata in modo che i controlli si spostino e si ridimensionino quando la finestra di dialogo viene ridimensionata.

  2. Disabilita la gestione dinamica del layout e i controlli figlio non vengono più regolati.
  3. Riattiva la gestione dinamica del layout e funziona di nuovo.

Il problema qui è che basta chiamare CWnd::EnableDynamicLayout non funzionerà perché questo metodo elimina e ricrea solo il CMFCDynamicLayout esempio.

void CWnd::EnableDynamicLayout(BOOL bEnable)
{
	if (m_pDynamicLayout != NULL)
	{
		delete m_pDynamicLayout;
		m_pDynamicLayout = NULL;
	}

	if (!bEnable)
	{
		return;
	}

	m_pDynamicLayout = new CMFCDynamicLayout;
}

Proprio come CDialog::OnInitDialog dovresti chiamare CWnd::LoadDynamicLayoutResource . Pertanto, il codice corretto per abilitare e disabilitare la gestione del layout dinamico dovrebbe assomigliare a questo:

void CMFCDynLayoutDemoDlg::EnableDynamicLayoutHelper(bool const enable)
{
   if (enable && this->IsDynamicLayoutEnabled())
      return;

   this->EnableDynamicLayout(enable ? TRUE : FALSE);

   if (enable) 
   {
      this->LoadDynamicLayoutResource(m_lpszTemplateName);
   }
}

Come accennato in precedenza, l'impostazione dei valori di spostamento e dimensione per la gestione dinamica del layout può essere eseguita a livello di codice utilizzando CMFCDynamicLayout classe. Questo è importante quando i controlli vengono creati dinamicamente e non nel modello di risorsa. Quello che devi fare è:

  • crea il CMFCDynamicLayout oggetto
  • Memorizza la finestra host (la finestra di dialogo) in quell'oggetto
  • aggiungi i controlli figlio con le loro impostazioni di spostamento e dimensione

Il codice seguente fornisce la stessa funzionalità di layout dinamico illustrata in precedenza, tranne per il fatto che tutto è impostato dal codice. Tieni presente che devi chiamare EnableDynamicLayoutHelper da OnInitDialog .

void CMFCDynLayoutDemoDlg::EnableDynamicLayoutHelper(bool const enable)
{
   if (enable && this->IsDynamicLayoutEnabled())
      return;

   this->EnableDynamicLayout(enable ? TRUE : FALSE);

   if (enable)
   {
      SetupDynamicLayout();
   }
}

void CMFCDynLayoutDemoDlg::SetupDynamicLayout()
{
   auto manager = this->GetDynamicLayout();
   if (manager != nullptr)
   {
      auto movenone = CMFCDynamicLayout::MoveSettings{};
      auto moveboth100 = CMFCDynamicLayout::MoveSettings {};
      moveboth100.m_nXRatio = 100;
      moveboth100.m_nYRatio = 100;
      auto movex100 = CMFCDynamicLayout::MoveSettings {};
      movex100.m_nXRatio = 100;
      auto movey100 = CMFCDynamicLayout::MoveSettings {};
      movey100.m_nYRatio = 100;

      auto sizenone = CMFCDynamicLayout::SizeSettings{};
      auto sizeboth100 = CMFCDynamicLayout::SizeSettings{};
      sizeboth100.m_nXRatio = 100;
      sizeboth100.m_nYRatio = 100;
      auto sizex100 = CMFCDynamicLayout::SizeSettings{};
      sizex100.m_nXRatio = 100;

      manager->Create(this);

      manager->AddItem(IDC_STATIC_GROUPBOX, movenone, sizeboth100);
      manager->AddItem(IDC_LIST1, movenone, sizeboth100);
      manager->AddItem(IDC_EDIT1, movenone, sizex100);
      manager->AddItem(IDC_BUTTON_ADD, movex100, sizenone);
      manager->AddItem(IDC_BUTTON_CLEAR, moveboth100, sizenone);
      manager->AddItem(IDOK, movey100, sizenone);
      manager->AddItem(IDCANCEL, moveboth100, sizenone);
   }
}

In realtà lo stesso codice di cui sopra può essere espresso in modo diverso con l'aiuto di diversi metodi statici da CMFCDynamicLayout che creano istanze di MoveSettings e Impostazioni dimensioni .

void CMFCDynLayoutDemoDlg::SetupDynamicLayout()
{
   auto manager = this->GetDynamicLayout();
   if (manager != nullptr)
   {
      manager->Create(this);

      manager->AddItem(IDC_STATIC_GROUPBOX, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontal(100));
      manager->AddItem(IDC_LIST1, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
      manager->AddItem(IDC_EDIT1, CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontal(100));
      manager->AddItem(IDC_BUTTON_ADD, CMFCDynamicLayout::MoveHorizontal(100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDC_BUTTON_CLEAR, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDOK, CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeNone());
      manager->AddItem(IDCANCEL, CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
   }
}

Una cosa importante da notare qui è che questo codice non chiama CWnd::LoadDynamicLayoutResource perché non ci sono impostazioni nel file di script delle risorse. Tutte queste impostazioni vengono fornite solo a livello di codice in questo caso.

Quando i controlli devono essere spostati o ridimensionati completamente (100%) su uno o entrambi gli assi, l'impostazione dei valori corretti per il layout è semplice. Diventa complicato quando i controlli non sono posizionati lateralmente o devono essere spostati o ridimensionati con regole più complicate. Facciamo un esempio in cui i pulsanti OK e Annulla sono posizionati in basso verticalmente e centrati orizzontalmente. Quando la finestra di dialogo si ridimensiona, dovrebbero mantenere le dimensioni originali, ma dovrebbero sempre rimanere al centro in basso.

In questo caso il rapporto Y per il movimento è di nuovo 100. Ma qual è il rapporto di movimento sull'asse X? Per determinare hai bisogno di carta e penna. Fondamentalmente dobbiamo trovare quanto si muovono i pulsanti su X quando la larghezza aumenta di 100 unità. Questo è il rapporto che dobbiamo impostare.

Inizialmente la finestra di dialogo ha 251 unità, ciò significa due metà di 125 e 126 unità. Vogliamo mantenere i pulsanti separati di 10 unità. Ciò significa che il pulsante OK è allineato a sinistra a 70 unità e il pulsante Annulla è illuminato a 130 unità.

Poi aumentiamo le dimensioni della finestra di dialogo di 100 unità. Ora sono 351 e gli aventi hanno 175 e 176. I pulsanti sono ancora a 10 unità l'uno dall'altro e il loro con è ancora a 50 unità ciascuno. Ciò significa che il pulsante OK è ora allineato a sinistra a 120 unità e il pulsante Annulla è allineato a sinistra a 180 unità.

La conclusione è che il loro margine sinistro si è spostato di 50 unità, e questo è il valore dobbiamo impostare il rapporto X della loro impostazione di spostamento. (Ricorda, il valore è un rapporto, ma anche 50 unità su 100 sono il 50%.)

Cosa succede se i pulsanti OK e Annulla devono essere entrambi allineati al centro di ciascuna metà sull'asse X e preservare sempre i margini? In altre parole dovrebbero cambiare così:

In questo esempio, inizialmente, la finestra di dialogo ha 231 unità e ciò significa due metà di 115 e 116 unità. I pulsanti hanno entrambi una larghezza di 60 unità, quindi sono allineati a 27 o 28 unità ai margini.

Quando la larghezza della finestra di dialogo aumenta di 100 unità a 331 unità, le due metà hanno 165 e 166 unità. I pulsanti conservano i margini, quindi la loro nuova larghezza è di 110 unità.

(Si noti che l'immagine sopra è allungata e i margini potrebbero essere fuorvianti.)

La conclusione è che:

  • Il pulsante OK non si è spostato orizzontalmente, ma ha aumentato la sua larghezza da 60 unità a 110 unità, ovvero il 50%.
  • Il pulsante Annulla si è spostato orizzontalmente e ora è allineato a sinistra a 193 unità invece delle 143 unità originali. Ciò significa che si è spostato del 50% in orizzontale. Le sue dimensioni sono aumentate da 60 unità a 110 unità, il che significa anche il 50%.

Con questi valori impostati i pulsanti vengono ridimensionati e posizionati come previsto.

Per ulteriori informazioni, vedere Layout della finestra di dialogo dinamica MFC.

Codice sorgente della demo:
MFC Dynamic Layout Management - demo 1 (2318 download)
MFC Dynamic Layout Management - demo 2 (1858 download)
MFC Dynamic Layout Management - demo 3 (1756 download)