Revit classes and WPF? The first M in MVVM

I have been receiving inquiries on my blog regarding the use of Revit classes inside the WPF window, because I mentioned that we must not use the Revit API classes inside our view models, the reason being the Revit classes obstruct the use of view-view model functionality during the design of our WPF windows. So how are we going to use these functions/classes especially in Revit’s Document class when the WPF window is active?

This is now the turn of the first M in the acronym MVVM, which is the model.

We represent some of our Revit classes in a model that we will call here the Revit model.

Inside this Revit model, the UIApplication, UIDocument, and Document classes are represented by their respective variables, which are made private. These variables shall be initialized inside the model’s constructor, which has an argument of a UIApplication class. We name this modelRevitBridge.cs.

Below is a sample of it.

[code language=”csharp”]
class modelRevitBridge
{
// Just like what you do when creating a Revit command, declare the necessary variable such as below.
private UIApplication UIAPP = null;
private Application APP = null;
private UIDocument UIDOC = null;
private Document DOC = null;

// The model constructor. Include a UIApplication argument and do all the assignments here.
public modelRevitBridge(UIApplication uiapp)
{
UIAPP = uiapp;
APP = UIAPP.Application;
UIDOC = UIAPP.ActiveUIDocument;
DOC = UIDOC.Document;
}

// This function will be called by the Action function in the view model, so it must be public.
public List<string> GenerateParametersAndValues(int idIntegerValue)
{
List<string> resstr = new List();

Element el = DOC.GetElement(new ElementId(idIntegerValue));
if (el != null)
{
foreach (Parameter prm in el.Parameters)
{
string str = prm.Definition.Name;
str += ” : “;
str += prm.AsValueString();

resstr.Add(str);
}
}

return resstr.OrderBy(x => x).ToList();
}
}
[/code]

Below is the view model that we integrate with the view later. Name this as viewmodelRevitBridge.cs. This class has a Dictionary that holds all the wall types’ name and id in integer value. It also has a variable of type int that holds the selected value from the combo box, as well as a list of string declared as an ObservableCollection where we save all the parameter information of the selected wall type. Also, we create a pair of command variable and action function to be connected to the button that will generate the parameters in strings.

[code language=”csharp”]
private Dictionary<string, int> _dicWallType;
private int _selectedWallType;
private ObservableCollection<string> _listParameters;

// Declare the Revit model class here.
// Consequently, create a get-set variable representing this.
private modelRevitBridge _revitModel;

public Dictionary DicWallType
{
get
{
return _dicWallType;
}

set
{
SetProperty(ref _dicWallType, value);
}
}

public int SelectedWallType
{
get
{
return _selectedWallType;
}

set
{
SetProperty(ref _selectedWallType, value);
}
}

public ObservableCollection<string> ListParameters
{
get
{
return _listParameters;
}

set
{
SetProperty(ref _listParameters, value);
}
}

// Commands
// This will be used by the button in the WPF window.
public ICommand RetrieveParametersValuesCommand
{
get
{
return new DelegateCommand(RetrieveParametersValuesAction);
}
}

// The get-set variable
internal modelRevitBridge RevitModel
{
get
{
return _revitModel;
}

set
{
_revitModel = value;
}
}

// The action function for RetrieveParametersValuesCommand
private void RetrieveParametersValuesAction()
{
if (SelectedWallType != -1)
{
ListParameters = new ObservableCollection(RevitModel.GenerateParametersAndValues(SelectedWallType));
}
}

// Constructor
public viewmodelRevitBridge()
{

}
[/code]

The following, on the other hand, is a sample of a view of our sample project, coded in XAML. Let us name this viewRevitBridge.xaml. This view will have a combo box and a list box.

Edit the corresponding xaml.cs file as well so that we make this window disposable and we hide the minimize and maximize button.

[code language=”xml”]
<Window x:Class=”RevitBridgeSample.viewRevitBridge”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:d=”http://schemas.microsoft.com/expression/blend/2008″
xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
xmlns:local=”clr-namespace:RevitBridgeSample”
mc:Ignorable=”d”
Title=”Revit Bridge Sample” Height=”456″ Width=”279″ ResizeMode=”NoResize” WindowStartupLocation=”CenterScreen” ShowInTaskbar=”False” SourceInitialized=”Window_SourceInitialized”>
<Window.DataContext>
<local:RevitBridgeViewModel/>
</Window.DataContext>
<Grid>
<ComboBox x:Name=”comboBox” Height=”35″ Margin=”10,10,10,0″ VerticalAlignment=”Top” ItemsSource=”{Binding DicWallType}” DisplayMemberPath=”Key” SelectedValuePath=”Value” SelectedValue=”{Binding SelectedWallType}”/>
<ListBox x:Name=”listBox” Margin=”10,95,10,77″ ItemsSource=”{Binding ListParameters}”/>
<Button x:Name=”bOk” Content=”OK” HorizontalAlignment=”Right” Height=”37″ Margin=”0,0,93,10″ VerticalAlignment=”Bottom” Width=”105″ IsDefault=”True” Click=”bOk_Click”/>
<Button x:Name=”bCan” Content=”Cancel” HorizontalAlignment=”Right” Height=”27″ Margin=”0,0,10,10″ VerticalAlignment=”Bottom” Width=”78″ IsCancel=”True”/>
<Button x:Name=”bProp” Content=”Retrieve Parameters and values” Height=”30″ Margin=”10,50,10,0″ VerticalAlignment=”Top” Command=”{Binding RetrieveParametersValuesCommand, Mode=OneWay}”/>
</Grid>
</Window>
[/code]

Lastly, this will be the sample code for the Revit command, which will run the WPF window.

[code language=”csharp”]
class RevitBridgeCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Application app = uiapp.Application;
Document doc = uidoc.Document;

try
{
// Get all the wall types in the current project and convert them in a Dictionary.
FilteredElementCollector felc = new FilteredElementCollector(doc).OfClass(typeof(WallType));
Dictionary<string, int> dicwtypes = felc.Cast().ToDictionary(x => x.Name, y => y.Id.IntegerValue);
felc.Dispose();

// Create a view model that will be associated to the DataContext of the view.
viewmodelRevitBridge vmod = new viewmodelRevitBridge();
vmod.DicWallType = dicwtypes;
vmod.SelectedWallType = dicwtypes.First().Value;

// Create a new Revit model and assign it to the Revit model variable in the view model.
vmod.RevitModel = new modelRevitBridge(uiapp);

System.Diagnostics.Process proc = System.Diagnostics.Process.GetCurrentProcess();

// Load the WPF window viewRevitbridge.
using (viewRevitBridge view = new viewRevitBridge())
{
System.Windows.Interop.WindowInteropHelper helper = new System.Windows.Interop.WindowInteropHelper(view);
helper.Owner = proc.MainWindowHandle;

// Assign the view model to the DataContext of the view.
view.DataContext = vmod;

if (view.ShowDialog() != true)
{
return Result.Cancelled;
}
}

return Result.Succeeded;
}
catch (Exception ex)
{
message = ex.Message;
return Result.Failed;
}
}
}
[/code]

Running this code will turn out like this:

The image below will be the initial screen, which gets all the wall types inside your Revit project.

rmw01

When you press the “Retrieve Parameters and values” button, all the parameters and their values of the selected wall type will be listed up inside the listbox.

rmw02

You can also create a function that writes something in your Revit project, like editing parameters of renaming the wall types. In this case though, we have to wrap the writing part of the code inside a Transaction class, or else the program will fail.
When you do this, every time you execute the writing function, it registers a command in the undo-redo mechanism. So if you want to run this function multiple times and want to undo or redo these process just once, you need to wrap the WPF window loading inside a TransactionGroup and just before the end of it, use the TransactionGroup.Assimilate() function.

4 Comments

Add a Comment

Your email address will not be published. Required fields are marked *