Can anyone share a working implementation of WPF progress bar with abort button?

Can anyone share a working implementation of WPF progress bar with abort button?

adam.krug
Advocate Advocate
7,899 Views
25 Replies
Message 1 of 26

Can anyone share a working implementation of WPF progress bar with abort button?

adam.krug
Advocate
Advocate

I already had a few takes on that topic but none worked ideally.

 

My current problem is that while Revit is processing through my command I cannot click on the "abort" button because the progress bar window is not responding. It updates well but won't react on mouse clicks most of the time...

 

Maybe someone has already figured out such progress bar window with WPF and could share?

0 Likes
7,900 Views
25 Replies
Replies (25)
Message 21 of 26

jeremy_tammik
Alumni
Alumni

By the way, for the sake of completeness: The Revit API and the TaskDialog class do provide for a built-in progress bar, however indeterminate, lacking start and stop: 

  

    

2.5.1. Add Animated Progress Bar to a TaskDialog

  

The new property:

  

  • TaskDialog.EnableMarqueeDialogBar

 

allows the TaskDialog to include a progress bar that has an indeterminate start and stop. The animation continues until the TaskDialog is closed.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 22 of 26

jeremy_tammik
Alumni
Alumni

Also note the possible use of  ProgressChangedEventArgs  for some cases:

  

https://thebuildingcoder.typepad.com/blog/2022/06/parameters-snippets-batch-mode-and-self-signing.ht...

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 23 of 26

Songohu_85
Enthusiast
Enthusiast

Hey, sorry for late reply. I've been very busy.

It's a listing tool although it's quite easy to also use it to modify document by correctly using transactions. You could just simply start and commit transaction on top of Execute method in IExternalEventHandler class or if you pass document as argument to your delete method then why you just not start and commit transaction outside the loop. I improved the example and added Delete walls option with cancelation and TaskDialog to choose if you want to roll back changes.

Current Execute method in IExternalEventHandler class:

 

public void Execute(UIApplication app)
{
	try 
	{
		DataViewModel viewModel = ThisDocument.Instance.ViewModel;
		if(viewModel.Action == ActionType.GetWalls)
		{
			viewModel.DataModel.GetWallModels(app.ActiveUIDocument.Document);
		}
		if(viewModel.Action == ActionType.DeleteWalls)
		{
			viewModel.DataModel.DeleteWallModels(app.ActiveUIDocument.Document);
		}
	} 
	catch (Exception ex)
	{
		
		TaskDialog.Show("Error", ex.ToString());
	}

}

 

 

DeleteWalls method in DataModel class:

 

public void DeleteWallModels(Document doc)
{
	List<Wall> walls = new FilteredElementCollector(doc).OfClass(typeof(Wall)).Select(e => e as Wall).ToList();
	int counter = 0;
	int totalItems = walls.Count;
	WallModels = new List<WallModelItem>();	
	using (TransactionGroup tgroup = new TransactionGroup(doc, "Delete all walls"))
	{
		tgroup.Start();
		
		foreach (Wall w in walls) 
		{
			using (Transaction tx = new Transaction(doc, "Delete wall"))
			{					
				counter++;
				WallModelItem deletedWall = WallModelItem.Initialize(w);
				WallModels.Add(deletedWall);
				tx.Start();
				doc.Delete(w.Id);
				tx.Commit();
				
				double percentage = ((double)counter / (double)totalItems) * 100;
				Worker.ReportProgress((int)percentage, string.Format("Deleted {0} of {1}", counter, totalItems));	                
				if(IsCancelled)
				{	                	
					var result = TaskDialog.Show("Info", "Would you like to roll back all changes?", TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);
					if(result == TaskDialogResult.Yes)
					{
						Worker.ReportProgress((int)percentage, string.Format("Canceled, rolled back, deleted 0 of {0}", totalItems));
						WallModels.Clear();
						tgroup.RollBack();
					}
					else
					{
						Worker.ReportProgress((int)percentage, string.Format("Canceled, deleted {0} of {1}", counter, totalItems));
						tgroup.Assimilate();
					}
					SignalEvent.Set();
					return;
				}
			}
		}
		
		tgroup.Assimilate();
	}											
	Worker.ReportProgress(100, "Finished!");
	SignalEvent.Set();
}

 

 

Full solution you can check in attached document macro.

EDIT: Please also focus on using IExternalEventHandler correctly. Proper way is to Raise() the event so Revit will "find" related object and call Execute(UIApplication app) method. This way you can process document changes inside valid data context.

Best regards
Marek

Message 24 of 26

ricaun
Advisor
Advisor

I create the package ricaun.Revit.UI.StatusBar last year to add a progress bar similar to the default Revit progress bar.

 

In theme is automatically selected depending on the Revit version and theme your plugin is running.

ricaun_0-1705420009363.gif

Here is a video with an example: https://github.com/ricaun-io/RevitAddin.ProgressBar.Example

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 25 of 26

zefreestijl
Advocate
Advocate

Hi, thank you,

 

I tried implement the macro into my API tool and it worked as exactly what I need!

 

so what I missed was the ManualResetEvent for successfully receiving worker's current state,

then I can collect the element list or delete elements when the ExternalEvent been executed.

 

this method really simplifies the workaround.

 

externalEvent0.png

 

manualResetEvent1.png

 

 

Thanks again for your help :]

0 Likes
Message 26 of 26

zefreestijl
Advocate
Advocate

Hi, I've visited your github,

 

those sources are truly treasures,

 

I'll try to learn and implement the package when I'm available.

 

thank you for sharing your wonderful work :]

0 Likes