Skip to content

Commit

Permalink
Merge pull request #4 from mitevpi/development
Browse files Browse the repository at this point in the history
Multi-Threading Updates
  • Loading branch information
mitevpi authored Apr 30, 2020
2 parents 401c170 + 1106861 commit 58b0744
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 161 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Revit WPF Template

![GitHub issues](https://img.shields.io/github/issues/mitevpi/revit-wpf-template)
![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/mitevpi/revit-wpf-template)
![GitHub contributors](https://img.shields.io/github/contributors/mitevpi/revit-wpf-template)

![GitHub last commit](https://img.shields.io/github/last-commit/mitevpi/revit-wpf-template)
![GitHub Release Date](https://img.shields.io/github/release-date/mitevpi/revit-wpf-template)
![GitHub All Releases](https://img.shields.io/github/downloads/mitevpi/revit-wpf-template/total)

![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/mitevpi/revit-wpf-template)
![GitHub repo size](https://img.shields.io/github/repo-size/mitevpi/revit-wpf-template)
![GitHub](https://img.shields.io/github/license/mitevpi/revit-wpf-template)

WPF Template for Revit Add-Ins including wrapped external methods for execution in a "Valid Revit API Context"

![Window A](assets/window1.png)
Expand Down
118 changes: 57 additions & 61 deletions Revit Template/App.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows.Media.Imaging;
Expand All @@ -22,7 +23,7 @@ class App : IExternalApplication
private Ui _mMyForm;

// Separate thread to run Ui on
private Thread _UiThread;
private Thread _uiThread;

public Result OnStartup(UIControlledApplication a)
{
Expand All @@ -32,31 +33,31 @@ public Result OnStartup(UIControlledApplication a)
// Method to add Tab and Panel
RibbonPanel panel = RibbonPanel(a);
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
PushButton button =
panel.AddItem(
new PushButtonData("WPF Template", "WPF Template", thisAssemblyPath,
"RevitTemplate.EntryCommand")) as
PushButton;

// defines the tooltip displayed when the button is hovered over in Revit's ribbon
button.ToolTip = "Visual interface for debugging applications.";

// defines the icon for the button in Revit's ribbon - note the string formatting
Uri uriImage = new Uri("pack://application:,,,/RevitTemplate;component/Resources/code-small.png");
BitmapImage largeImage = new BitmapImage(uriImage);
button.LargeImage = largeImage;

PushButton button2 =
panel.AddItem(
new PushButtonData("WPF Template Multi-Thread", "WPF Template Multi-Thread", thisAssemblyPath,
"RevitTemplate.EntryCommandSeparateThread")) as
PushButton;
// BUTTON FOR THE SINGLE-THREADED WPF OPTION
if (panel.AddItem(
new PushButtonData("WPF Template", "WPF Template", thisAssemblyPath,
"RevitTemplate.EntryCommand")) is PushButton button)
{
// defines the tooltip displayed when the button is hovered over in Revit's ribbon
button.ToolTip = "Visual interface for debugging applications.";
// defines the icon for the button in Revit's ribbon - note the string formatting
Uri uriImage = new Uri("pack://application:,,,/RevitTemplate;component/Resources/code-small.png");
BitmapImage largeImage = new BitmapImage(uriImage);
button.LargeImage = largeImage;
}

// defines the tooltip displayed when the button is hovered over in Revit's ribbon
button2.ToolTip = "Visual interface for debugging applications.";
// BUTTON FOR THE MULTI-THREADED WPF OPTION
if (panel.AddItem(
new PushButtonData("WPF Template\nMulti-Thread", "WPF Template\nMulti-Thread", thisAssemblyPath,
"RevitTemplate.EntryCommandSeparateThread")) is PushButton button2)
{
button2.ToolTip = "Visual interface for debugging applications.";
Uri uriImage = new Uri("pack://application:,,,/RevitTemplate;component/Resources/code-small.png");
BitmapImage largeImage = new BitmapImage(uriImage);
button2.LargeImage = largeImage;
}

// defines the icon for the button in Revit's ribbon - note the string formatting
button2.LargeImage = largeImage;

// listeners/watchers for external events (if you choose to use them)
a.ApplicationClosing += a_ApplicationClosing; //Set Application to Idling
Expand All @@ -82,16 +83,14 @@ public Result OnShutdown(UIControlledApplication a)
public void ShowForm(UIApplication uiapp)
{
// If we do not have a dialog yet, create and show it
if (_mMyForm == null || _mMyForm != null) // || m_MyForm.IsDisposed
{
//EXTERNAL EVENTS WITH ARGUMENTS
EventHandlerWithStringArg evString = new EventHandlerWithStringArg();
EventHandlerWithWpfArg evWpf = new EventHandlerWithWpfArg();

// The dialog becomes the owner responsible for disposing the objects given to it.
_mMyForm = new Ui(uiapp, evString, evWpf);
_mMyForm.Show();
}
if (_mMyForm != null && _mMyForm == null) return;
//EXTERNAL EVENTS WITH ARGUMENTS
EventHandlerWithStringArg evStr = new EventHandlerWithStringArg();
EventHandlerWithWpfArg evWpf = new EventHandlerWithWpfArg();

// The dialog becomes the owner responsible for disposing the objects given to it.
_mMyForm = new Ui(uiapp, evStr, evWpf);
_mMyForm.Show();
}

/// <summary>
Expand All @@ -103,28 +102,26 @@ public void ShowForm(UIApplication uiapp)
public void ShowFormSeparateThread(UIApplication uiapp)
{
// If we do not have a thread started or has been terminated start a new one
if (_UiThread is null || !_UiThread.IsAlive)
if (!(_uiThread is null) && _uiThread.IsAlive) return;
//EXTERNAL EVENTS WITH ARGUMENTS
EventHandlerWithStringArg evStr = new EventHandlerWithStringArg();
EventHandlerWithWpfArg evWpf = new EventHandlerWithWpfArg();

_uiThread = new Thread(() =>
{
//EXTERNAL EVENTS WITH ARGUMENTS
EventHandlerWithStringArg evStr = new EventHandlerWithStringArg();
EventHandlerWithWpfArg eDatabaseStore = new EventHandlerWithWpfArg();

_UiThread = new Thread(() =>
{
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
// The dialog becomes the owner responsible for disposing the objects given to it.
_mMyForm = new Ui(uiapp, evStr, eDatabaseStore);
_mMyForm.Closed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
_mMyForm.Show();
Dispatcher.Run();
});

_UiThread.SetApartmentState(ApartmentState.STA);
_UiThread.IsBackground = true;
_UiThread.Start();
}
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
// The dialog becomes the owner responsible for disposing the objects given to it.
_mMyForm = new Ui(uiapp, evStr, evWpf);
_mMyForm.Closed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
_mMyForm.Show();
Dispatcher.Run();
});

_uiThread.SetApartmentState(ApartmentState.STA);
_uiThread.IsBackground = true;
_uiThread.Start();
}

#region Idling & Closing
Expand Down Expand Up @@ -157,27 +154,26 @@ public RibbonPanel RibbonPanel(UIControlledApplication a)
{
a.CreateRibbonTab(tab);
}
catch
catch (Exception ex)
{
Util.HandleError(ex);
}

// Try to create ribbon panel.
try
{
RibbonPanel panel = a.CreateRibbonPanel(tab, "Develop");
}
catch
catch (Exception ex)
{
Util.HandleError(ex);
}

// Search existing tab for your panel.
List<RibbonPanel> panels = a.GetRibbonPanels(tab);
foreach (RibbonPanel p in panels)
foreach (RibbonPanel p in panels.Where(p => p.Name == "Develop"))
{
if (p.Name == "Develop")
{
ribbonPanel = p;
}
ribbonPanel = p;
}

//return panel
Expand Down
84 changes: 59 additions & 25 deletions Revit Template/Methods.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.Revit.DB;

namespace RevitTemplate
{

/// <summary>
/// Create methods here that need to be wrapped in a valid Revit Api context.
/// Things like transactions modifying Revit Elements, etc.
/// </summary>
internal class Methods
{
/// <summary>
/// Method for collecting sheets as an asynchronous operation on another thread.
/// </summary>
/// <param name="doc">The Revit Document to collect sheets from.</param>
/// <returns>A list of collected sheets, once the Task is resolved.</returns>
private static async Task<List<ViewSheet>> GetSheets(Document doc)
{
return await Task.Run(() =>
{
Util.LogThreadInfo("Get Sheets Method");
return new FilteredElementCollector(doc)
.OfClass(typeof(ViewSheet))
.Select(p => (ViewSheet) p).ToList();
});
}

/// <summary>
/// Rename all the sheets in the project. This opens a transaction, and it MUST be executed
Expand All @@ -23,38 +38,50 @@ internal class Methods
/// <param name="doc">The Revit Document to rename sheets in.</param>
public static void SheetRename(Ui ui, Document doc)
{
// get sheets
ICollection<ViewSheet> sheets = new FilteredElementCollector(doc)
Util.LogThreadInfo("Sheet Rename Method");

// get sheets - note that this may be replaced with the Async Task method above,
// however that will only work if we want to only PULL data from the sheets,
// and executing a transaction like below from an async collection, will crash the app
List<ViewSheet> sheets = new FilteredElementCollector(doc)
.OfClass(typeof(ViewSheet))
.Select(p => (ViewSheet)p).ToList();
.Select(p => (ViewSheet) p).ToList();

// report the count
string message = $"There are {sheets.Count} Sheets in the project";
// report results - push the task off to another thread
Task.Run(() =>
{
Util.LogThreadInfo("Sheet Rename Show Results");
ui.Dispatcher.Invoke(() => ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + message);
// report the count
string message = $"There are {sheets.Count} Sheets in the project";
ui.Dispatcher.Invoke(() =>
ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + message);
});

// rename all the sheets
// first open a transaction
// rename all the sheets, but first open a transaction
using (Transaction t = new Transaction(doc, "Rename Sheets"))
{
Util.LogThreadInfo("Sheet Rename Transaction");

// start a transaction within the valid Revit API context
t.Start("Rename Sheets");

// loop over the collection of sheets
foreach (ViewSheet sheet in sheets)
// loop over the collection of sheets using LINQ syntax
foreach (string renameMessage in from sheet in sheets
let renamed = sheet.LookupParameter("Sheet Name")?.Set("TEST")
select $"Renamed: {sheet.Title}, Status: {renamed}")
{
// rename the sheets
bool? renamed = sheet.LookupParameter("Sheet Name")?.Set("TEST");
string renameMessage = $"Renamed Sheet: {sheet.Title}";
ui.Dispatcher.Invoke(() => ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + renameMessage);
ui.Dispatcher.Invoke(() =>
ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + renameMessage);
}

t.Commit();
t.Dispose();
}

// report completion
ui.Dispatcher.Invoke(() => ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + "SHEETS HAVE BEEN RENAMED");
// invoke the UI dispatcher to print the results to report completion
ui.Dispatcher.Invoke(() =>
ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + "SHEETS HAVE BEEN RENAMED");
}

/// <summary>
Expand All @@ -77,15 +104,22 @@ public static void DocumentInfo(Ui ui, Document doc)
/// <param name="doc">The Revit Document to count the walls of.</param>
public static void WallInfo(Ui ui, Document doc)
{
ICollection<Wall> walls = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType()
.Select(p => (Wall)p).ToList();
Task.Run(() =>
{
Util.LogThreadInfo("Wall Count Method");
string message = $"There are {walls.Count} Walls in the project";
// get all walls in the document
ICollection<Wall> walls = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType()
.Select(p => (Wall) p).ToList();
ui.Dispatcher.Invoke(() => ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + message);
}
// format the message to show the number of walls in the project
string message = $"There are {walls.Count} Walls in the project";
// invoke the UI dispatcher to print the results to the UI
ui.Dispatcher.Invoke(() =>
ui.TbDebug.Text += "\n" + (DateTime.Now).ToLongTimeString() + "\t" + message);
});
}
}
}

}
18 changes: 9 additions & 9 deletions Revit Template/MethodsWrapped.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,27 @@ public override void Execute(UIApplication uiApp, Ui ui)
UIDocument uiDoc = uiApp.ActiveUIDocument;
Document doc = uiDoc.Document;

bool CbDocumentDataIsChecked = false;
ui.Dispatcher.Invoke(() => CbDocumentDataIsChecked = ui.CbDocumentData.IsChecked.GetValueOrDefault());
bool cbDocumentDataIsChecked = false;
ui.Dispatcher.Invoke(() => cbDocumentDataIsChecked = ui.CbDocumentData.IsChecked.GetValueOrDefault());

bool CbSheetDataIsChecked = false;
ui.Dispatcher.Invoke(() => CbSheetDataIsChecked = ui.CbSheetData.IsChecked.GetValueOrDefault());
bool cbSheetDataIsChecked = false;
ui.Dispatcher.Invoke(() => cbSheetDataIsChecked = ui.CbSheetData.IsChecked.GetValueOrDefault());

bool CbWallDataIsChecked = false;
ui.Dispatcher.Invoke(() => CbWallDataIsChecked = ui.CbWallData.IsChecked.GetValueOrDefault());
bool cbWallDataIsChecked = false;
ui.Dispatcher.Invoke(() => cbWallDataIsChecked = ui.CbWallData.IsChecked.GetValueOrDefault());

// METHODS
if (CbDocumentDataIsChecked)
if (cbDocumentDataIsChecked)
{
Methods.DocumentInfo(ui, doc);
}

if (CbSheetDataIsChecked)
if (cbSheetDataIsChecked)
{
Methods.SheetRename(ui, doc);
}

if (CbWallDataIsChecked)
if (cbWallDataIsChecked)
{
Methods.WallInfo(ui, doc);
}
Expand Down
1 change: 1 addition & 0 deletions Revit Template/Revit Template.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
<Compile Include="UI.xaml.cs">
<DependentUpon>UI.xaml</DependentUpon>
</Compile>
<Compile Include="Util.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="License.txt">
Expand Down
Loading

0 comments on commit 58b0744

Please sign in to comment.