Timer is not firing

Hi there.

Need some wisdom that I lack.

I want a timer to fire to allow the slow adding of files from a LARGE folder to a listbox. However, when I run the code, I never get my timer firing. Here is the code that calls the timer:

if theFolderIDropped.Count >=500 then

//we will have a routine here to go through
//each run

// for testing only
System.DebugLog “More than 500 Pictures”

// run the timer so we can SLOWLY add files
timerLargeNumberOfFiles.Enabled = TRUE

else

// for testing only
System.DebugLog “LESS than 500 pictures”

Var fileInTheFolder As FolderItem = theFolderIDropped.ChildAt(theNumberOfFiles)

If fileInTheFolder <> Nil Then

windowMyDuplicatePictureFinder.listboxFilesInFolder.AddRow ""
windowMyDuplicatePictureFinder.listboxFilesInFolder.CellTextAt(windowMyDuplicatePictureFinder.listboxFilesInFolder.LastAddedRowIndex,0) = fileInTheFolder.Name

windowMyDuplicatePictureFinder.progressBarFileLoading.Value = windowMyDuplicatePictureFinder.progressBarFileLoading.Value + 1

else

End If

end if

And this is my timer. It is set to fire every 10 seconds, Multiple times. It is disabled when the program starts, but when you find you have a whack of files, it is enabled. Here is that code, under the “Action” event… And yes I know all it does now is give a log. :smiley:

// for testing only, just add a row.
System.DebugLog “Timer fired”

What am I doing… or NOT doing?

Regards

// run the timer so we can SLOWLY add files
timerLargeNumberOfFiles.Enabled = TRUE

This doesn’t completely start a timer. You need to change the RunMode. For predictability, I also always Reset a Timer when I do so.

Try this

// run the timer so we can SLOWLY add files
timerLargeNumberOfFiles.Enabled = TRUE
timerLargeNumberOfFiles.RunMode = Timer.RunModes.Multiple
timerLargeNumberOfFiles.Reset
1 Like

Thank you for that information, Tim.

I will try and advise.

Thank you for your time!

Regards

Hi Mr. Parnell.

Nope. That doesn’t work.

I go through my loop, and see that it does the

timerLargeNumberOfFiles.Enabled = TRUE
timerLargeNumberOfFiles.RunMode = Timer.RunModes.Multiple
timerLargeNumberOfFiles.Reset

(I have a breakpoint so I am watching)

But I NEVER see the timer fire. I have reduced the time to 1 second (1000 ms) and I never see the System.DebugLog I have in there fire… even one time.

And I am sorry for acting stupid, but when I go Enable, the timer should act every time period, right? Even if I hit the timer enable event more than one time?

Regards

Update: I went for giggles and enabled when the program started - just for testing - the timer when the program starts.

I get the system.debug log.

Now I am really confused.

Regards v2.0

The RunMode needs to be something other than Off. In the past the Enabled property did nothing at all, at some point it became required that Timers must be Enabled to fire. Setting enabled, then the run mode, then resetting should cause the Timer to fire. Setting just enabled does not guarantee that.

If you post a sample project demonstrating the setup you have that’s not working, we may be able to find the problem.

Update: Struck-out the historically inaccurate statement, I checked the 2019r1.1 docs.

Thanks Mr. Parnell.

I will make up a little project and post it, so you can see what I am doing.

As a note. When I created my timer, it was already set to “Multiple”

And it might take some time to get the project to you. Please be patient. Real life still goes on.

You are not being ignored.

Regards

What are .enabled and .reset supposed to do?

Hi Ms Willius.

For me, the enabled is for a situation where a user has a large number of files to fill the listbox. Since it takes a lot of processor time (in my case think 15000 plus items) the user might think it is not responding. So I want to pass the listbox filling to a timer that works in batches.

As for reset, it resets the state, from what I understand.

Regards

Enabled controls whether or not the Run event can occur. I don’t recall it resetting the timer, but according the the new documentation, it does.

Reset will set the currently passed time back to zero, which I do for consistency and predictability. I just like my code to be self-documenting. Changing the RunMode resets the timer, but the extra line in my code saves me from having to open the documentation, which is now a whole online thing.

Hi Guys.

I think I found my problem. But testing will tell.

When I drop the folder, since it is so big, the system is being “starved” and never gets a chance to actually do the system.debuglog printing.

Let me try some stuff and I will, if / when I get an answer, share it.

Stay tuned.

This sounds like something that might be better suited to a Thread.

4 Likes

It’s important to understand the fundamental design of the Xojo event model - while processing one event in the main thread, other events are not processed, and will be held until the event in the main thread has finished.

So something like this will never work:

Window1.Pushbutton1.Pressed
  Timer1.runMode = Timer.Runmodes.Single
  for i = 1 to 100000
     ... do something ...
  next

The timer will never fire during this entire event handler.

The workaround as @Kem_Tekinay mentions is to use a Thread.

(Edit: typos)

1 Like

Here’s a little demo project, which consists of:

  • Thread with a Run event (which does nothing interesting)
  • ProgressBar which is updated from within the thread
  • Run and Cancel buttons which start and stop the thread.

Using this technique, you would put your code in side Thread1.Run - no timers are actually needed since the code uses the Thread.UserInterfaceUpdate method event which always fires on the main thread.

progressbar_thread.v1.xojo_binary_project.zip (6.6 KB)

2 Likes

Thanks Michael.

I will take a look at this. I was thinking of using a thread, but since I’m not sure what I am doing when I use them, even with the youtube and examples, I have been avoiding it.

Guess I’m going to have to dive in.

Regards

Ok. I have it fixed.

I think. :smiley:

Using this code:

// Clear UI

listboxFilesInFolder.RemoveAllRows

progressBarFileLoading.Value = 0



// Store folder

droppedFolder = obj.FolderItem



// Initialize counters

currentIndex = 0

totalFiles = droppedFolder.Count



// Setup progress bar

progressBarFileLoading.MaximumValue = totalFiles



// Debug

System.DebugLog("Folder: " + droppedFolder.Name)

System.DebugLog("Files: " + totalFiles.ToString)



// Decide processing method

If totalFiles < 500 Then

  

  // Small folder: process immediately

  For i As Integer = 0 To totalFiles - 1

    AddFileToList(droppedFolder.ChildAt(i))

  Next

  

Else

  

  // Large folder: use timer

  timerLargeNumberOfFiles.Enabled = True

  

End If

I give my program time to take a breath.

So far testing seems… promising.

Thanks to everyone for their help. I hope my code assists someone else.

Regards

I think you’ve overcomplicated your code. A solution that is good for a large number of files should be a good solution for a small number of files too. You want to reduce the number of special cases, and you don’t have enough information to decide whether or not a particular cutoff is good for the user. Computer A might process faster than Computer B.

Instead, I would simply drop the files into an array, execute the timer often - I’d start with 250ms - and move a chunk from the array into the listbox. Size your chunk depending on the amount of work AddFileToList needs to do. If it’s nothing more than adding to a listbox, you should have zero trouble working on 100 files per chunk. Only thing I can’t really tell is if you want this to be recursive or not, so I’ll assume not.

Code would look something like this:

Event DropObject(Obj As DragItem, Action As DragItem.Types)
  // Your DragItem can have multiple parts, such as multiple dragged files.
  Do
    If Obj.FolderItemAvailable = False Then
      Continue
    End If

    Self.ProcessDroppedFile(Obj.FolderItem)
  Loop Until Obj.NextItem = False
End Event

Sub ProcessDroppedFile(File As FolderItem)
  If Self.ListUpdaterTimer.RunMode <> Timer.RunModes.Multiple Then
    Self.ListUpdaterTimer.RunMode = Timer.RunModes.Multiple
  End If

  If File.IsFolder = False Then
    Self.mPendingFiles.Add(File)
    Return
  End If

  For Each Child As FolderItem In File.Children
    Self.mPendingFiles.Add(Child)
  Next
End Sub

Event ListUpdaterTimer_Action()
  If Self.mPendingFiles.Count = 0 Then
    Me.RunMode = Timer.RunModes.Off
    Return
  End If

  Var NumListed As Integer
  While Self.mPendingFiles.Count > 0 And NumListed <= 100
    Self.AddFileToList(Self.mPendingFiles(0))
    Self.mPendingFiles.RemoveAt(0)
    NumListed = NumListed + 1
  Wend
End Event

I wrote this all in-forum, so there may be minor bugs, but the concept is there. I’d prefer to use a thread if AddFileToList is doing more than just pushing the file into a ListBox, but it’s a bit more complex and there’s too many things I don’t know about the code. But the concept would be similar: the thread works on the pending files and pushes updates to the UI with UserInterfaceUpdate.

2 Likes

thank you ever so much, Thom.

I will save this, read it, and figure it out… or do my darndest.

Once again, to you and everyone on the forum, thanks.

Regards

A personal design choice I make is to stash the FolderItems from the DropObject in a property then belay processing them with a Timer. The Xojo app and Finder both lock up until the end of the DropObject event, so I want to gather the references only and exit the event as soon as possible.

FolderItem actions are main-thread blocking, so if you process in a Thread I suggest you add your own additional yielding to make the UI “less stiff”. You can see what I mean in my app Plugins Pro if you activate a large number of plugins at once. It’s not as fluid as one would hope because of the main-thread blocking nature.

We do have new tools since I built Plugins Pro. You could use a Worker or Preemptive Thread and either option should really free up the UI and make it fluid again. I do think it would be easier to start with a basic Cooperative Thread and transition once you are ready. Both technologies are newer than Plugins Pro so I don’t have much advice on them for you. I do recall Kem being very excited about Preemptive Threads, so perhaps he can advise you there :slight_smile:

1 Like

I’m going to put my 2 cents in here… don’t just loop over the contents of the dropped folder. Use an iterator. They are so much faster that this stuff you’re doing may be overkill.

For each file as folderitem in droppedfolder.children
… process file…
Next
3 Likes

The way I ended up doing it in my app (which I’ve tested up to drops of 10000 files) is this:

  • Start processing the files in a Thread (silently with no UI)
  • if the elapsed time is greater than about 1-2 seconds, and there are a non-trivial number of files left to go, then show the UI - which includes a progress bar and a Cancel button

Depending on the circumstances, the UI may be a Modal Dialog, or just a few controls on the window - the difference is whether you want the rest of the app’s UI to be blocked during this operation vs. not.

Modal dialogs are interesting, because they are an exception to the rule: while processing one event in the main thread, other events are not processed.