Monday, August 22, 2011

[Revit API]–How to create an installer for your Revit Addins

Often I see Revit API utilities distributed with instructions to copy manifest files around and put the files in a certain directory (I’ve done it myself in the past), which works fine, but there is a more user friendly way of setting up your addins. You can use Visual Studios ‘Setup Project’ functions to create an installer, which isn’t the pretties installer going around, but it gets the job done.
Doing this is a little harder than you’d think, there’s a few gotchas, so this post aims to step you through it.
Add a new project to your solution, under ‘Other Project Types’, ‘Setup and Deployment’ choose ‘Setup Project’.
image
You’ll see a view like this, delete ‘Users Desktop’, ‘User Programs Menu’
image
Right click on Application folder and go to add > project output. Choose the Addin project.
image
Right click on application folder again, go to properties and change the DefaultLocation to be:
[ProgramFiles64Folder][Manufacturer]\[ProductName]
This is so that it will always be in a consistent install location, rather than program files (x86).
Now, we don’t want to distribute RevitAPI and RevitAPIUI dlls, so in the solution explorer, expand detected dependencies and right click on those and click exclude.
image
Click on the installer project, then click the properties tab, and set the ‘Manufacturer’ and ‘ProductName’ to match your company/addin name. Also, while you are here, change the ‘TargetPlatform’ to X64. Unfortunately with these types of installers you need a separate install for X86 and X64, but I think most of you will be building API addins for X64 systems, as Revit generally needs at least 4gb of RAM, and if you are enough of a power user to be installing add-ins, you are going to be running a 64bit operating system I’m sure. But keep in mind that it may vary for you.
We need to create a .addin manifest file that points to the program files folder as follows, make sure you generate a new GUID.
<RevitAddIns>
  <AddIn Type="Command">
    <Text>My Revit Util</Text>
    <Assembly>C:\Program Files\COMPANY NAME\PRODUCT NAME\Addin.dll</Assembly>
    <AddInId>18520534-3ee1-473e-ae97-a68dfbda3754</AddInId>
      <FullClassName>MyRevitUtil.Command</FullClassName>
  <VendorId>COMP </VendorId>
     <VendorDescription>MY COMPANY </VendorDescription>  
  </AddIn>
</RevitAddIns>

In The primary output screen, add a new custom folder, called Autodesk Addins. Right click on it and go to properties and set the DefaultLocation to
[CommonAppDataFolder]\Autodesk\Revit\Addins\2012
and Property to AUTODESKADDINS
This will install it for all users. Right click in the middle panel and go to add file, and choose your manifest.
image
At this point you’re almost done, there’s just a couple of .NET settings to tweak. First of all, we don’t need to bundle an installer for .NET framework as Revit installs it by default, so right click on your installer project, go to properties and hit prerequisites
image
Then untick the first checkbox
image
Finally, right click on your project, click View > Launch Conditions, click .NET framework and change it to .NET Framework 4 (or lower if you are in Revit 2011 mode still).
image
Now you are done, you can right click your project and go to Build and your .msi installer will be output.

Monday, August 1, 2011

[WPF]–How to select checkbox of items in ListBox by clicking text

In WPF, you can create a checked list box by creating a normal ListBox and using an ItemTemplate as follows:
<ListBox Margin="10" HorizontalAlignment="Stretch" Name="lbSheets" 
              VerticalAlignment="Stretch" Width="Auto" Grid.Row="1" MinWidth="321"
              MinHeight="40" HorizontalContentAlignment="Left" 
              ItemTemplate="{StaticResource ListBoxItemTemplate}" VerticalContentAlignment="Top" Background="#FFDCEBEE" SelectionMode="Single" SelectionChanged="lbSheets_SelectionChanged">
 
     </ListBox>


<DataTemplate x:Key="ListBoxItemTemplate" >
 
     <WrapPanel>
         <CheckBox Focusable="False" IsChecked="{Binding Selected}" VerticalAlignment="Center" />
         <ContentPresenter  Content="{Binding FullName, Mode=OneTime}"  Margin="2,0" />
     </WrapPanel>
 
 </DataTemplate>

In my example, I am then binding a domain model object that implements INotifyPropertyChanged, and I have a boolean property for Selected, when the checkbox is checked, it will toggle this value, and vice versa. The text that is displayed is from a property called ‘FullName’, as specified in the DataTemplate.
image

However, I wanted to be able to check the checkboxes by clicking on the text next to them, not by pinpointing the check box. I tried a number of ways of doing this before finally arriving at a solution.
I ended up suing the ‘MouseUp’ event on the ContentPresenter from my DataTemplate
<DataTemplate x:Key="ListBoxItemTemplate" >
 
      <WrapPanel>
          <CheckBox Focusable="False" IsChecked="{Binding Selected}" VerticalAlignment="Center" />
          <ContentPresenter  Content="{Binding FullName, Mode=OneTime}"  Margin="2,0" MouseUp="ContentPresenter_MouseUp" />
      </WrapPanel>
 
  </DataTemplate>
And in this method I did the following:
private void ContentPresenter_MouseUp(object sender, MouseButtonEventArgs e)
{
    string text = ((TextBlock) e.OriginalSource).Text;
 
    foreach (var item in this.lbSheets.Items)
    {
        PrintableSheet sheetItem = (PrintableSheet) item;
        if (sheetItem.FullName.Equals(text))
        {
            sheetItem.Selected = !sheetItem.Selected;
        }
    }
 
}
I used the OriginalSource proprety from the event arguments to get the TextBlock, I used its text, and matched it up with my domain object (PrintableSheet) using the property that was bound. So now when you click the text on the item, it will toggle the checkbox.