B4J Question Debounce

xulihang

Well-Known Member
Licensed User
Longtime User
I need to update the display percentage of an image view, which has PDF rendered into images.

When I am zooming in, it displays the thumbnail and then renders the PDF into a high-fidelity one, which costs time. How to avoid the unnecessary rendering when zooming? I think it is called debounce.

Here is the simulated demo code:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private Button1 As B4XView
    Private Spinner1 As Spinner
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    Dim jo As JavaObject = Spinner1
    Dim e As Object = jo.CreateEventFromUI("javafx.event.EventHandler", "scroll", Null)
    jo.RunMethod("setOnScroll", Array(e))
End Sub

Sub Button1_Click
    xui.MsgboxAsync("Hello World!", "B4X")
End Sub

Private Sub Spinner1_ValueChanged (Value As Object)
    HeavyJob
End Sub

Private Sub HeavyJob
    Log("HeavyJob")
    Sleep(5000)
    Log("HeavyJob done")
End Sub

Sub Scroll_Event (MethodName As String, Args() As Object) As Object
    Dim scrollevent As JavaObject = Args(0)
    Dim DeltaY As Int=scrollevent.RunMethod("getDeltaY", Null)
    If DeltaY>0 Then
        Spinner1.Value=Spinner1.Value+5
    Else
        Spinner1.Value=Spinner1.Value-5
    End If
    Return Null
End Sub
 
Solution
The idea is to prevent HeavyJob from being restarted with every tiny zoom variation, and to only run it once the user has finished zooming.

The goal is to prevent Spinner1_ValueChanged from triggering HeavyJob with every value change, and to only launch the high-resolution rendering after a delay without any new events.

The solution would be to use a Timer as a "debounce".
Each time the value changes, a Timer is restarted. If no new changes occur for X milliseconds, the Timer triggers HeavyJob.

If the user continues to zoom, the Timer is reset, and HeavyJob is never launched unnecessarily.

Add Private DebounceTimer As Timer to Process_Globals

Example:
Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1...

zed

Well-Known Member
Licensed User
The idea is to prevent HeavyJob from being restarted with every tiny zoom variation, and to only run it once the user has finished zooming.

The goal is to prevent Spinner1_ValueChanged from triggering HeavyJob with every value change, and to only launch the high-resolution rendering after a delay without any new events.

The solution would be to use a Timer as a "debounce".
Each time the value changes, a Timer is restarted. If no new changes occur for X milliseconds, the Timer triggers HeavyJob.

If the user continues to zoom, the Timer is reset, and HeavyJob is never launched unnecessarily.

Add Private DebounceTimer As Timer to Process_Globals

Example:
Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show

    DebounceTimer.Initialize("DebounceTimer", 300) ' 300ms delay
    DebounceTimer.Enabled = False

    Dim jo As JavaObject = Spinner1
    Dim e As Object = jo.CreateEventFromUI("javafx.event.EventHandler", "scroll", Null)
    jo.RunMethod("setOnScroll", Array(e))
End Sub

Private Sub Spinner1_ValueChanged (Value As Object)
    DebounceTimer.Enabled = False '
    DebounceTimer.Enabled = True ' restarts the timer
End Sub

Sub DebounceTimer_Tick
    DebounceTimer.Enabled = False
    HeavyJob
End Sub

Code not tested
 
Upvote 1
Solution

xulihang

Well-Known Member
Licensed User
Longtime User
I tried using sleep, but it does not work well. The timer version works perfectly. I cannot come up with a better way using sleep.


Sleep:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private Button1 As B4XView
    Private Spinner1 As Spinner
    Private previousPerformedTime As Long = 0
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    Dim jo As JavaObject = Spinner1
    Dim e As Object = jo.CreateEventFromUI("javafx.event.EventHandler", "scroll", Null)
    jo.RunMethod("setOnScroll", Array(e))
End Sub

Sub Button1_Click
    xui.MsgboxAsync("Hello World!", "B4X")
End Sub

Private Sub Spinner1_ValueChanged (Value As Object)
    HeavyJob
End Sub

Private Sub HeavyJob
    Dim percent As Double = Spinner1.Value
    If DateTime.Now - previousPerformedTime < 500 Then
        Sleep(1000)
        If DateTime.Now - previousPerformedTime < 1000 Then
            Log("debounce: "&percent)
            Return
        End If
    End If
    Log("HeavyJob: "&percent)
    previousPerformedTime = DateTime.Now
    Sleep(5000)
    Log("HeavyJob done: "&percent)
End Sub


Sub Scroll_Event (MethodName As String, Args() As Object) As Object
    Dim scrollevent As JavaObject = Args(0)
    Dim DeltaY As Int=scrollevent.RunMethod("getDeltaY", Null)
    If DeltaY>0 Then
        Spinner1.Value=Spinner1.Value+5
    Else
        Spinner1.Value=Spinner1.Value-5
    End If
    Return Null
End Sub

Timer:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private Button1 As B4XView
    Private Spinner1 As Spinner
    Private debounceTimer As Timer
    Private lastValue As Object
    Private debounceDelay As Int = 500 ' 防抖延迟500ms
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
 
    ' 初始化防抖计时器
    debounceTimer.Initialize("debounceTimer", debounceDelay)
    debounceTimer.Enabled = False
 
    ' 设置Spinner滚动事件
    Dim jo As JavaObject = Spinner1
    Dim e As Object = jo.CreateEventFromUI("javafx.event.EventHandler", "scroll", Null)
    jo.RunMethod("setOnScroll", Array(e))
End Sub

Sub Button1_Click
    xui.MsgboxAsync("Hello World!", "B4X")
End Sub

Private Sub Spinner1_ValueChanged (Value As Object)
    ' 存储最新的值
    lastValue = Value
 
    ' 重置并启动防抖计时器
    debounceTimer.Enabled = False
    debounceTimer.Enabled = True
End Sub

Private Sub debounceTimer_Tick
    debounceTimer.Enabled = False
    HeavyJob(lastValue)
End Sub

Private Sub HeavyJob(Value As Object)
    Log("执行HeavyJob,值: " & Value)
    ' 这里执行耗时的操作
    Sleep(1000) ' 模拟耗时操作
    Log("HeavyJob完成,值: " & Value)
End Sub

Sub Scroll_Event (MethodName As String, Args() As Object) As Object
    Dim scrollevent As JavaObject = Args(0)
    Dim DeltaY As Int = scrollevent.RunMethod("getDeltaY", Null)
    If DeltaY > 0 Then
        Spinner1.Value = Spinner1.Value + 5
    Else
        Spinner1.Value = Spinner1.Value - 5
    End If
    Return Null
End Sub
 
Last edited:
  • Like
Reactions: zed
Upvote 0
Top