Force Repaint of Excel Workbook (WinAPI)

Superbead

New Member
Joined
Nov 30, 2011
Messages
1
Hi there, Superbead here –

I need Very Fast Redraw in Excel for game animation purposes, which I understand's a big ask, although I'm reasonably sure I'm not too far away from achieving it. I'm using VBA for Excel 2007 on an XP SP3 machine. Basically I need to alternate the colours of a couple of thousand cells in the time of an animation frame (less than 1/10s) without any flicker or stutter.

I've tried switching Application.Screenupdating in the game loop, and while it's False the cell colours update fantastically quickly, but the very switching of this application variable every frame introduces a significant delay. My idea was to turn .Screenupdating off at the very beginning, and then force repaints of the Workbook window with WinAPI calls each frame to avoid the overhead of accessing the Application object.

Here's an demo which attempts to use WM_SETREDRAW, InvalidateRect() and UpdateWindow() to alternate the colour of cell 10,10 between red and green. I say 'attempts', as it doesn't work, and I can't work out why.

(Test() is called from, let's say, Worksheet_Activate.)

Code:
Option Explicit
Option Base 0

Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
    ByVal hWndParent As Long, _
    ByVal hWndChildAfter As Long, _
    ByVal lpszClass As String, _
    ByVal lpszWindow As String _
        ) As Long
    
Private Declare Function InvalidateRect Lib "user32" ( _
    ByVal hWnd As Long, _
    ByRef lpRect As Long, _
    ByVal bErase As Long _
        ) As Long

Private Declare Function UpdateWindow Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
    ByVal hWnd As Long, _
    ByVal Msg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long _
        ) As Long

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


Private Const WM_SETREDRAW As Long = &HB&


' #######################################

Private Function GethWndWorkbook() As Long

Dim hWndXLDESK As Long

    hWndXLDESK = FindWindowEx( _
        Application.hWnd, _
        0, _
        "XLDESK", _
        vbNullString)
    GethWndWorkbook = FindWindowEx( _
        hWndXLDESK, _
        0, _
        vbNullString, _
        ActiveWorkbook.Name)
        
End Function

' #######################################

Private Sub ScreenUpdate(hWnd As Long, bState As Boolean)

Dim lResult As Long

    If bState Then
        lResult = SendMessage(hWnd, WM_SETREDRAW, 1&, 0&)
        lResult = InvalidateRect(hWnd, 0&, 1&)
        lResult = UpdateWindow(hWnd)
    Else
        lResult = SendMessage(hWnd, WM_SETREDRAW, 0&, 0&)
    End If
    
End Sub

' #######################################

Public Sub Test()

Dim hWnd As Long
Dim I As Long

    hWnd = GethWndWorkbook
    
    Application.ScreenUpdating = False
    
    For I = 1 To 100
    
        ScreenUpdate hWnd, False
        Worksheets(1).Cells(10, 10).Interior.Color = RGB(255, 0, 0)
        ScreenUpdate hWnd, True
        
        Sleep 10
        
        ScreenUpdate hWnd, False
        Worksheets(1).Cells(10, 10).Interior.Color = RGB(0, 255, 0)
        ScreenUpdate hWnd, True
        
        Sleep 10
        
    Next I
    
End Sub
Supposedly, WM_SETREDRAW with wParam=True allows redrawing, InvalidateRect() tells the window its whole client area needs repainting on the next WM_PAINT message, and UpdateWindow() sends that WM_PAINT message.

However – the cell never appears to change colour. MS Spy++ confirms I've got the correct hWnd for the Workbook window, but the only WM_PAINT message received is when the code exits (and presumably Application.Screenupdating is reset to True). The WM_SETREDRAW on and off messages are shown in Spy++ as received, though.

Strangely, sending WM_PAINT after setting WM_SETREDRAW true does result in the Workbook window receiving paint messages, but regardless the window is never repainted, and the cell still cannot be seen to change colour. I'm aware you're not supposed to send WM_PAINT to other processes, at least not according to the XP SDK, but I thought I'd give it a crack.

Has anyone any ideas as to why UpdateWindow isn't working? Or even, a better suggestion? (PS. I've already tried palette rotation with both Excel and GDI, and that's a no-go.)

Regards – SB


The background:

I'm porting Jet Set Willy (an old 8-bit 1980s platform game) from the ZX Spectrum to Excel. My graphics 'engine' manipulates the interior colours of shrunken cells to simulate the Spectrum's 256x192px display. Although I've exceeded my expectations of how far I'd get in terms of smooth drawing and animation, I'm having trouble with a particularly slow-to-draw element. In case anyone remembers it, it's the Speccy's FLASH attribute which exchanged the background and foreground colours of 8x8px cells about three or four times a second. No matter how super-efficient my drawing routines are, the 'wash' effect can easily be seen when redrawing thousands of cells at once.
 

Excel Facts

Why does 9 mean SUM in SUBTOTAL?
It is because Sum is the 9th alphabetically in Average, Count, CountA, Max, Min, Product, StDev.S, StDev.P, Sum, VAR.S, VAR.P.
Hi Superbead and welcome to MrExcel. I remember JSW as an incredibly addictive game with good graphics and sound for its time, given the limitations of the Spectrum.

The following code might do what you need; it flashes the cell between red and green without the screen flickering. I found the code at http://weblogs.asp.net/jdanforth/archive/2004/03/12/88458.aspx and both the LockWindowUpdate and SendMessage techniques are implemented.
Code:
Option Explicit
Option Base 0


Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
    ByVal hWndParent As Long, _
    ByVal hWndChildAfter As Long, _
    ByVal lpszClass As String, _
    ByVal lpszWindow As String _
        ) As Long
    
Private Declare Function InvalidateRect Lib "user32" ( _
    ByVal hWnd As Long, _
    ByRef lpRect As Long, _
    ByVal bErase As Long _
        ) As Long

Private Declare Function UpdateWindow Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function LockWindowUpdate Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
    ByVal hWnd As Long, _
    ByVal Msg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long _
        ) As Long

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


Private Const WM_SETREDRAW As Long = &HB&
Private Const WM_USER = &H400
Private Const EM_GETEVENTMASK = (WM_USER + 59)
Private Const EM_SETEVENTMASK = (WM_USER + 69)


' #######################################

Private Function GethWndWorkbook() As Long

Dim hWndXLDESK As Long

    hWndXLDESK = FindWindowEx( _
        Application.hWnd, _
        0, _
        "XLDESK", _
        vbNullString)
    GethWndWorkbook = FindWindowEx( _
        hWndXLDESK, _
        0, _
        vbNullString, _
        ActiveWorkbook.Name)
        
End Function

' #######################################

Private Sub ScreenUpdate(hWnd As Long, bState As Boolean)

Dim lResult As Long

'SendMessage(hEdit, %WM_SETREDRAW, 0, 0)  'Turn off redraw for faster and smoother action
'SendMessage hEdit, %WM_SETREDRAW, 1, 0   'Turn on redraw again and refresh

    If bState Then
        lResult = SendMessage(hWnd, WM_SETREDRAW, 1&, 0&)
        lResult = InvalidateRect(hWnd, 0&, 1&)
        lResult = UpdateWindow(hWnd)
        DoEvents
    Else
        lResult = SendMessage(hWnd, WM_SETREDRAW, 0&, 0&)
    End If
    
End Sub

' #######################################

Public Sub Test()

Dim hWnd As Long
Dim I As Long

    hWnd = GethWndWorkbook
    
    Application.ScreenUpdating = False
    
    For I = 1 To 100
    
        ScreenUpdate hWnd, False
        Worksheets(1).Cells(10, 10).Interior.Color = RGB(255, 0, 0)
        ScreenUpdate hWnd, True
        
        Sleep 10
        
        ScreenUpdate hWnd, False
        Worksheets(1).Cells(10, 10).Interior.Color = RGB(0, 255, 0)
        ScreenUpdate hWnd, True
        
        Sleep 10
        
    Next I
    
End Sub

'===============================================================

Private Sub Draw1(hWnd As Long, colour As Long)

    LockWindowUpdate hWnd
    
    'Change cell colour
    Worksheets(1).Cells(10, 10).Interior.Color = colour
    
    LockWindowUpdate 0

End Sub


Private Sub Draw2(hWnd As Long, colour As Long)

    Dim eventMask As Long
    
    'Stop redrawing
    SendMessage hWnd, WM_SETREDRAW, 0, 0
    
    'Stop sending of events
    eventMask = SendMessage(hWnd, EM_GETEVENTMASK, 0, 0)
    
    'Change cell colour
    Worksheets(1).Cells(10, 10).Interior.Color = colour

    'Turn on events
    SendMessage hWnd, EM_SETEVENTMASK, 0, eventMask
    
    'Turn on redrawing
    SendMessage hWnd, WM_SETREDRAW, 1, 0
    

End Sub


Public Sub Draw1Test()

    Dim hWnd As Long
    Dim I As Long

    hWnd = GethWndWorkbook
    
    For I = 1 To 100
        Draw1 hWnd, RGB(255, 0, 0)
        Sleep 10
        Draw1 hWnd, RGB(0, 255, 0)
        Sleep 10
    Next
    
End Sub


Public Sub Draw2Test()

    Dim hWnd As Long
    Dim I As Long

    hWnd = GethWndWorkbook
    
    For I = 1 To 100
        Draw2 hWnd, RGB(255, 0, 0)
        Sleep 10
        Draw2 hWnd, RGB(0, 255, 0)
        Sleep 10
    Next
    
End Sub
Good luck with the rest of your project. I would be interested to see the final game!
 
Upvote 0

Forum statistics

Threads
1,223,365
Messages
6,171,654
Members
452,415
Latest member
mansoorali

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top