Interesting form paint behavior


A customer sent us a repro scenario of a problem. There was a fairly complicated form with a PageFrame tab control with several pages, each with grids and other controls on it. Sometimes, the form did not paint completely, even though the grid did. It seemed that if you ran the form over a network, it wouldn’t paint, but if you ran it on the local machine, it worked just fine. It turns out to be unrelated to a network, but more related to timing.


 


Before execution of each line of user code, VFP calls a routine that checks to see if there are any pending events in the Windows event queue to dispatch. For example, the user could have hit <esc> or there could be pending WM_PAINT messages to refresh windows.


That routine doesn’t check for messages each time it’s called:  it keeps track of how long it’s been since the last check and only checks if it’s been a while.


That way, if the user is in a tight FOR loop or long SQL Select statement that has no I/O, the <esc> key can interrupt execution, or Alt-Tab back/forth can repaint the windows.


 


When the form is activated. VFP invalidates the entire form rectangle which means Windows will send WM_PAINT messages to repaint it. The user has lots of code that executes when the form gets painted. This will eventually trigger a WM_PAINT message dispatched from that checking routine. Sounds recursive, doesn’t it?!?


The WM_PAINT dispatch forces the rect to be painted by calling a routine that sends a message to all controls to paint themselves. Then ValidateRect is called and VFP thinks the entire form has been painted.


 


When a control (such as a button on a page or a grid within a grid) is painted, a clipping rectangle is set to the control’s parent container, so that the control doesn’t paint outside its container’s rectangle. If a WM_PAIINT message is dispatched while the clipping rectangle is set, the ValidateRect call says that the entire form has been painted.


 


A simple way to avoid this behavior is to set _VFP.AutoYield to 0. This will suppress checking and dispatching of windows messages while executing user code.


 


This code will demonstrate. (Run it a couple times. You might need to tweak the numbers a little). It’s a pageframe with a grid on it. The grid has DynamicBackColor set to call a method that takes a while to execute and alternates boy and girl colors for the rows. The commandbutton and the form background don’t paint correctly: Whatever was behind is now visible. If you force a paint (perhaps by covering/uncovering the window) it paints correctly.


 


 


 


_vfp.AutoYield=1


 


CREATE CURSOR foo (name c(10))


FOR i = 1 TO 100


      INSERT INTO foo VALUES (TRANSFORM(i))


ENDFOR


LOCATE


PUBLIC x


x=NEWOBJECT(“myform”)


x.show


ACTIVATE SCREEN


x.show


DEFINE CLASS myform as Form


      allowoutput=.f.


      top=300


      left=200


      PROCEDURE init


            this.AddObject(“pf”,”pageframe”)


            this.pf.pagecount=5


            this.pf.top=20


            this.pf.page1.addobject(“gr”,”grid”)


            this.pf.page1.gr.column1.DynamicBackColor=”thisform.getcolor(RECNO())”


            this.AddObject(“cmd”,”commandbutton”)


            this.SetAll(“visible”,1)


      PROCEDURE getcolor(n)


            ns=SECONDS()+.1


            DO WHILE SECONDS() < ns


            ENDDO


            IF MOD(n,2)=0


                  RETURN 0xffc0c0


            ENDIF


            RETURN 0xc0c0ff


ENDDEFINE


 

Comments (18)

  1. Craig Boyd says:

    Interesting issue and nice repro example. However, I’m more interested in your statement, "The WM_PAINT dispatch forces the rect to be painted by calling a routine that sends a message to all controls to paint themselves." Would you mind pulling the curtain back a little further and telling me what the name of that routine is and a little more about how it works? I’m not asking about the usual BeginPaint, Paint, EndPaint response to a WM_PAINT message… more about how Visual FoxPro is doing it internally given the off-screen bitmap and windowless controls. Thanks.

  2. I’ve begun playing around with Visual FoxPro’s ReportPreview project. Eventually

  3. Craig Boyd says:

    Hi Calvin,

    I have no idea why all these comments are showing up from my site. It is almost as if your blog is treating the referrals from my site (I included a link to this entry in something I wrote earlier today) as a comment. So, everytime someone clicks on that link on my blog another comment shows up over here.

    With the number of people that read my blog daily, this could end up being really messy. Any ideas why your blog is picking it up as a comment rather than a referral? I’ve triple checked the link, it’s the correct one and nothing fancy about it.

  4. In my last post Enable crop and zooming in on your digital photograph display form&amp;nbsp; there is code…

  5. Simon White says:

    I am interested to know if something similar happens using SetAll.

    I use Setall to trigger a method on all the appropriate objects. I use listboxes for table navigation and when I change the selected element in a listbox I use:

    This.Parent.SetAll("ctriggerMethod",This.cRecordSource+".RefreshControl()")

    The cTriggerMethod_Assign method looks like this:

    LParameters tcAction

    =This.myControlSource=JustStem(tcAction).And.Eval("This."+JustExt(tcAction))

    The net result is that all the controls that are bound to the same table as the listbox are refreshed. This works perfectly accept when you rapidly select differect items in the list using the mouse. The controls do not get refreshed with the current record data. They are usually one or two selections behind.

    If I simply issue a

    _Screen.ActiveForm.LockScreen=.T.

    _Screen.ActiveForm.LockScreen=.F.

    The controls are painted correctly with the up to date values.

    The AutoYield setting does not appear to make any difference in this case.

    The reason I am not using the native data binding is because the table can store encrypted and compressed data which I obviously modify before displaying on the screen.

    Thanks