Click here to Skip to main content
15,038,345 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Platform; Windows Mobile 6.0 (Professional editon)

hardware: O2 mobile with 600 mHz cpu, 128M mem

IDE: VS2005

Problem Description:

I've used MFC to build UI application in my current project, The UI interface of the app is very simple, it only has a scroll window with several controls on it. The application requires the scrolling of the window should be very smooth with no flicker. However, it seems that this requirement is difficult to achieve. Following is my puzzle:

I used CScrollview which has scrolling functionality without adding extra code. I found that the window was flickering when I dragged the scrollbar up and down. My first thought was maybe I put too much controls on the window, so I reduced the controls on the window to testify, but unfortunately the flicker was still there even I put only one button on the window.

I studied this problem for days and also searched on the internet, and finally I found the reason why the flicker happens. I'd like to do a little explanation on the scrolling procedure before I give the reason.

When the scroll bar is being drag from top to bottom:

1. The scroll bar sends a scroll message to it's parent window
2. The scroll window receives the message and then prepare to scroll. Suppose the scrolling results in 100 pixels movement and the whole window client area's height is 320 pixel
3. scroll window copies all pixels in bottom rectangle, whose height is 220 ( = 320 - 100 ) to the top, now the top 220 lines of the window has been painted.
4. Invokes the OnEraseBkgnd to paint the background of the window
5. Invokes OnDraw to paint the bottom rectangle, whose height is 100, now the whole window has been re-painted.
6. Move all child windows from bottom to top with 100 pixels offset
7. The window sends WM_PAINT to all it's children to re-painted the child windows or controls

The flicker problem is caused by clipping mechanism. As we known, the clipping mechanism is a way to paint window effectively, so the area occupied by child window will not be painting when the window paints the client area, and then the child window would paint this area by itself. However, this is not good for scrolling because it cause flicker. If there's a button on coordinate (0, 50) with 100 pixels width, 30 pixels height, the whole rectangle area occupied by this button is not copied and painted in step 3 because it is clipped. And this area will be painted with background color in step 4 because it is invalidated, then will be painted again in step 5 for normal content. Finally the area, whose coordinate is (0, -50), that the button would be moved to would be painted.

So there are four changes on the screen

1. Copy the screen from bottom to top
2. fill the button area, which is clipped, with background color
3. print the content on the clipped area
4. print the child window on its new position

All the changes will be seen by our eyes although the time of changing is very short. So we can't see the whole changing process clearly but flicker.

The solution to solve it is removing the style WS_CLIPCHILDREN. The clipping does not work without this style so the whole area will be copied, the change 2, 3 will be canceled. The change 4 will still be done but have no effect on eyes.

I have tested it on WIN32 MFC, and it works.

However, the removing of windows style WS_CLIPCHILDREN is forbidden on windows mobile , because the MSDN says "All windows implicitly have the WS_CLIPSIBLINGS and WS_CLIPCHILDREN styles." So we can't create the CScrollView without WS_CLIPCHILDREN.



How do i solve this flicker problem? Does someone know any solution or workaround for this problem? It really drive me crazy because i've spend the whole week on finding the solution.

Please let me know if i make any mistake on this description.
Posted
Updated 31-Oct-10 4:37am
v2
Comments
[no name] 1-Nov-10 6:53am
   
did you try double buffering?
Chen_Zhuo 1-Nov-10 11:12am
   
Yes, I tried. But it didn't work. Double buffering works fine on most flickering cases, but in my case the drawing didn't flicker but the control did. In my mind, the control can't be drawn on buffer, am i right?

1 solution

Still no answer? That's really sad. :((
I finally found a solution by myself, it's a little ugly but it work.
My solution is using my own scrolling function instead of the system's, following is detail
1. Overrides CScrollview::OnScrollBy, the core part of scrolling is implemented by this method.
2. Copies the MFC CScrollview::OnScrollBy source code to the overridden function
3. Uses following code instead of the line which has ::ScrollWindowEx
            CRect srcRect;
            GetClientRect(clientRect);
            // When visible, let Windows do the scrolling
            if( yAmount > 0 ){
                srcRect.DeflateRect( 0, 0, 0, yAmount );
            } else {
                srcRect.DeflateRect( 0, -yAmount, 0, 0 );
            }

            CDC* dc = GetDCEx( NULL, DCX_CACHE | DCX_WINDOW| DCX_CLIPSIBLINGS );
            CRect destRect = srcRect;
            destRect.OffsetRect( 0, yAmount );
            dc->BitBlt( destRect.left, destRect.top, destRect.Width(), destRect.Height(),
                dc, srcRect.left, srcRect.top,
                SRCCOPY);
            CRect invalidRect;
            GetClientRect( &invalidRect );
            invalidRect.SubtractRect( invalidRect, dest );
            InvalidateRect( &invalidRect, TRUE );
//             The original code
//             ::ScrollWindowEx(m_hWnd, xAmount, yAmount, NULL, NULL,
//                 NULL, NULL, SW_INVALIDATE | SW_ERASE);
        }

        {
            HWND hWndChild = ::GetWindow(m_hWnd, GW_CHILD);
            if (hWndChild != NULL)
            {
                for (; hWndChild != NULL;
                    hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))
                {
                    CRect rect;
                    ::GetWindowRect(hWndChild, &rect);
                    ScreenToClient(&rect);
                    ::SetWindowPos(hWndChild, NULL,
                        rect.left+xAmount, rect.top+yAmount, 0, 0,
                        SWP_NOSIZE|SWP_NOACTIVATE|SWP_NOZORDER);
                }
            }
        }


Note: The above code implements the vertical scrolling only, but it's easy to add horizontal implementation by yourself.

The principal of the code is using a non-clipped DC instead of clipped DC to do scrolling, GetDCEx() is used to get such non-clipped DC. Then the source and destination of bitblt() are calculated, and copies the content from source to destination according to the calculation result. Next calculates the invalid rect of the window and invalidate it, the system will automatically repaint this area later. Finally scrolls all child windows.

I've test it on the emulator and my O2 mobile phone, it works fine so far. I have to say it's not a perfect solution but it's the only way I can find till now.

Please let me know if you have any problem or suggestion with no hesitate.
goldencz(AT)263.net
   
Comments
Peter_in_2780 2-Nov-10 19:27pm
   
This is good stuff (even if I don't have any use for it!)
1. Have a +5.
2. Why don't you write it up as a tip/trick. This is the kind of stuff that makes CP the great site it is.

Cheers,
Peter
Chen_Zhuo 2-Nov-10 22:43pm
   
Yes, it's a good idea, I'll do it soon.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900