DevStudio-like Properties Dialog

Environment: VC6 SP3, NT4 SP5

These are the steps to follow to create a dialog resembling the DevStudio Properties dialog: clicking outside the dialog induces dialog closing, unless the Keep Visible button was previously pressed.

  1. Include the NewButton.h and NewButton.cpp files in your project
  2. Create a dialog with a button with Owner Draw style
  3. Define the class that manage the dialog (must derive from CDialog or any other class that derive from CDialog), in the example CDlg
  4. Add a member variable for the button, must be of CNewButton class; if the Class Wizard doesn't permit to choose that variable's type, you could choose the CButton class and then modify it in the .h file of the CDlg class (in the example Dlg.h); remember to insert the line

    #include "NewButton.h"

    before the class definition:

    // Dialog Data
    //{{AFX_DATA(CDlg)

    enum { IDD = IDD_PROPERTIES };
    CNewButton m_KeepVisible;

    //}}AFX_DATA

  5. Modify CDlg constructor removing the default value of the pParent parameter; it is essential for the dialog to know its father:
    • in the .h file

      CDlg(CWnd* pParent);

    • in the .CPP file

      CDlg::CDlg(CWnd* pParent) : CDialog(CDlg::IDD, pParent), m_pParent( pParent )
      {

      //{{AFX_DATA_INIT(CDlg)
      //}}AFX_DATA_INIT

      }

  6. Define in Dlg.h public section the function:

    BOOL Create(); // Creation of mode-less dialog

  7. Define in Dlg.h protected section the following member variables:

    CWnd* m_pParent; // Parent of the dialog
    CToolTipCtrl m_toolTip; // Tooltip management
    static WNDPROC m_MFCWndProc; // Pointer to the default MFC window procedure
    static CDlg* m_pDialog; // Pointer to the active instance of the dialog (this variable is correct if we do the hypothesis that only one instance of CDlg could be active at once; otherwise is necessary to define a map which related a HWND with a CDlg*)

    and the function:

    static LRESULT CALLBACK NewWndProc( HWND, UINT, WPARAM, LPARAM ); // New window procedure of the dialog

  8. With Class Wizard re-define WM_INITDIALOG, WM_CLOSE and PreTraslateMessage(), and define the button click message function; in the example:

    OnKeepVisible()

  9. Define the UserMessage to be used by the dialog to communicate with its father:

    #define WM_CLOSEDLG WM_USER+5

  10. In the .CPP file define the static variables:

    WNDPROC CDlg::m_MFCWndProc = 0;
    CDlg* CDlg::m_pDialog = NULL;

    and insert the following implementation of the previously declared functions and message handlers:

    BOOL CDlg::OnInitDialog()
    {
      CDialog::OnInitDialog();
     
      
    // Creation and init of tooltip for the Keep Visible button
      
    m_toolTip.Create( this, TTS_ALWAYSTIP );
      m_toolTip.Activate( TRUE );
      m_toolTip.AddTool( GetDlgItem( IDC_KEEP_VISIBLE ), "Keep Visible" );
     
      
    // Init of the Keep Visible button: set the parent of the button (dialog
      // that contains it) and the bitmaps which shows when the button is
      // pressed/released and the mouse is over/non-over
      
    UINT nBitmapIDs[] = { IDB_VISIBLEU, IDB_VISIBLED,
       IDB_VISIBLEU_OVER, IDB_VISIBLED_OVER };
      m_KeepVisible.SetParent( this, nBitmapIDs );
     
      
    // Change of the default window procedure
      
    WNDPROC temp = reinterpret_cast(::SetWindowLong(
       GetSafeHwnd(), GWL_WNDPROC, reinterpret_cast(NewWndProc) ));
      if ( !m_MFCWndProc ) m_MFCWndProc = temp;
      m_pDialog = this;
     
      return TRUE;
    // return TRUE unless you set the focus to a control
       // EXCEPTION: OCX Property Pages should return FALSE
    }
     
    BOOL CDlg::Create()
    {
      return CDialog::Create( CDlg::IDD );
    }
     
    void CDlg::OnKeepVisible()
    {
      CPoint point;
      ::GetCursorPos( &point );
      CRect rect;
      m_KeepVisible.GetWindowRect( &rect );
      
    // Check that the mouse is over the button
      
    if ( rect.PtInRect( point ) )
      {
        
    // Change the button state
        
    m_KeepVisible.SetMode();
        
    // The SetState function must be called two times
        
    m_KeepVisible.SetState( m_KeepVisible.GetMode() );
        m_KeepVisible.SetState( m_KeepVisible.GetMode() );
      }
    }
     
    LRESULT CALLBACK CDlg::NewWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
      
    // The new window procedure manages only the dialog inactivate;
      // the other msgs are managed by default window procedure
      
    if ( message == WM_ACTIVATE )
      {
        if ( wParam == 0 )
        {
          if ( !m_pDialog->m_KeepVisible.GetMode() )
            m_pDialog->m_pParent->PostMessage( WM_CLOSEDLG );
          return FALSE;
        }
      }
      return ::CallWindowProc( m_MFCWndProc, hWnd, message, wParam, lParam );
    }
     
    void CDlg::OnClose()
    {
      
    // Parent notify
      
    m_pDialog->m_pParent->PostMessage( WM_CLOSEDLG );
     
      CDialog::OnClose();
    }
     
    BOOL CDlg::PreTranslateMessage(MSG* pMsg)
    {
      
    // Different management of the WM_KEYDOWN msg when is pressed the ENTER button
      // (the default management closes the dialog) or the ESC (we want to close the
      // dialog)
      
    if ( pMsg->message == WM_KEYDOWN )
        switch ( (int)pMsg->wParam )
        {
          case VK_RETURN:
            if ( m_KeepVisible.GetMode() )
              return true;
            break;
          case VK_ESCAPE:
            m_pDialog->m_pParent->PostMessage( WM_CLOSEDLG );
            return true;
        }
     
      
    // Tooltip management
      
    m_toolTip.RelayEvent( pMsg );
     
      return CDialog::PreTranslateMessage(pMsg);
    }

  11. At this time we need to manage the dialog from its father (in the example the father is the MainFrame), in particular the closing message; so in the .h file we need to insert the definition of the message handler:

    // Generated message map functions
    protected:
    //{{AFX_MSG(CMainFrame)
    ...
    //}}AFX_MSG
    afx_msg LRESULT OnCloseDlg( WPARAM wParam, LPARAM lParam );
    DECLARE_MESSAGE_MAP()

    while in the .CPP file the message map:

    BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ...
    //}}AFX_MSG_MAP
    ON_MESSAGE( WM_CLOSEDLG, OnCloseDlg )
    END_MESSAGE_MAP()

    and the function implementation:

    LRESULT CMainFrame::OnCloseDlg( WPARAM wParam, LPARAM lParam )
    {
      if ( m_pDlg )
      {
         m_pDlg->DestroyWindow();
         delete m_pDlg;
         m_pDlg = NULL;
      }
     
      return 0L;
    }

    where m_pDlg is a pointer to the dialog active instance.

To correctly use this source it's necessary define, in the MainFrame or where you want manage the dialog, a member variable which points to the dialog active instance:

CDlg* m_pDlg;

furthermore, in the code, where you want create the dialog, insert the next lines:

// If the dialog already exists set the focus to it, otherwise create the dialog and show it
if ( m_pDlg )
  m_pDlg->SetFocus();
else
{
  m_pDlg = new CDlg( this );
  m_pDlg->Create();
  m_pDlg->ShowWindow( TRUE );
}

Don't forget to initialize the pointer in the constructor:

m_pDlg = NULL;

and verify, in the destructor, that there isn't active instance:

if ( m_pDlg )
{
  m_pDlg->DestroyWindow();
  delete m_pDlg;
}

Thanks to Matteo Benzoni and Diego Perrotta

Downloads

Download demo project - 35 Kb
Download source - 5 Kb



Comments

  • cool, but not working properly

    Posted by Legacy on 03/11/2003 12:00am

    Originally posted by: vincent

    Very cool code....however, when I create a dialog-app and let the main dlg create a modal dialog, and that dialog creates the 'properties/mode-less/pushpin/stay-on-top'-dialog the stay-on-top doesnt stay on top...it gets put after the modal-dialog...Do you know how to take care of this?

    tnx

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds