News | Articles | Libraries | Developer Tools | Books | Forum Links | Search   
Sections:
 

QA: Why does my child list view control repaints when the SIP is toggled?

By Joao Paulo Figueira, August 08, 2003.
Print version

Question

My MFC application has a CListCtrl as the CMainFrame's child. When I open or close the SIP the CListCtrl will always repaint itself. Why? How can I prevent this?

Answer

Actually, there will be one situation where the list control will not get repainted and another where it will always repaint. This first situation happens when the SIP does not cover any of the list's displayed data. The second situation will happen when, by opening or closing the SIP, the resizing of the list will imply showing or hiding the scrollbar(s). This is default behaviour. But what happens when the list has enough data to display the vertical scrollbar and you open the SIP? The File Explorer application uses a list view control to display the files and it will not repaint under these circumstances. So why is it different under MFC?

MFC will always change the window classes it creates, unless we tell it not to. This is important because, by default, MFC will always add the CS_VREDRAW and CS_HREDRAW to all window classes it uses to create windows, even to the common control classes. As a matter of fact, MFC will superclass these and will even rename them, prepending a WCE_ prefix to the class name. See http://www.pocketpcdn.com/articles/wce_prefix.html.

So you are not really using a SysListView32, but a WCE_SysListView32 with the CS_VREDRAW and CS_HREDRAW styles. These will force the window to repaint itself when its size changes, a situation that is forced when the SIP changes its state.

Also, your CMainFrame window class will have these style bits set. When you resize its child window (whatever its window class bits are) it will also repaint. So, to solve this problem, we will need to address both windows, and make sure that they will not have those window class style bits set.

Fixing the List

Fixing the list view control is simple: create it using the API and then subclass it. The sample application shows how to do this in the CDemoList class. Here are the highlights of the class implementation:

HWND CDemoList::CreateList(CWnd *pParent) { HWND hWnd; DWORD dwStyle = 0, dwExStyle = 0; CWinApp* pApp = AfxGetApp(); dwStyle = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SINGLESEL; // // By using CreateWindowEx and then subclassing, we make sure we are using // a "straight" list view control, not MFC's superclassed one. // hWnd = ::CreateWindowEx(dwExStyle, WC_LISTVIEW, NULL, dwStyle, 0, 0, 0, 0, *pParent, (HMENU)AFX_IDW_PANE_FIRST, pApp->m_hInstance, NULL); if(hWnd) { if(SubclassWindow(hWnd)) { // // Prepare the list here. Note that by using this method, // we lose access to the OnCreate event (WM_CREATE message). // This message will be processed by the list view control's // native window proc. // InsertColumn(0, _T("Color"), LVCFMT_LEFT, 80, -1); InsertColumn(1, _T("R"), LVCFMT_RIGHT, 35, 1); InsertColumn(2, _T("G"), LVCFMT_RIGHT, 35, 2); InsertColumn(3, _T("B"), LVCFMT_RIGHT, 35, 3); InsertColor(_T("White"), RGB(255, 255, 255)); InsertColor(_T("Teal"), RGB( 0, 255, 255)); InsertColor(_T("Purple"), RGB(255, 0, 255)); InsertColor(_T("Blue"), RGB( 0, 0, 255)); InsertColor(_T("Light grey"), RGB(192, 192, 192)); InsertColor(_T("Dark grey"), RGB(128, 128, 128)); InsertColor(_T("Dark teal"), RGB( 0, 128, 128)); InsertColor(_T("Dark purple"), RGB(128, 0, 128)); InsertColor(_T("Dark blue"), RGB( 0, 0, 128)); InsertColor(_T("Yellow"), RGB(255, 255, 0)); InsertColor(_T("Green"), RGB( 0, 255, 0)); InsertColor(_T("Dark yellow"), RGB(128, 128, 0)); InsertColor(_T("Dark green"), RGB( 0, 128, 0)); InsertColor(_T("Red"), RGB(255, 0, 0)); InsertColor(_T("Dark red"), RGB(128, 0, 0)); InsertColor(_T("Black"), RGB( 0, 0, 0)); InsertColor(_T("Black"), RGB( 0, 0, 0)); InsertColor(_T("Black"), RGB( 0, 0, 0)); } else return NULL; } return hWnd; }

When the window is being destroyed, you have to unsubclass it:

void CDemoList::OnDestroy() { CListCtrl::OnDestroy(); UnsubclassWindow(); }

One drawback you have to be aware of is that by using this approach, you will lose the ability to intercept the WM_CREATE message, so your OnCreate handler will never be called. But, by doing this, you have created a bona fide SysListView32 window.

Fixing the Frame

Fixing the CMainFrame window involves another approach, where you have to force MFC to register a window class without the CS_VREDRAW and CS_HREDRAW class styles. The best way to do it is by overriding the CFrameWnd LoadFrame virtual method. Add the following declaration to your CMainFrame class, in the public section:

virtual BOOL LoadFrame(UINT nIDResource, DWORD dwDefaultStyle = WS_CHILD|WS_BORDER|WS_CLIPSIBLINGS, CWnd* pParentWnd = NULL, CCreateContext* pContext = NULL);

And here is the implementation, using most of CFrameWnd's code:

BOOL CMainFrame::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext) { CString strClass; strClass = AfxRegisterWndClass(CS_DBLCLKS, 0, (HBRUSH)::GetStockObject(WHITE_BRUSH)); // only do this once ASSERT_VALID_IDR(nIDResource); ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource); m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE) CString strFullString; if(strFullString.LoadString(nIDResource)) AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string // attempt to create the window LPCTSTR lpszTitle = m_strTitle; if(!Create(strClass, lpszTitle, WCE_IF(WS_VISIBLE, dwDefaultStyle), rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext)) { return FALSE; // will self destruct on failure normally } // save the default menu handle ASSERT(m_hWnd != NULL); m_hMenuDefault = ::WCE_FCTN(GetMenu)(m_hWnd); // load accelerator resource LoadAccelTable(MAKEINTRESOURCE(nIDResource)); if(pContext == NULL) // send initial update SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE); return TRUE; }

Don't forget to add the following include directive in the cpp file:

#include <afxpriv.h>

Finally, you will have to comment out (or delete) one line in the PreCreateWindow code generated by the wizard:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs // cs.lpszClass = AfxRegisterWndClass(0); return TRUE; }

This way, your main frame will have the correct window class style bits, and the unnecessary repainting will be avoided.

Thank You

To Alexander Shargin who provided precious help in cracking this one down.

Sample

You can also download a sample application - NoRepaint.zip - 28K.

Related resources:

Discuss

Discuss this article. Here you can write your comments and read comments of other developers.
Rate this article:     Poor Excellent    
 12345 
© 2001-2005 Pocket PC Developer Network, a division of Spb Software House