Link to home
Start Free TrialLog in
Avatar of Surfer
Surfer

asked on

Add MFC controls (CButton, CListCtrl, etc. ) to CWnd derived class without producing flicker?

I have modified a CWnd derived class to use a custom background by modifying the OnEraseBkgnd() and OnSize() methods. To eliminate flicker, I  am using a memory DC in the OnEraseBkgnd() method.  This works great.

My questin is , how best to add MFC controls (CButton, CListCtrl, etc. ) to this client area without producing flicker?
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

You haven't actually said what is causing the flicker.

You MAY (coz I'm guessing this is the cause) have to modify the OnEraseBkgnd you have so it doesn't draw onto the area the contols take in the client.
Avatar of Surfer
Surfer

ASKER

Can you please show me in code using a CButtton and say a CListCtl?
Not easily - I don't know what your code is.

GetClientRect for instance will get the size and position of a window (control)
Oops - silly mistake.
GetClientRect will only get the size, GetWindowRect will get the left and top co-ordinates (and indirectly the size).  You will also want to look at ScreenToClient to convert the absolute coordinates into coordinates relative ot the client area of the dialog your controls are on.
ASKER CERTIFIED SOLUTION
Avatar of sarabande
sarabande
Flag of Luxembourg image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Surfer

ASKER

sarabande,  
Your suggestion seems sound in that it means I will only have to manage one "group" within my client area and not the individual controls.  I will give it a try and let you know.
Now I'm puzzled.
Your previous comment seems to say you haven't actually tried to add controls to the dialog.  (In the question I took that to mean you have added controls and were experiencing flickering).

I hope you are aware that the redrawing is a two step process.  First the background then the foreground (in response to different messages) - and that for both the dialog and for each control on it.  This is what can result in flickering.

You haven't actually said what your custom painting of the background involves - this could be the root of your problems.
Avatar of Surfer

ASKER

Instead of adding individual controls to my MDI based CMDIFrameWnd, I have taken sarabande advice and created a CDialog to act as a "container" for all my controls:

IDD_MYCLIENTDLG DIALOG DISCARDABLE  0, 0, 186, 93
STYLE  WS_CHILD
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,129,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,129,24,50,14
END


// MyClientDlg.cpp : implementation file
//

#include "stdafx.h"
#include "MDIClientTest2.h"
#include "MyClientDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMyClientDlg dialog


CMyClientDlg::CMyClientDlg(CWnd* pParent /*=NULL*/)
      : CDialog(CMyClientDlg::IDD, pParent)
{
      //{{AFX_DATA_INIT(CMyClientDlg)
            // NOTE: the ClassWizard will add member initialization here
      //}}AFX_DATA_INIT
}


void CMyClientDlg::DoDataExchange(CDataExchange* pDX)
{
      CDialog::DoDataExchange(pDX);
      //{{AFX_DATA_MAP(CMyClientDlg)
            // NOTE: the ClassWizard will add DDX and DDV calls here
      //}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CMyClientDlg, CDialog)
      //{{AFX_MSG_MAP(CMyClientDlg)
            // NOTE: the ClassWizard will add message map macros here
      ON_WM_ERASEBKGND()
      ON_WM_SIZE()
      //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMyClientDlg message handlers
BOOL CMyClientDlg::OnEraseBkgnd(CDC* pDC)
{
      // TODO: Add your message handler code here and/or call default
      CWnd::OnEraseBkgnd(pDC);
#if 0
      CRect rect ;
      GetClientRect ( &rect ) ;
      
      CBrush br( RGB( 0,0,0 ) ) ;
      CBrush* pOldBrush = pDC->SelectObject(&br) ;
      pDC->PatBlt(rect.left + rect.Width()/4,
            rect.top + rect.Height()/4,
            rect.Width()/2,
            rect.Height()/2,
            PATCOPY) ;
      pDC->SelectObject(pOldBrush) ;

      return TRUE ;
#endif
      return CWnd::OnEraseBkgnd(pDC);
}

void CMyClientDlg::OnSize(UINT nType, int cx, int cy)
{
      CWnd::OnSize(nType, cx, cy);
      
      // TODO: Add your message handler code here
      //SetWindowPos(&CWnd::wndBottom ,0,0,0,0,SWP_SHOWWINDOW|SWP_NOMOVE|SWP_NOSIZE|SWP_NOREPOSITION);

      Invalidate(TRUE);
      
}

I then setup my custom CMDIFrameWnd and use the return CWnd to create a show my Dialog.
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
      if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
            return -1;
      
      if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
            | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
            !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
      {
            TRACE0("Failed to create toolbar\n");
            return -1;      // fail to create
      }

      if (!m_wndStatusBar.Create(this) ||
            !m_wndStatusBar.SetIndicators(indicators,
              sizeof(indicators)/sizeof(UINT)))
      {
            TRACE0("Failed to create status bar\n");
            return -1;      // fail to create
      }

      // TODO: Delete these three lines if you don't want the toolbar to
      //  be dockable
      m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
      EnableDocking(CBRS_ALIGN_ANY);
      DockControlBar(&m_wndToolBar);

      if ( !m_cMyClient.SubclassWindow(m_hWndMDIClient) )
            return -1 ;
      
      CWnd* pWnd = CWnd::FromHandle( m_hWndMDIClient );
      
      m_cMyDlg.Create(CMyClientFormView::IDD, pWnd);
      
      m_cMyDlg.ShowWindow(SW_SHOW) ;

      return 0;
}

This create and show the Dialog ( containing my controls ) on my m_cMyClient and it works great.

My problem is with the m_cMyClient  focus under at least two conditions.

1) m_cMyClient on the m_hWndMDIClient redrawing is not playing nice when with the  CMDIChildWnd windows are opened, maximized or whatever, the rest of my m_hWndMDIClient drawing within CMyCwndClient::OnEraseBkgnd(CDC* pDC)  works perfect.

2)When the mouse is not on any of the controls of the Dialog, the last control is still selected in hover mode.  My desired behavior is that if the mouse is not on the controls within the Dialog, they all loose focus.

// MyCwndClient.cpp : implementation file
//

#include "stdafx.h"
#include "MDIClientTest2.h"
#include "MyCwndClient.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMyCwndClient

CMyCwndClient::CMyCwndClient()
{
}

CMyCwndClient::~CMyCwndClient()
{
}


BEGIN_MESSAGE_MAP(CMyCwndClient, CWnd)
      //{{AFX_MSG_MAP(CMyCwndClient)
      ON_WM_ERASEBKGND()
      ON_WM_SIZE()
      //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CMyCwndClient message handlers

BOOL CMyCwndClient::OnEraseBkgnd(CDC* pDC)
{
      // TODO: Add your message handler code here and/or call default
      CWnd::OnEraseBkgnd(pDC);
      
      CRect rect ;
      GetClientRect ( &rect ) ;
      
      CBrush br( RGB( 255,255,0) ) ;
      CBrush* pOldBrush = pDC->SelectObject(&br) ;
      pDC->PatBlt(rect.left + rect.Width()/4,
            rect.top + rect.Height()/4,
            rect.Width()/2,
            rect.Height()/2,
            PATCOPY) ;
      pDC->SelectObject(pOldBrush) ;
      
      return TRUE ;//CWnd::OnEraseBkgnd(pDC);
}

void CMyCwndClient::OnSize(UINT nType, int cx, int cy)
{
      CWnd::OnSize(nType, cx, cy);
      
      // TODO: Add your message handler code here



      Invalidate(FALSE);
      
}
A coulpe of points.

When you create an MDI project you can select a CFormView as the class for the child view (default is a CView).  A CFormView supports controls via the resource editor in the same way a dialog does.  What you are currently doing seems to be reinventing the wheel.

Flickering - just doing a repaint of the background (as you do) is a way to make your app flicker (see my previous comment #38158772) if the area being repainted is over a control.  To use such a crude approach it is better to just return true from the OnEraseBkgnd (NOT calling the base implementation) and have that code in the OnPaint - then all the repainting is done in response to one message.
Avatar of Surfer

ASKER

I am not looking to put a dialog or cformview in a cview /childframe windows but instead, in the single client area of the mainfrm window.

As for the flicker - I have managed to handle that original issue however, the problem has now become a problem with the dialog in the maimfrm not playing nice with the focus of the childframe windows when the are maximizes

I guess my question comes down to, how do I embed a dialog / cformview in the client area of my cmainfrm window and not have it adversely effect the mdi child windows?

Code sample welcome
in an SDI project a formview would be child window of the mainframe window and part of the client area. the advantage of using the predefined architecture over a self-defined one  is - beside of efficiency and not reinventing the wheel (see remarks of Andy) - that the repainting process is well-defined, what means it cares for the order of paintings better than you can do it and it normally does the paintings only once.

anyhow, you could try to fix the problems with resizing by removing the OnSize handler you currently were using. the OnSize was called "after" the window was painted in the new size. so the Invalidate call would cause an additional painting and likely is responsible for the current flicker. if the issue is not gone by using standard OnSize you may override the OnSizing what is a handler of the WM_SIZING message. that message was sent by the framework prior to the sizing and painting and you could avoid some of the paintings by comparing the previous and the current size of the window (frame). if you omit calling the baseclass implementation of OnSizing as long as the differences in size are not greater than a specific threshold, you could decrease the number of paintings significantly (though you may not omit the final OnSizing somehow, maybe by using a timer or a private message). you also could modify your OnEraseBkgnd function such that it is not painting while the user still has captured the frame.

regarding the focus behavior i strongly would advise against a non-standard behavior.  in the OnInitDialog of your dialog you can decide by returning TRUE or FALSE whether you want the focus to be set automatically (TRUE) to a control or not. if you return FALSE and don't have called SetFocus for one of your controls, none of the controls will have focus until the user clicked onto one of them.

Sara
Avatar of Surfer

ASKER

Sara,
There my be a misunderstanding.  My application is not SDI but instead MDI and I have no problem adding a CFormView to this client area if I only new how.  In fact, this is my question.  Does this change the first part of your response above?

Do you are anyone else show me the code required to add a CDialog or CFormView ( if that is a better approach ) to my CMainFrm client area that behaves the same way as the client area hosting the dialog or formview?
in the first part of my last response i only wanted to say that i share the estimation Andy has made that in a SDI or MDI app a CFormView derived class would fit better than one derived from a CDialog. a SDI would have the CMainFrame as only frame class while in a MDI you have a frame class derived from CMDIChildWnd for each document type. nevertheless those frames could be "invisible" when the mainframe could handle all frame functionality itself. in that case the formview would be visually in the client area of the mainframe (though actually it is the client area of the parent frame).

to use a form view just create a CFormView derived class and associate the dialog resource (ID) you already have. then use that formview class when you create a pointer of CMultiDocTemplate. after the formview worked, you could add the OnEraseBkGnd handler.

Sara
Avatar of Surfer

ASKER

Are you saying that the CFormView will handle the drawing updates / focus better then the CDialog if embedded in the MDI client area?  
Can you provide a code sample or link showing your above suggestion given my project parameters?
the point is that the formview actually is a modeless dialog. the advantage is that by using the mfc framework for integrating frame, view and dialog that the framework would do all that where you would need extra code when using a self-created modeless dialog window. moreover, the extra code would need many efforts to synchronize background and foreground and prevent the background to be painted two or more times, so causing flicker. with formview you have same "chances" to overdo and so create the issues which you want to avoid. but, as there is less work to do, you at least have a wizard generated code in the first approach, which is optimal and where you can fall back when customizing the code.

as told, it is all wizard code beside of the OnEraseBkGnd. create a new MDI project, add your form, create CFormView derived view class and modify creation of the CMDIDocTemplate in the InitInstance of your application class. if you have problems with that, we all can help you with the code. i have plenty of MDI code, but to strip it down to your needs it would need hours and finally would not run in your environment.

Sara
Avatar of Surfer

ASKER

I think you may be missing something with my situation. My app is a MDI app already using  CMDIDocTemplate in my  InitInstance method to setup the apps primary function (nothing to do with my current challenge ).  This  doc/view function has nothing to do with my current desire to add controls to my CMainFrm client.      
My current challenge is, to add a CDialog / CFormView to the MainFram client as shown in my code and have its focus and behavior mirror the client area it is embedded on.
if the formview works with wizard generated code, you could add the same view class 1:1 to your current mdi. you either could add a new mdi doc template to make it run or if you already have a doc view you can add the formview as a second view to the existing document.

the mainframe itself actually doesn't have a client view beside of the initial screen, which normally is empty. cause for any document opened or created, the associated frame would replace/use the mainframe and also replace the client view by its own views. if you don't have other frame windows but only the mainframe, your app actually is an SDI even if you created it as MDI. if so, it doesn't make much difference (for that you want to achieve) beside that we need to find the right words where we both know what was meant.

Sara
Avatar of Surfer

ASKER

Thanks for your help.

My app does use multiple doc/view pairs for its primary purpose so it is a MDI app.  

To this, I want to add controls to the main client area.  This main client area is the area seen when all the child windows are closed. To this area, is where I am trying to add controls.

Does that help clear it up and do you have example code or links to get me started?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Surfer

ASKER

Thanks for everyone help!
The solution I decided to go with is to simply SW_HIDE my dialog when Child windows are on top.  While this does not solve the persistent  underlying problem of ownership / focus of the dialog embedded in the main client area, it at least solves the "appearance" to the end user of what is going on.