Asynchronous Windows Forms Updating?

Does anyone know how to asynchronously update a windows form in IronPython? I found this example Progress Bar which executes everything on its own thread and while I would be able call my own functions within the update() function, it sounds like a bad idea to wrap everything in the same class. Ideally, I would like to be able to define the range of a progress bar and increment it as I iterate a list. I am aware that there is already a library which accomplishes this, but relying on a .dll raises issues of portability.

Here is what I have so far (mainly copied from the IronPython Cookbook but modified to be compatible with Dynamo) I have also added a self.Close() call so that the window closes once the loop is exited:

import sys
sys.path.append(r'C:\Program Files (x86)\IronPython 2.7\Lib')
sys.path.append(r'C:\Program Files\IronPython 2.7\Lib')
import os

import clr
clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
from System.Drawing import Icon
from System.Windows.Forms import Application, Form, ProgressBar, Label
from System.Threading import ThreadStart, Thread

clr.AddReference('IronPython')
from IronPython.Compiler import CallTarget0

class ProgressBarDialog(Form):
	def __init__(self):
		self.Text = 'Test'
		pb = ProgressBar()
		pb.Minimum = 1
		pb.Maximum = 100
		pb.Step = 1
		pb.Value = 1
		pb.Width = 400
		self.Controls.Add(pb)
		self.prog = pb
		self.Shown += self.start

	def start(self, s, e):
		def update():
			for i in range(100):
				print i
				def step():
					self.prog.Value = i + 1
				self.Invoke(CallTarget0(step))
				Thread.Sleep(15)
			self.Close()
		t = Thread(ThreadStart(update))
		t.Start()

Application.EnableVisualStyles()
f = ProgressBarDialog()
f.ShowDialog()
1 Like

I think that the DLL is going to be the easiest way to transport and build this kind of item. Writing it in Python May remove the immediate ‘distribution’ issue, but it adds a longevity issue as every graph using that code would have to be updated manually should something break as the tech evolves. Moving around a DLL via package manager is way easier than having to track and manually update files.

Looking over the add-in, I feel like there is potential to apply this type of function to more elements as a view extension… I know there was a lot of work on tracking graph run times produced at the recent UK - might be able to mine some stuff there.

1 Like

True, definitely more scalable in that regard. I only planned to use it in a single graph, but I would probably feel the urge to integrate it into more in the future.

1 Like

Quick question on this: would you be running in a Dynamo for Revit environment or a Dynamo Sandbox or something else?

There is a progress bar in the Dynamo for Civil 3D 2020 integration which is another avenue that may be worth exploring. Pops up in the bottom of the AutoCAD window near where your command line is. I have no idea how it’s integrated or what the accuracy is, but I do take it as a good sign of user’s desire for such an indicator though.

This is specifically for Dynamo for Revit.

This is actually quite tricky to get right because any interactions with the form running on the separate thread must be done using BeginInvoke/Invoke() while any Revit API interactions must be executed on the main thread.

The issue I see with this code is that there isn’t a way to call back to the main thread to perform Revit API work while the dialog is running (as ShowDialog() is a blocking call).

The way I’ve worked around this is start the background thread, create the Form on that thread, show it on that thread using the non-blocking Show() method instead, then run a UI message pump on that thread using System.Windows.Threading.Dispatcher.Run().

Since starting the thread is non-blocking and the dialog is created and shown on that thread, you can then proceed to do Revit API work on the main thread, using BeginInvoke()/Invoke() on the Form instance to update its UI. (If you don’t use these you’ll potentially get unpredictable behaviour).

There is one remaining gotcha: if you attempt to update the UI / progress from the main thread before the Form instance is shown, you’ll likely run into errors because the Form handle hasn’t yet been initialized. The way to workaround this is to setup an EventWaitHandle on the main thread and call WaitOne() on it. Now, in your background thread, after calling Show() but before running the dispatcher message pump, use Dispatcher.CurrentDispatcher.BeginInvoke() passing an action that calls Set() on the EventWaitHandle instance. This ensures that the form is shown/initialized before proceeding.

Hope that’s not confusing, I’d show you some code but I’m paraphrasing private code I wrote for work so I can’t copy paste it here verbatim.

2 Likes

Dan, this is a very helpful response. I will look into the Form.Show() method and see if this solves my issue. I am still not very familiar with threading in general but I will look more into this. Thanks again.