View-ViewModel-ing for your WPF Windows (Revit API with WPF Series 3/3)

This is a long overdued post. Apologies for the very long wait. If I would be allowed to make an excuse, I became busy working on a Dynamo package named Celery, which is still on a pre-beta release due to much time dedicated to my company work and physical workouts.

I will still process some of the codes and images that this post needs. So please stay tuned for the update.

Anyway, let’s go to the real topic.

In this part of the series, we will tackle how to associate values between the plugin class and the view model class, and between the view model class and the WPF window.

First, create some controls within the window like the one I did below.

RevitAPIWPF3-01
We have a combo box, three radio buttons, a text box, two checkboxes, and an OK button. These are some of the controls that are frequently used in windows. We need to associate some variables to the each of the controls, except for the radio buttons, which shares one variable.
To associate them, we need to create a definition class that we will call the view model class.

Before creating a view model class, let us download first the “Prism” library from nu-get. We will be using the Prism library as our base library for our MVVM classes.

Now, we create a new class file for our new view model class. Let’s name it “MyViewModel.cs”. Open the newly created file and modify the file so that it inherits function we need for class to become a view model class:

using Prism.MVVM;

namespace MyRevitWPFProgram
{
class vmodThisWindow : BindableBase
{

}
}

The BindableBase base class is just a simplified class derived from INotifyPropertyChanged interface class, and has the event declaration and function already contained in it.

Now we create the variables we need. All the controls have their corresponding types of variable so we must know what to assign to which. When gathering a string of text from a TextBox, we need a string variable. When we need a number for a TextBox, we need a number variable with a string converter. When we need something to monitor if an item is checked or not, a boolean for each of them, or an integer with converter and parameters for radio buttons. WPF beginners can get more details regarding these on the internet.
But one thing I would like to recommend: DO NOT USE ANY OF THE REVIT CLASSES WITHIN THE VIEW MODEL. Using these classes will not let you use the capabilities of WPF automation functions in Visual Studio, and eventually the window will not display in the Design Screen disabling you to modify the contents of your window graphically. I have just verified this even very lately with the Visual Studio Community 2015.

So I declare my variables for my window something like below.

 

private ObservableCollection<string> _StringSelection;
private string _selectedString;
private int _radioOption;
private bool _isSelectedOption3;
private bool _isSelectedAddMe;
private bool _isSelectedAddMeToo;
private Dictionary<string, int> _dicIdString;

public ObservableCollection<string> StringSelection
{
get
{
return _StringSelection;
}

set
{
_StringSelection = value;
}
}

public string SelectedString
{
get
{
return _selectedString;
}

set
{
_selectedString = value;
RaisePropertyChanged("SelectedString");
}
}

public int RadioOption
{
get
{
return _radioOption;
}

set
{
_radioOption = value;
RaisePropertyChanged("RadioOption");
}
}

public bool IsSelectedOption3
{
get
{
return _isSelectedOption3;
}

set
{
_isSelectedOption3 = value;
RaisePropertyChanged("IsSelectedOption3");
}
}

public bool IsSelectedAddMe
{
get
{
return _isSelectedAddMe;
}

set
{
_isSelectedAddMe = value;
RaisePropertyChanged("IsSelectedAddMe");
}
}

public bool IsSelectedAddMeToo
{
get
{
return _isSelectedAddMeToo;
}

set
{
_isSelectedAddMeToo = value;
RaisePropertyChanged("IsSelectedAddMeToo");
}
}

public Dictionary<string, int> DicIdString
{
get
{
return _dicIdString;
}

set
{
_dicIdString = value;
}
}

 

Here are the major tips that I would suggest when declaring variables in creating a view model class:

a. ) When you have a combo box in your window, know its purpose. For example, if you want to put data of some of the family types and select only one of them, use a Dictionary class and assign the names as keys, and the IntegerValue of the Ids as values. However, if the content seem to be complex (e.g. materials with name, id and color as you primary info), create a unique class for that content and include only the variables that you need. In this case, you may use either the simple List<> class or a more sophisticated IObservableCollection<> class (but since most of the time the contents of the combo box do not change, List<> will be enough).
When you create a Dictionary, never forget to input the word “Key” in the DisplayMemberPath property, and the word “Value” in the SelectedValuePath property. This will make the combo box propagate only the keys to show in the combo box, and when one is selected, return its corresponding integervalue of the id.
On the other hand, when using list of classes, use the Binding procedure to bind a class item to the DisplayMemberPath what to show in the combo box, and another class item to the SelectedValuePath property.
On both cases, bind a variable to the combo box’s SelectedValue property, with the type the same as what you bind in the SelectedValuePath property. This variable must be independent, and you may not need to add an RaisePropertyChanged() function to it.

b.) When declaring variable with get-set and takes part of the user input handling from any of the controls inside the window, always call the RaisePropertyChanged(“<name of the public variable>”) on the last part of the ‘set’ segment.

c.) Do not include arguments in the constructor. Do not also create overloads of the constructor. Do all the variable assignments after creating the view model class.

 

Automating the Binding Procedure
When all the variables are declared, COMPILE FIRST THE PROGRAM AND MAKE SURE IT IS FREE OF ERRORS. Otherwise, you cannot use the interactive approach of binding contexts to the WPF window.

Now we will use this interactive approach to bind the view model class to our XAML window.

Select the window frame in the design view of the XAML, and from the Properties window, go to the DataContext property and click the “New” button adjacent to it. This will bring you to the window further below.

RevitAPIWPF3-03
Select “MyViewModel” class from the nested list and press OK. This will automatically integrate your view model to your WPF window.

RevitAPIWPF3-04

RevitAPIWPF3-05

In the image above, you can verify that Visual Studio made us bind our view model class to the WPF window by enclosing our view model class with the <Window.DataContext>.

 

Next we bind our variables to the window controls the similar way.

First, select the TextBox from the design view, and from the properties, click on the small box on the right (which is shaded black; it indicates it has a set value).

RevitAPIWPF3-06

 

Then from the context menu, select “Create Data Binding…”

RevitAPIWPF3-02

This will bring you to another window like the one below.

Make sure the Binding Type is set to Data context, so that you can see the view model class we created on the right side of the window. Expand MyViewModel, and there comes all the variables we declared. Select the SelectedString : (String) and press OK.

RevitAPIWPF3-07

Now we can check from the XAML code if it made us the binding. It should be like I have below. You can see that in the Text property of the TextBox SelectedString is bound to it.

RevitAPIWPF3-08

 

 

Binding the Radio Buttons

 

One complicated area here is binding three radio buttons in a single variable that is RadioOption. We will be using a converter and integrate converter parameters in each of the radio buttons.

Below is how I wrote my Converter class. I created a new class named MyConverterRadioButton, which is derived from the IValueConverter interface.

 

public class MyConverterRadioButton : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (parameter.ToString() == value.ToString());
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? parameter : Binding.DoNothing;
}
}

 

Since we are using the automation features in the Properties window of Visual Studio, we will associate the converter class to all the radio buttons.

In the properties window, click the square on the right of the IsChecked property to reveal the window below. On the right hand side of the window, selected RadioOption variable (1), and then pull down the combo box in the Converter option below (2), and then select “<Add value converter…>” (3)

RevitAPIWPF3-09

This will bring you to a child window like the one below. If the converter class you created appears (in this case, MyConverterRadioButton), then select the class and press OK. Otherwise, revert and compile the program first before returning to this window.

RevitAPIWPF3-10

When the converter class is selected, we can now assign a converter parameter in each of the radio buttons using the previous window. In the ConverterParameter field, enter “0” for the first radio button, “1” for the second, and “2” for the third one (4). Press OK when done (5). The result will reflect in the XAML code.

RevitAPIWPF3-11

Next we associate the text box to be enabled for editing once the third radio button is selected. Select the Text Box of the window, and in the Properties panel, press the square on the right side of the IsEnabled property. Then from the context menu, select Create Data Binding.

RevitAPIWPF3-12

From the window, select the variable IsSelectedOption3 (1) and press OK (2).

RevitAPIWPF3-13

Then we will just update our view model class with the code below inside the RadioOption set function in order to activate it every time the third radio button is selected, and deactivated otherwise.

 

public int RadioOption
{
get
{
return _radioOption;
}

set
{
_radioOption = value;

IsSelectedOption3 = value == 2;

RaisePropertyChanged(“RadioOption”);
}
}

 

Binding the Checkboxes

 

Binding the Checkboxes is straightforward as the method is similar with others mentioned above. But this time, we have to click the square of the IsChecked properties of the two checkboxes, and create the data bindings. Use IsSelectedAddMe and IsSelectedAddMeToo correspondingly.

RevitAPIWPF3-14

 

Binding a Button Command

 

Although this area does not play a significant role in our example, it is essential to learn how to bind them and how they function when the program is executed.

Add this code inside the view model class and re-compile the program.

 

#region Commands and Actions
public ICommand OKCommand
{
get
{
return new DelegateCommand(OKAction);
}
}

private void OKAction()
{
// This function will be executed after the button events.
}
#endregion

 

We simply give an action to the OK button whenever it is pressed. To give the event an action compatible with MVVM, first we create a variable OKCommand out of an ICommand class. Then inside its get function, the code just returns a new declaration of DelegateCommand that has an argument of OKAction, which will be a void function to be invoked. DelegateCommand is a function from Prism, so be sure Prism is installed in your project to be able to use it.

Once the command is called, the function invoked will execute, however, as mentioned in the comment above, OKAction will be executed after its event when pressed. So for example, you make an event from the Button named OKButton_Click(). The code inside OKButton_Click() will be executed first, before OKAction(). You can always verify it by debugging the program and see for yourself.

Now you know the sequence of the execution of the command events defined by the usual events and MVVM events, plan the program into its importance; what must come ahead and behind, or maybe, the necessity of each other. In this case, since the OK button is the only button, we may not add code inside the OKAction, but when it comes to other buttons, this trick may come handier.

Now we finished defining our Command events for our button. Compile it and see if there are errors. If there are none, let us bind it from the Properties window.

In the Miscellaneous group of the Properties window, there is an item there named Command. This item accepts any variable of type ICommand, which will be made clear further below. Click the square on its right and from the context menu, select Create Data Binding.

RevitAPIWPF3-15

In the window, select the OKCommand variable (1) and press OK (2). If you notice, OKCommand is variable of ICommand, which is recognized by WPF.

RevitAPIWPF3-16

 

…to be continued.

Tags:,
11 Comments

Leave a Reply to LinhNguyen Cancel reply

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