QA: Why does my child list view control repaints when the SIP is toggled?
Joao Paulo Figueira (joao.fig@mail.telepac.pt), August 08, 2003.
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
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:
-
http://www.pocketpcdn.com/sections/sip.html
Section: SIP
-
http://www.pocketpcdn.com/articles/sip.html
Article: Working with SIP
-
http://www.pocketpcdn.com/articles/setcurrentim.html
QA: How can I set the qwerty keyboard as the current input method?
-
http://www.pocketpcdn.com/articles/wordcompletion.html
QA: How can I disable SIP word completion?
-
http://www.pocketpcdn.com/articles/im.html
Article: Custom input method for SIP