Page 1 of 2
How to reduce flicker without adding a delay?
Posted: Sun Aug 16, 2009 3:00 am
by myutopia
Hi all
Does anyone know how to reduce the flicker on the update of the TextGadget without using delay(1)?
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Procedure ChangeText(textgadget)
For x = 0 To 999999
SetGadgetText(textgadget,Str(x))
;Delay(1) ;This reduces the flicker when uncommented. Is there any other way besides this?
Next
EndProcedure
If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
TextGadget(#Text,5,5,90,90,"Hello")
EndIf
CreateThread(@ChangeText(),#Text)
Repeat
ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
Thanks!
Posted: Sun Aug 16, 2009 4:35 am
by netmaestro
I guess you're concerned about speed? The delay slows it down a lot. If it were me and I wanted it to go like a bat out of hell with no flicker I'd switch technologies away from the textgadget:
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Global threadrunning
Procedure ChangeText(textgadget)
threadrunning = #True
x=0
While threadrunning
x+1
ClearScreen(GetSysColor_(#COLOR_BTNFACE))
StartDrawing(ScreenOutput())
DrawingFont(GetStockObject_(#DEFAULT_GUI_FONT))
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(0,0,Str(x),#Black)
StopDrawing()
FlipBuffers(0)
Wend
EndProcedure
If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
InitSprite()
OpenWindowedScreen(WindowID(0),5,5,90,20,0,0,0)
EndIf
tid = CreateThread(@ChangeText(),#Text)
Repeat
ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
threadrunning=#False
WaitThread(tid)
Tested with DX9 subsystem, really fast with no flicker here.
Posted: Sun Aug 16, 2009 4:49 am
by myutopia
Yes, speed was my main concern. Didn't find it logical to purposely slow down the procedure just to make something look good.
Thanks, netmaestro! I'll try this one out

Posted: Sun Aug 16, 2009 6:13 am
by Ollivier
NetMaestro wrote a good method if there is only one rectangular area in which the text is displayed.
In his code, I discover too these two API functions :
- GetStockObject_(#DEFAULT_GUI_FONT)
- GetSysColor_(#COLOR_BTNFACE)
(I use them in the following code)
But if you want to display two numbers (or changing text) and if there is a gadget between (for example, a button), I think you should use images instead of TextGadget(). This code loads characters table and display them via the images. For this test, you don't need DirectX. There is perhaps an other solution, but I don't know it.
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I
Procedure LoadCharMap()
Protected I.I ; Iteration
Protected A.S ; Ascii char
Protected W.I ; char Width
Protected H.I ; char Height
Protected Font.I
Font = GetStockObject_(#DEFAULT_GUI_FONT)
CreateImage(256, 16, 16)
For I = 32 To 255
A = Chr(I)
StartDrawing(ImageOutput(256) )
DrawingFont(Font)
W = TextWidth(A)
H = TextHeight(A)
StopDrawing()
CreateImage(I, W, H)
StartDrawing(ImageOutput(I) )
Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
DrawingFont(Font)
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(0, 0, A, #Black)
StopDrawing()
Next I
EndProcedure
Procedure SetDisplayStart(x.I, y.I)
xStart = x
yStart = y
EndProcedure
Procedure DisplayHome()
xChar = xStart
yChar = yStart
EndProcedure
Procedure DisplayCarriageReturn()
xChar = xStart
EndProcedure
Procedure DisplayLineFeed()
yChar + ImageHeight(32)
EndProcedure
Procedure DisplayString(StringContent.S)
Protected I.I
Protected A.I
For I = 1 To Len(StringContent)
A = Asc(Mid(StringContent, I, 1) )
If A > 31
DrawImage(ImageID(A), xChar, yChar)
xChar + ImageWidth(A)
Else
Select A
Case 13
DisplayCarriageReturn()
Case 10
DisplayLineFeed()
EndSelect
EndIf
Next I
EndProcedure
Procedure DisplayText(X.I, Y.I, Text.S)
SetDisplayStart(X, Y)
DisplayHome()
DisplayCarriageReturn()
DisplayString(Text)
EndProcedure
If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
LoadCharMap()
ButtonGadget(0, 200, 5, 200, 25, "MyButton")
Repeat
Delay(1)
Event = WindowEvent()
N + 1
StartDrawing(WindowOutput(#Window) )
DisplayText(5, 5, Str(N) )
DisplayText(550, 5, Str(N) )
DisplayText(5, 30, "This is a long text to test if it flickers" + Chr(13) + Chr(10) + "And again a line to see if really there is no problem about that.")
StopDrawing()
Until Event = #PB_Event_CloseWindow
EndIf
Posted: Sun Aug 16, 2009 6:35 am
by Ollivier
:roll:
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Procedure ChangeText(textgadget)
For x = 0 To 9999999
NoDelay + 1
If NoDelay > 10000
SetGadgetText(textgadget,Str(x))
Delay(1)
NoDelay = 1
EndIf
Next
SetGadgetText(textgadget,Str(x))
EndProcedure
If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
TextGadget(#Text,5,5,90,90,"Hello")
EndIf
CreateThread(@ChangeText(),#Text)
Repeat
ev = WindowEvent()
Delay(1)
Until ev = #PB_Event_CloseWindow
Posted: Sun Aug 16, 2009 7:12 am
by myutopia
Thanks, Ollivier

I will try these out as well.
Posted: Sun Aug 16, 2009 4:44 pm
by Ollivier
@MyUtopia
Hi. You should publish the interesting question you asked me because I think NetMaestro has more informations than me about API functions. I have solutions but they are too complex. I suppose there is a function adapted to update simply and correctly the window when you want to clear the characters drawn on the window but I don't know it. I am sorry...
Edit: I would want you to observe the second code I published. Something says me it's more adapted to your first question. If you need a very fast counter, don't forget your eyes only see between 20 and 30 frames per seconds. That's the reason I modify the procedure inside the thread to control when you need to see the progress of the counter (i.e. every 1000 iterations). I think removing the Delay() function is not a good solution but using it every iteration is a wrong solution too. There is a "just middle"...
Ollivier
Posted: Sun Aug 16, 2009 5:58 pm
by myutopia
Erm... okay...
I opted to use this code because I tested its speed against netmaestro's version, and this one was faster on my computer. Netmaestro's code was "neater" however, in the sense that it doesn't have to keep redrawing to keep the drawn items on the screen if other window overlaps, or when the window is moved "off screen". This once does.
But for my purpose, I simply need it to show the progress flicker free, and once it's done, disappear.
This is how I'm doing it now, but I have a funny feeling there's a better way to accomplish this. Maybe someone can help?
Code: Select all
Enumeration
#Window
#Text
#StatusBar
EndEnumeration
Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I
Procedure LoadCharMap()
Protected I.I ; Iteration
Protected A.S ; Ascii char
Protected W.I ; char Width
Protected H.I ; char Height
Protected Font.I
Font = FontID(LoadFont(#PB_Any, "Verdana", 10))
CreateImage(256, 16, 16)
For I = 32 To 255
A = Chr(I)
StartDrawing(ImageOutput(256) )
DrawingFont(Font)
W = TextWidth(A)
H = TextHeight(A)
StopDrawing()
CreateImage(I, W, H)
StartDrawing(ImageOutput(I) )
Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
DrawingFont(Font)
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(0, 0, A, RGB(255,50,50))
StopDrawing()
Next I
EndProcedure
Procedure SetDisplayStart(x.I, y.I)
xStart = x
yStart = y
EndProcedure
Procedure DisplayHome()
xChar = xStart
yChar = yStart
EndProcedure
Procedure DisplayCarriageReturn()
xChar = xStart
EndProcedure
Procedure DisplayLineFeed()
yChar + ImageHeight(32)
EndProcedure
Procedure DisplayString(StringContent.S)
Protected I.I
Protected A.I
For I = 1 To Len(StringContent)
A = Asc(Mid(StringContent, I, 1) )
If A > 31
DrawImage(ImageID(A), xChar, yChar)
xChar + ImageWidth(A)
Else
Select A
Case 13
DisplayCarriageReturn()
Case 10
DisplayLineFeed()
EndSelect
EndIf
Next I
EndProcedure
Procedure DisplayText(X.I, Y.I, Text.S)
SetDisplayStart(X, Y)
DisplayHome()
DisplayCarriageReturn()
DisplayString(Text)
EndProcedure
Procedure DrawThisBaby(void)
#Maxx = 999
For x = 1 To #Maxx
N + 1
StartDrawing(WindowOutput(#Window) )
DisplayText(5, 5, Str(N) )
DisplayText(500, 5, Str(#Maxx-N) )
DisplayText(5, 30, "This is a long text to test if it flickers")
DisplayText(5, 80, Str(N) )
StopDrawing()
Next
;*******************************************************
; This is how I clear it now. Is there a "better" way?
;*******************************************************
; StartDrawing(WindowOutput(#Window) )
; DisplayText(5, 5, " " )
; DisplayText(500, 5, " " )
; DisplayText(5, 30, " ")
; DisplayText(5, 80, " " )
; StopDrawing()
EndProcedure
If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
CreateStatusBar(#StatusBar, WindowID(#Window))
LoadCharMap()
ButtonGadget(0, 200, 5, 200, 25, "MyButton")
CreateThread(@DrawThisBaby(),0)
Repeat
Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
EndIf
Thanks y'all!

Posted: Sun Aug 16, 2009 6:01 pm
by myutopia
@Ollivier,
Yes I saw that one too. Great idea

I'm sure it'll come in handy for future projects.
Posted: Sun Aug 16, 2009 7:43 pm
by Ollivier
Okay, if you prefer this...

... I found a solution.
Code: Select all
Enumeration
#Window
#Text
#StatusBar
EndEnumeration
Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I
Procedure LoadCharMap()
Protected I.I ; Iteration
Protected A.S ; Ascii char
Protected W.I ; char Width
Protected H.I ; char Height
Protected Font.I
Font = FontID(LoadFont(#PB_Any, "Verdana", 10))
CreateImage(256, 16, 16)
For I = 32 To 255
A = Chr(I)
StartDrawing(ImageOutput(256) )
DrawingFont(Font)
W = TextWidth(A)
H = TextHeight(A)
StopDrawing()
CreateImage(I, W, H)
StartDrawing(ImageOutput(I) )
Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
DrawingFont(Font)
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(0, 0, A, RGB(255,50,50))
StopDrawing()
Next I
EndProcedure
Procedure SetDisplayStart(x.I, y.I)
xStart = x
yStart = y
EndProcedure
Procedure DisplayHome()
xChar = xStart
yChar = yStart
EndProcedure
Procedure DisplayCarriageReturn()
xChar = xStart
EndProcedure
Procedure DisplayLineFeed()
yChar + ImageHeight(32)
EndProcedure
Procedure DisplayString(StringContent.S)
Protected I.I
Protected A.I
For I = 1 To Len(StringContent)
A = Asc(Mid(StringContent, I, 1) )
If A > 31
DrawImage(ImageID(A), xChar, yChar)
xChar + ImageWidth(A)
Else
Select A
Case 13
DisplayCarriageReturn()
Case 10
DisplayLineFeed()
EndSelect
EndIf
Next I
EndProcedure
Procedure DisplayText(X.I, Y.I, Text.S)
SetDisplayStart(X, Y)
DisplayHome()
DisplayCarriageReturn()
DisplayString(Text)
EndProcedure
Procedure DrawThisBaby(void)
#Maxx = 999
For x = 1 To #Maxx
N + 1
StartDrawing(WindowOutput(#Window) )
DisplayText(5, 5, Str(N) )
DisplayText(500, 5, Str(#Maxx-N) )
DisplayText(5, 30, "This is a long text to test if it flickers")
DisplayText(5, 80, Str(N) )
StopDrawing()
Next
;*******************************************************
; This is how I clear it now. Is there a "better" way?
;*******************************************************
; StartDrawing(WindowOutput(#Window) )
; DisplayText(5, 5, " " )
; DisplayText(500, 5, " " )
; DisplayText(5, 30, " ")
; DisplayText(5, 80, " " )
; StopDrawing()
HideWindow(#window, 1)
HideWindow(#window, 0)
EndProcedure
If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
CreateStatusBar(#StatusBar, WindowID(#Window))
LoadCharMap()
ButtonGadget(0, 200, 5, 200, 25, "MyButton")
CreateThread(@DrawThisBaby(),0)
Repeat
Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
EndIf
Posted: Mon Aug 17, 2009 5:57 am
by Rescator
There is no point updating the numbers faster than the frame or refresh rate.
What many apps do is display intervals, i.e. just the tens, or the hundreds etc.
You can see this in Windows GUI as well, holding in the button on a cycle gadget, the longer you hold the more faster it seems to skip through numbers.
Remember that display all 10000 numbers vs every 100th number of those 10000 slows down the program as well.
This should give you the visual you need, but use hardly any CPU at all.
I have no idea what else your doing in that loop though, but... If's it's to run on a single core CPU you might want to move the Delay(0) right above the Next instead to make sure OS remains responsive.
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Procedure ChangeText(textgadget)
c=0
For x = 0 To 999999
If c=123
c=0
SetGadgetText(textgadget,Str(x))
Delay(0)
Else
Delay(1)
EndIf
c+1
Next
EndProcedure
If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
TextGadget(#Text,5,5,90,90,"Hello")
EndIf
CreateThread(@ChangeText(),#Text)
Repeat
ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
Oh in case you are wondering why I chose 123 for the c trigger, it's that it looks more random, if it triggered on 100 it would display 00 at the nd all the time
You might also find this interesting
http://www.purebasic.fr/english/viewtopic.php?t=3727
Posted: Mon Aug 17, 2009 6:37 am
by myutopia
Thanks for the suggestions y'all. It has helped, plus I learned some new tricks.

Posted: Mon Aug 17, 2009 10:18 am
by einander
You can try this trick from Sparkie (Windows only)
And it works without the delay.
Code: Select all
Enumeration
#Window
#Text
EndEnumeration
Macro NoFlickWin(Win) ;- NoFlickWin(win) - this one is from Sparkie *************
SetWindowLongPtr_(WindowID(Win), -20, GetWindowLongPtr_(WindowID(Win), -20) | $2000000)
EndMacro
Procedure ChangeText(textgadget)
For X = 0 To 999999
SetGadgetText(textgadget,Str(X))
;Delay(1) ;This reduces the flicker when uncommented. Is there any other way besides this?
Next
EndProcedure
If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
NoFlickWin(#Window)
TextGadget(#Text,5,5,90,90,"Hello")
EndIf
CreateThread(@ChangeText(),#Text)
Repeat
Ev = WaitWindowEvent()
Until Ev = #PB_Event_CloseWindow
Cheers
Posted: Tue Aug 18, 2009 12:45 am
by Rescator
@einander
That flickers here on Vista.
@myutopia
This may be an odd question to you but.
What is the point in something shown so fast it can't be read?
that kinda defeat the purpose of showing it in the first place.
If it's not readable there is no point showing it as it has no meaning

Posted: Tue Aug 18, 2009 2:15 am
by Sparkie
Macro NoFlickWin(Win) ;- NoFlickWin(win) - this one is from Sparkie *************
SetWindowLongPtr_(WindowID(Win), -20, GetWindowLongPtr_(WindowID(Win), -20) | $2000000)
EndMacro
For the life of me I cannot remember coming up with that. Must have been somebody else
