A demo for the "Exporting Families" use case.
Note that this demo is incomplete, fix any bug yourself.
public class MyForm : Form
{
private ExportFamiliesEventHandler ExportFamiliesEventHandler { get; }
private ExternalEvent ExportFamiliesExternalEvent { get; }
public MyForm(ExportFamiliesEventHandler exportFamiliesEventHandler)
{
ExportFamiliesEventHandler = exportFamiliesExternalEvent;
ExportFamiliesExternalEvent = ExternalEvent.Create(ExportFamiliesEventHandler);
InitializeComponent();
}
public void Button_Export_Click(object sender, EventArgs e)
{
ExportFamiliesEventHandler.ProgressChanged += OnProgressChanged;
ExportFamiliesEventHandler.Finished += OnFinished;
ExportFamiliesEventHandler.Cancelled += OnCancelled;
ExportFamiliesExternalEvent.Raise();
}
public void Button_Cancel_Click(object sender, EventArgs e)
{
ExportFamiliesEventHandler.Cancel();
}
private void OnProgressChanged(object sender, int progressPercentage)
{
this.Invoke(() => {
ProgressBar.Value = progressPercentage;
})
}
private void OnFinished(object sender, EventArgs e)
{
ExportFamiliesEventHandler.ProgressChanged -= OnProgressChanged;
ExportFamiliesEventHandler.Finished -= OnFinished;
ExportFamiliesEventHandler.Cancelled -= OnCancelled;
MessageBox.Show("Finished");
}
private void OnCancelled(object sender, EventArgs e)
{
ExportFamiliesEventHandler.ProgressChanged -= OnProgressChanged;
ExportFamiliesEventHandler.Finished -= OnFinished;
ExportFamiliesEventHandler.Cancelled -= OnCancelled;
MessageBox.Show("Cancelled");
}
private void OnError(object sender, ErrorEventArgs e)
{
ExportFamiliesEventHandler.ProgressChanged -= OnProgressChanged;
ExportFamiliesEventHandler.Finished -= OnFinished;
ExportFamiliesEventHandler.Cancelled -= OnCancelled;
MessageBox.Show(e.Exception.Message);
}
}
public class ExportFamiliesEventHandler : IExternalEventHandler
{
public int ProgressPercentage { get; private set; }
private ChainedJobExecuter<Family, boolean> Executer { get; set; }
public event EventHandler<int> ProgressChanged;
public event EventHandler Finished;
public event EventHandler Cancelled;
public event EventHandler<ErrorEventArgs> Error;
public string GetName()
{
return "Export Families";
}
public void Execute(UIApplication app)
{
var families = new FilteredElementCollector(app.ActiveUIDocument.Document)
.OfClass(typeof(Family))
.Cast<Family>()
.Where(f => f.IsEditable)
.ToList();
var jobs = families.Select(f => new ExportFamilyJob(f, @"C:\Temp")).Cast<IJob<Family, boolean>>().ToArray();
ChainedJobExecuter = new ChainedJobExecuter<Family, boolean>(jobs);
ChainedJobExecuter.Finished += OnFinished;
ChainedJobExecuter.ProgressChanged += OnProgressChanged;
ChainedJobExecuter.Cancelled += OnCancelled;
ChainedJobExecuter.Error += OnError;
ChainedJobExecuter.Execute();
return Result.Succeeded;
}
public void Cancel()
{
ChainedJobExecuter.Cancel();
}
private void OnFinished(object sender, EventArgs e)
{
Finished?.Invoke(this, null);
Unsubscribe();
}
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressPercentage = Math.Round(e.Progress * 1.0 / e.Total);
ProgressChanged?.Invoke(this, ProgressPercentage);
}
private void OnCancelled(object sender, EventArgs e)
{
Cancelled?.Invoke(this, null);
Unsubscribe();
}
private void OnError(object sender, ErrorEventArgs e)
{
Error?.Invoke(this, e);
Unsubscribe();
}
private void Unsubscribe()
{
ChainedJobExecuter.Finished -= OnFinished;
ChainedJobExecuter.ProgressChanged -= OnProgressChanged;
ChainedJobExecuter.Cancelled -= OnCancelled;
ChainedJobExecuter.Error -= OnError;
}
}
public class ChainedJobExecuter<TParameter, TResult>
{
private bool IsCancellationRequested { get; set; } = false;
private Queue<IJob<TParameter, TResult>> Jobs { get; };
private int Total { get; };
private List<TResult> Results { get; };
public event EventHandler Finished;
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
public event EventHandler Cancelled;
public event EventHandler<ErrorEventArgs> Error;
public JobExecuter(IJob<TParameter, TResult>[] job)
{
Jobs = new Queue<IJob<TParameter, TResult>>(jobs);
Total = jobs.Length;
}
public void Execute()
{
if (IsCancellationRequested)
{
Cancelled?.Invoke(this, null);
return;
}
var job = Jobs.Dequeue();
if (job == null)
{
Finished?.Invoke(this, null);
return;
}
var externalEvent = ExternalEvent.Create(job);
externalEvent.Raise();
job.Finished += OnJobFinished;
job.Error += OnJobError;
}
private void OnJobFinished(object sender, TResult result)
{
var job = sender as IJob<TParameter, TResult>;
job.Finished -= OnJobFinished;
job.Error -= OnJobError;
Results.Add(result);
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(Results.Count, Total));
Execute();
}
private void OnJobError(object sender, ErrorEventArgs e)
{
var job = sender as IJob<TParameter, TResult>;
job.Error -= OnJobError;
job.Finished -= OnJobFinished;
Error?.Invoke(this, e);
}
public Task Cancel()
{
IsCancellationRequested = true;
}
public TResult[] GetResults()
{
return Results.ToArray();
}
}
public class ExportFamilyJob : GenericJob<Family, boolean>
{
private string Directory { get; }
public ExportFamilyJob(Family family, string directory) : base(family)
{
Directory = directory;
}
public override string GetName()
{
return $"Export Family {Parameter.Name}";
}
protected override boolean InternalExecute(UIApplication app)
{
var family = Parameter;
var doc = app.ActiveUIDocument.Document;
var path = Path.Combine(Directory, $"{family.Name}.rfa");
var options = new SaveAsOptions
{
OverwriteExistingFile = true,
Compact = true,
MaximumBackups = 1
};
doc.SaveAs(path, options);
return true;
}
}
public interface IJob<TParameter, TResult> : IExternalEventHandler
{
TParameter Parameter { get; }
event EventHandler<TResult> Finished;
event EventHandler<ErrorEventArgs> Error;
}
public abstract class GenericJob<TParameter, TResult> : IJob<TParameter, TResult>
{
public TParameter Parameter { get; }
public event EventHandler<TResult> Finished;
public event EventHandler<ErrorEventArgs> Error;
public GenericJob(TParameter parameter)
{
Parameter = parameter;
}
public abstract string GetName();
public TResult Execute(UIApplication app)
{
try
{
var result = InternalExecute(app);
OnFinished();
}
catch (Exception e)
{
Error?.Invoke(this, new ErrorEventArgs(e));
}
}
protected abstract TResult InternalExecute(UIApplication app);
}