Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Refreshing form smoother via Cls than Refresh. Why? 4

Status
Not open for further replies.

Olaf Doschke

Programmer
Oct 13, 2004
14,847
DE
First of all you typically refresh a form via Thisform.Refresh(), but can also enforce the total repainting of the form by thisform.Cls().

Recently I did the following simple progress form and initially used Refresh, as I thought that a) would be enough and b) would be faster and more often refresh the progress bar. The real world code does more in the while loop, but for demonstration purposes this code just updating the progress bar is sufficent as repro code:

Code:
PUBLIC oform1
oform1=NEWOBJECT("Progressform")
oform1.Show
RETURN

DEFINE CLASS Progressform AS form
	BorderStyle = 1
	Height = 40
	Width = 800
	AutoCenter = .T.
	TitleBar = 0
	AlwaysOnTop = .T.
	InitialWidth = 0

	ADD OBJECT shpprogress AS shape WITH ;
		Top = 0, ;
		Left = 0, ;
		Height = 40, ;
		Width = 800, ;
		BorderStyle = 0, ;
		BorderWidth = 0, ;
		FillStyle = 0, ;
		FillColor = RGB(32,192,64)

	ADD OBJECT tmrstartprocess AS timer WITH ;
		Enabled = .T., ;
		Interval = 1,  ;
		Name = "tmrStartProcess"

	PROCEDURE Process
		Local t0, lnProgress
		t0 = Seconds()
		lnProgress = 0
		Do While lnProgress<100
		   lnProgress = 100*(Seconds()-t0)/3
                   Thisform.ShowProgress(lnProgress)
                   * Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,lnProgress/100 * Thisform.InitialWidth))
                   * Thisform.Cls()

                   Doevents
		EndDo
	EndProc
   
   Procedure ShowProgress
         Lparameters tnProgress
         Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,tnProgress/100 * Thisform.InitialWidth))
         Thisform.Cls() && <-------------- try Thisform.Refresh() here instead
   Endproc

	PROCEDURE shpprogress.Init
		Thisform.InitialWidth = This.Width
		This.Width = 0
	ENDPROC

	PROCEDURE tmrstartprocess.Timer
		This.Enabled = .F.
		Thisform.Process()
		Thisform.Release()
	ENDPROC

ENDDEFINE

Start this as is, and the progress bar will smoothly fill up from left to right.
Now change the Cls() to Refresh() (see the comment) and let it run again. Now the bar will fill less smooth, the rectangle will grow in abrupt steps (maybe it's just on my hardware?)

This already establishes the question of why Cls() is better than Refresh(), but on top of that the behavior differs, if you don't have the ShowProgress code in a separate method.
So comment the call of the ShowProgress method in the Process Method and uncomment the lines below, which do the same. Again smooth, as Cls() is used. But now change Cls() to Refresh() again, and it stays smooth.

That's odd, isn't it?

And you can even top it off, by doing COVERAGE logging. I get more iterations with Refresh() than with Cls(), even in the case the progress bar grows abruptly. So coverage measures Refresh() to be faster than Cls(), which I would have expected in the first place. It seems Refresh() calls are only causing an event queuing in the Windows event queue and eventually be executed once the OS has time for it, while Cls() enforces a direct repaint. So far my thoughts on this.

But what influence does the separate method have, why does the progress show extremely abrupt in the case I do Refresh() instead of Cls() in the separate ShowProgress method, while coverage then still logs more iterations in the case of using Refresh() than when using Cls()?

I don't get it.

Bye, Olaf.
 
Here the Coverage log counts:
I simply used OCCURS on the line setting the shape width to count how many times it's updated.

CLS in ShowProgress Method: 15618
REFRESH in ShowProgress Method: 28929 <---- worst abrupt visual refreshes

CLS in Process Method: 40084
REFRESH in Process Method: 48100

Even for the lowest number of iterations I would expect a smoothly filling progressbar, as it's only 800 pixels wide, so each width from 0-800 would be set about 20 times in 15618 iterations and even a 100Hz monitor would only show 300 frames in 3 seconds anyway. Even more so with 28929 iterations.

You don't need to turn on COVERAGE, you could also simply add a counter variable, I used coverage to eventally see which steps take so long to cause only a few abrupt updates, but indeed no code line looks suspicious.

Bye, Olaf.
 
This is certainly a rum do. I can confirm the behaviour you are seeing. I see exactly the same results, and I can't explain it either.

That said, it would never occur to me to do a Refresh in this situation. I work on the assumption that a Refresh only has an effect where there is some underlying data involved (a control source) which is not the case here. I know that a Refresh also re-paints the screen, even if no data is actually refreshed, but I assume that even in that case, there will still be a performance penalty with Refresh.

But that's got nothing to do with the difference in behaviour we are seeing when you remove the function call.

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Yes, I see, you can do neither Refresh() nor Cls() as a third option. That also runs smooth in case you set the shape width in the same method.

I think I leave it as a mystery and if I have much time take process monitor to figure out what happens "behind the scenes".

Thanks for confirming what I see, it's not easy to judge by sing remote desktop, as that refreshes abruptly anyway. I'll take this third option and not do anything but change the shape width.

Bye, Olaf.
 
It seems that ThisForm.Paint() is fired more frequently when ThisForm.cls() is use than ThisForm.Refresh() or none is used.
Code:
PUBLIC oform1,gni
gni=0
oform1=NEWOBJECT("Progressform")
oform1.Show
RETURN

DEFINE CLASS Progressform AS form
	BorderStyle = 1
	Height = 40
	Width = 800
	AutoCenter = .T.
*	TitleBar = 0
	AlwaysOnTop = .T.
	InitialWidth = 0

	ADD OBJECT shpprogress AS shape WITH ;
		Top = 0, ;
		Left = 0, ;
		Height = 40, ;
		Width = 800, ;
		BorderStyle = 0, ;
		BorderWidth = 0, ;
		FillStyle = 0, ;
		FillColor = RGB(32,192,64)

	ADD OBJECT tmrstartprocess AS timer WITH ;
		Enabled = .T., ;
		Interval = 1,  ;
		Name = "tmrStartProcess"

	PROCEDURE Process
		Local t0, lnProgress
		t0 = Seconds()
		lnProgress = 0
		Do While lnProgress<100
		   lnProgress = 100*(Seconds()-t0)/3
                   Thisform.ShowProgress(lnProgress)
                   * Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,lnProgress/100 * Thisform.InitialWidth))
                   * Thisform.Cls()

                   Doevents
		ENDDO
		MESSAGEBOX(gni)
	EndProc
   
   Procedure ShowProgress
         Lparameters tnProgress
         Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,tnProgress/100 * Thisform.InitialWidth))
         Thisform.Cls() && <-------------- try Thisform.Refresh() here instead
*        Thisform.Refresh()
   Endproc

	PROCEDURE shpprogress.Init
		Thisform.InitialWidth = This.Width
		This.Width = 0
	ENDPROC
	
	PROCEDURE paint
		gni=gni+1
	ENDPROC

	PROCEDURE tmrstartprocess.Timer
		This.Enabled = .F.
		Thisform.Process()
		Thisform.Release()
	ENDPROC

ENDDEFINE

Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
Just added a ThisForm.pset() and not it's more obvious the difference between ThisForm.cls() and ThisForm.Refresh() (or none)
Also I set the DrawWidth to 5.

Code:
PUBLIC oform1,gni
RAND(-1)
gni=0
oform1=NEWOBJECT("Progressform")
oform1.Show
RETURN

DEFINE CLASS Progressform AS form
	BorderStyle = 1
	Height = 400
	Width = 800
	AutoCenter = .T.
	TitleBar = 0
	AlwaysOnTop = .T.
	InitialWidth = 0
	DrawWidth=5

	ADD OBJECT shpprogress AS shape WITH ;
		Top = 0, ;
		Left = 0, ;
		Height = 400, ;
		Width = 800, ;
		BorderStyle = 0, ;
		BorderWidth = 0, ;
		FillStyle = 0, ;
		FillColor = RGB(32,192,64)

	ADD OBJECT tmrstartprocess AS timer WITH ;
		Enabled = .T., ;
		Interval = 1,  ;
		Name = "tmrStartProcess"

	PROCEDURE Process
		Local t0, lnProgress
		t0 = Seconds()
		lnProgress = 0
		Do While lnProgress<100
		   lnProgress = 100*(Seconds()-t0)/3
                   Thisform.ShowProgress(lnProgress)
                   * Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,lnProgress/100 * Thisform.InitialWidth))
                   * Thisform.Cls()

                   Doevents
		ENDDO
		MESSAGEBOX(gni)
	EndProc
   
   Procedure ShowProgress
         Lparameters tnProgress
         ThisForm.PSet(INT(RAND()*ThisForm.Width),INT(RAND()*ThisForm.Height))
         Thisform.shpProgress.Width = Min(Thisform.Width,Max(0,tnProgress/100 * Thisform.InitialWidth))
*         Thisform.Cls() && <-------------- try Thisform.Refresh() here instead
*        Thisform.Refresh()
   Endproc

	PROCEDURE shpprogress.Init
		Thisform.InitialWidth = This.Width
		This.Width = 0
	ENDPROC
	
	PROCEDURE paint
		gni=gni+1
	ENDPROC

	PROCEDURE tmrstartprocess.Timer
		This.Enabled = .F.
		Thisform.Process()
		Thisform.Release()
	ENDPROC

ENDDEFINE

Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
The following example shows more clearly the relation between PAINT() and CLS() and the relation between PAINT() and Refresh()

Code:
PUBLIC ofrm,nCo,nCo2
nCo=0
nCo2=0
ofrm=CREATEOBJECT("MyForm")
ofrm.show

DEFINE CLASS myform as Form
	ADD OBJECT cmd1 as commandbutton WITH caption='CLS'
	ADD OBJECT cmd2 as commandbutton WITH caption='Refresh',top=50
	ADD OBJECT txt2 as textbox WITH top=50,left=100,controlsource='m.nCo'
	ADD OBJECT cmd3 as commandbutton WITH caption='Refresh',top=100
	ADD OBJECT txt3 as textbox WITH top=100,left=100,controlsource='m.nCo2'

	PROCEDURE cmd1.click
		LOCAL lni
		nCo=0
		FOR lni=1 TO 100 && each cls trigger a paint
			ThisForm.Cls
		NEXT
		MESSAGEBOX('CLS triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd2.click
		LOCAL lni
		nCo=0
		FOR lni=1 TO 100000
			ThisForm.refresh
		NEXT
		MESSAGEBOX('Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd3.click
		LOCAL lni
		nCo=0
		nCo2=0
		FOR lni=1 TO 100000
			nCo2=m.nCo2+1
			ThisForm.refresh
		NEXT
		MESSAGEBOX('Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE paint
		nCo=m.nCo+1
	ENDPROC
ENDDEFINE


Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
Code:
PUBLIC ofrm,nCo,nCo2,nCo3
nCo=0
nCo2=0
nCo3=0
ofrm=CREATEOBJECT("MyForm")
ofrm.show

DEFINE CLASS myform as Form
	ADD OBJECT cmd1 as commandbutton WITH caption='CLS'
	ADD OBJECT cmd2 as commandbutton WITH caption='Refresh',top=50
	ADD OBJECT txt2 as textbox WITH top=50,left=100,controlsource='m.nCo'
	ADD OBJECT cmd3 as commandbutton WITH caption='Refresh',top=100
	ADD OBJECT txt3 as textbox WITH top=100,left=100,controlsource='m.nCo2'

	PROCEDURE cmd1.click
		LOCAL lni
		nCo=0
		nCo3=0
		FOR lni=1 TO 100 && each cls trigger a paint
			ThisForm.Cls
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' CLS triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd2.click
		LOCAL lni
		nCo=0
		nCo3=0
		FOR lni=1 TO 100000
			ThisForm.refresh
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd3.click
		LOCAL lni
		nCo=0
		nCo2=0
		nCo3=0
		FOR lni=1 TO 100000
			nCo2=m.nCo2+1
			ThisForm.refresh
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE paint
		nCo=m.nCo+1
	ENDPROC
	
	PROCEDURE refresh
		nCo3=m.nCo3+1
	ENDPROC

	PROCEDURE cls
		nCo3=m.nCo3+1
	ENDPROC
ENDDEFINE

Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
Thanks, Vilhelm-Ion

What i thought, CLS triggers PAINT directly, REFRESH only sometimes, Doing neither does not reliable repaint often enough.
So now I do CLS but in the CLS method check, whether the last paint was long ago (1/50 seconds):

Code:
Public ofrm,nCoP
ofrm=Createobject('MyForm')
ofrm.Show

Define Class myform As Form
	Add Object cmd1 As CommandButton With Caption='start',Top=0, Width=200
	Add Object shp1 As Shape With Top = 25, Width = 0
	lastpaint=0

	Procedure cmd1.Click
		Local lni, t0, t1
		nCoP=0
		t0 = Seconds()
		For lni=1 To 40000
			Thisform.showprogress(lni)
		Next
		t1 = Seconds()-t0
		Messagebox('PAINT was triggered '+Transform(m.nCoP)+' times in '+Transform(m.t1)+' seconds, '+Transform(m.nCoP/m.t1)+' Hz.',0,'Cls')
	Endproc

	Procedure showprogress(tni)
		Thisform.shp1.Width = m.tni/200
		Thisform.Cls()
	Endproc

	Procedure Paint
		nCoP=m.nCoP+1
		Thisform.lastpaint = Seconds()
	Endproc

	Procedure Cls
		If Seconds()-Thisform.lastpaint<1/50
			Nodefault
		Endif
	Endproc

Enddefine

Bye, Olaf.
 
The frequency of paint() triggered by refresh() increase with number and the complexity of objects to be refreshed. (I expected otherwise)
Code:
PUBLIC ofrm,nCo,nCo2,nCo3
nCo=0
nCo2=0
nCo3=0
ofrm=CREATEOBJECT("MyForm")
ofrm.show

DEFINE CLASS myform as Form
	ADD OBJECT cmd1 as commandbutton WITH caption='CLS'
	ADD OBJECT cmd2 as commandbutton WITH caption='Refresh',top=50
	ADD OBJECT txt2 as textbox WITH top=50,left=100,controlsource='m.nCo'
	ADD OBJECT cmd3 as commandbutton WITH caption='Refresh',top=100
	ADD OBJECT txt3 as textbox WITH top=100,left=100,controlsource='m.nCo2'

	ADD OBJECT txt40 as textbox WITH top=200,left=100
	ADD OBJECT txt41 as textbox WITH top=200,left=100
	ADD OBJECT txt42 as textbox WITH top=200,left=100
	ADD OBJECT txt43 as textbox WITH top=200,left=100

*!*		ADD OBJECT txt4 as textbox WITH top=200,left=100,controlsource='m.nCo2'
*!*		ADD OBJECT txt5 as textbox WITH top=200,left=100,controlsource='m.nCo2'
*!*		ADD OBJECT txt6 as textbox WITH top=200,left=100,controlsource='m.nCo2'
*!*		ADD OBJECT txt7 as textbox WITH top=200,left=100,controlsource='m.nCo2'

*!*		ADD OBJECT txt8 as textbox WITH top=200,left=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt9 as textbox WITH top=200,left=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt10 as textbox WITH top=200,left=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt11 as textbox WITH top=200,left=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt12 as textbox WITH top=200,left=200,controlsource='m.nCo2'

*!*		ADD OBJECT txt13 as textbox WITH top=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt14 as textbox WITH top=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt15 as textbox WITH top=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt16 as textbox WITH top=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt17 as textbox WITH top=200,controlsource='m.nCo2'
*!*		ADD OBJECT txt18 as textbox WITH top=200,controlsource='m.nCo2'
	PROCEDURE cmd1.click
		LOCAL lni
		nCo=0
		nCo3=0
		FOR lni=1 TO 100 && each cls trigger a paint
			ThisForm.Cls
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' CLS triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd2.click
		LOCAL lni
		nCo=0
		nCo3=0
		FOR lni=1 TO 100000
			ThisForm.refresh
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE cmd3.click
		LOCAL lni
		nCo=0
		nCo2=0
		nCo3=0
		FOR lni=1 TO 100000
			nCo2=m.nCo2+1
			ThisForm.refresh
		NEXT
		MESSAGEBOX(TRANSFORM(m.nCo3)+' Refresh triggerd PAINT '+TRANSFORM(m.nCo)+' times',0,'Cls')
	ENDPROC

	PROCEDURE paint
		nCo=m.nCo+1
	ENDPROC
	
	PROCEDURE refresh
		nCo3=m.nCo3+1
	ENDPROC

	PROCEDURE cls
		nCo3=m.nCo3+1
	ENDPROC
ENDDEFINE

Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
Interesting. But one thing I can't reproduce: The difference in changing the shape width in a separate method or not. It won't matter much anymore.

From the back of my mind I remember an API function for invalidating a rectangle. That's what also happens, if a part of a form is hidden behind some other form and becomes visible again. There is an API call InvalidateRect, but even more direct RedrawWindow:

#Define RDW_UPDATENOW 0x0100
Declare INTEGER RedrawWindow in User32.dll INTEGER hWnd, INTEGER lprcUpdate, INTEGER hrgnUpdate, INTEGER flags
RedrawWindow(THISFORM.HWnd, 0, 0, RDW_UPDATENOW)

That'll force a redraw, it does invalidate the full form and causes a WM_PAINT message to the window.

Using lprcUpdate as a pointer to a RECT structure, you could also only update a portion of the form. InvalidateRect is another function you could use. But all that doesn't resolve the mystery of doing refresh in a separate method.

Bye, Olaf.
 
It strikes me that this is one of those times when DOEVENTS might be relevant.

Tamar
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top