Updating Family Type Parameters

Updating Family Type Parameters

ChristianMolino
Enthusiast Enthusiast
578 Views
4 Replies
Message 1 of 5

Updating Family Type Parameters

ChristianMolino
Enthusiast
Enthusiast

I am still relatively new to coding and the Revit API, and I am working on a program that uses a windows form to display the user with parameters from a family type, make changes and then save those changes back to the type. I have everything working up until actually applying the changes to the type. From everything I can find, it seems like I am following samples of similar code to a t, but once the transaction commits, the family type is not updating like I would expect (nothing is changing).

 

The debug statements I have "MessageBox.Show("Changing " + checkBoxEntry.Value.ToString() + " to " + checkboxValue);" shows the correct parameter name, so I am not sure what exactly is going wrong.

 

ChristianMolino_0-1721406315322.png

ChristianMolino_1-1721406353007.png

 

 

 

Here is the code I am using to get all the parameters

 

private void comboBoxFamilyType_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBoxFamilyType.SelectedItem is FamilyTypeItem selectedFamilyType)
    {
        // Use a filtered element collector to find all instances of the selected family type
        var collector = new FilteredElementCollector(_doc)
            .OfClass(typeof(FamilyInstance))
            .WhereElementIsNotElementType()
            .Where(x => ((FamilyInstance)x).Symbol.Id == selectedFamilyType.Id);

        // Attempt to retrieve the first instance found; this will be used to access the family data
        var selectedInstance = collector.FirstOrDefault() as FamilyInstance;

        if (selectedInstance != null)
        {
            changesTracker.Clear();

            Family family = selectedInstance.Symbol.Family; // Retrieve the family from the instance symbol

            familyDoc = _doc.EditFamily( family ); // Open the family for editing which returns the family document

            familyManager = familyDoc.FamilyManager; // Get the family manager which manages the parameters of the family

            // Retrieve and print the number of parameters in the family
            int n = familyManager.Parameters.Size; 

            //Debug.Print("\nFamily {0} has {1} parameter{2}", _doc.Title, n, Util.PluralSuffix( n ) );

            // Create a dictionary to map parameter names to their corresponding FamilyParameter objects
            Dictionary<string, FamilyParameter> fps = new Dictionary<string, FamilyParameter>(n);

            foreach (FamilyParameter fp in familyManager.Parameters)
            {
                string name = fp.Definition.Name;
                fps.Add (name, fp );
            }
            // Sort the keys (parameter names) for better manageability
            List<string> parameters = new List<string>( fps.Keys );
            parameters.Sort();

            // Print the number of types in the family and their names
            n = familyManager.Types.Size;

            //Debug.Print("\nFamily {0} has {1} type{2}{3}", _doc.Title, n, Util.PluralSuffix(n), Util.DotOrColon(n));

            string matchName = selectedFamilyType.ToString()
                    .Replace("DD Mirror: ", "")
                    .Replace("DD Adjust: ", "");

            Dictionary<string, string> parameterValues = new Dictionary<string, string>(); // Dictionary to store parameter values

            // Iterate through each type in the family
            foreach (Autodesk.Revit.DB.FamilyType type in familyManager.Types)
            {
                // Get the match name and remove "DD Mirror: " or "DD Adjust: " from it
                if (type.Name == matchName)
                {
                    activeFamilyType = type;
                    // For each parameter key, check if the type has a value set and print it
                    foreach (string key in parameters)
                    {
                        FamilyParameter fp = fps[key];
                        if (type.HasValue(fp))
                        {
                            string value = Util.FamilyParamValueString(type, fp, _doc);
                            parameterValues[key] = value; // Store the parameter value in the dictionary
                            //Debug.Print("    {0} = {1}", key, value);
                        }
                    }
                }
                else
                {
                    continue;
                }
            }

            // Temporarily disable change tracking
            DisableChangeTracking();


            // Set dimension text box values
            foreach (var entry in ControlMappings.TextBoxDimensionMappings)
            {
                var textBox = this.Controls.Find(entry.Key, true).FirstOrDefault() as System.Windows.Forms.TextBox;
                if (textBox != null)
                {
                    textBox.Text = parameterValues.ContainsKey(entry.Value) ? parameterValues[entry.Value] : "Error";
                }
            }

            // Set text box values
            foreach (var entry in ControlMappings.TextBoxMappings)
            {
                var textBox = this.Controls.Find(entry.Key, true).FirstOrDefault() as System.Windows.Forms.TextBox;
                if (textBox != null)
                {
                    textBox.Text = parameterValues.ContainsKey(entry.Value) ? parameterValues[entry.Value] : "Error";
                }
            }

            // Set checkbox values
            foreach (var entry in ControlMappings.CheckBoxMappings)
            {
                var checkBox = this.Controls.Find(entry.Key, true).FirstOrDefault() as System.Windows.Forms.CheckBox;
                if (checkBox != null)
                {
                    checkBox.Checked = parameterValues.ContainsKey(entry.Value) && parameterValues[entry.Value] == "1";
                }
            }

            SetupChangeTracking();
        }
        else
        {
            MessageBox.Show("No instances of the selected family type were found.");
        }
    }
}

 

 

Here is the Change Tracking Method

        private void SetupChangeTracking()
        {
            // Initiate recursive setup from the top-level form controls
            SetupControlTracking(this);
        }

        private void SetupControlTracking(System.Windows.Forms.Control control)
        {
            foreach (System.Windows.Forms.Control child in control.Controls)
            {
                if (child is System.Windows.Forms.TextBox textBox)
                {
                    string originalText = textBox.Text; // Store initial text
                    textBox.Tag = originalText;  // Use Tag to store the original value
                    EventHandler textBoxHandler = (s, e) =>
                    {
                        if (textBox.Text != originalText)
                        {
                            changesTracker[textBox.Name] = textBox.Text;
                            originalText = textBox.Text; // Update the original text after the change
                        }
                    };
                    textBox.Leave += textBoxHandler;
                    textBoxDelegates[textBox] = textBoxHandler;
                }
                else if (child is CheckBox checkBox)
                {
                    bool originalState = checkBox.Checked; // Store initial state
                    checkBox.Tag = originalState;  // Use Tag to store the original value
                    EventHandler checkBoxHandler = (s, e) =>
                    {
                        if (checkBox.Checked != originalState)
                        {
                            changesTracker[checkBox.Name] = checkBox.Checked ? "1" : "0";
                            originalState = checkBox.Checked; // Update the original state after the change
                        }
                    };
                    checkBox.CheckedChanged += checkBoxHandler;
                    checkBoxDelegates[checkBox] = checkBoxHandler;
                }

                // Recursively handle child controls
                if (child.HasChildren)
                {
                    SetupControlTracking(child);
                }
            }
        }

 

Here is the method calling the IExternalEventHandler from the form

 

public partial class MainFormEditor : System.Windows.Forms.Form
{
    //private Dictionary<string, string> originalParameterValues; // Dictionary to store parameter values
    private Dictionary<string, string> changesTracker = new Dictionary<string, string>();
    private Document _doc;
    private Document familyDoc;  // Add class-level family document
    private FamilyManager familyManager;  // Add class-level family manager
    private ElementId _initialSelectedId = null; // If a DualDeck is selected in the document when the program is launched, store it here
    private FamilyType activeFamilyType = null;

    private ExternalEvent exEvent;
    private ParameterUpdateHandler handler;

    private Dictionary<System.Windows.Forms.Control, EventHandler> textBoxDelegates = new Dictionary<System.Windows.Forms.Control, EventHandler>();
    private Dictionary<System.Windows.Forms.Control, EventHandler> checkBoxDelegates = new Dictionary<System.Windows.Forms.Control, EventHandler>();
    public MainFormEditor(Document doc, UIDocument uidoc)
    {
        InitializeComponent();
        _doc = doc;
        InitializeDualDeckSelection(uidoc);
        comboBoxFamilyType.SelectedIndexChanged += comboBoxFamilyType_SelectedIndexChanged; // Attach the event handler for updating the data based on DualDeck selection
        textBoxDD_Depth.Leave += textBoxDD_Depth_Leave;
        //SetupChangeTracking();

        handler = new ParameterUpdateHandler(); // Initially empty, setup later
        exEvent = ExternalEvent.Create(handler);
        //btnSave.Click += btnSave_Click;
    }

private void btnSave_Click(object sender, EventArgs e)
{
    Debug.Print("Changes to save: " + changesTracker.Count);
    if (familyDoc != null && familyManager != null)
    {
        // Update the handler with current documents and parameters
        handler.Setup(familyDoc, familyManager, activeFamilyType, changesTracker);
    }
    exEvent.Raise();
}
}

 

 

And finally here is the external event handler 

 

public class ParameterUpdateHandler : IExternalEventHandler
{
    private Document doc;
    private FamilyManager familyManager;
    private FamilyType currentFamilyType;
    private Dictionary<string, string> changesTracker;

    public void Setup(Document doc, FamilyManager manager, FamilyType familyType , Dictionary<string, string> tracker)
    {
        this.doc = doc;
        this.familyManager = manager;
        this.changesTracker = new Dictionary<string, string>(tracker);
        this.currentFamilyType = familyType; // Setup with current family type
    }

    public void Execute(UIApplication app)
    {
        MessageBox.Show("Executing update with changes count: " + changesTracker.Count); // Check if this shows and has count > 0
        using (Transaction tx = new Transaction(doc, "Update Parameters"))
        {
            tx.Start();
            
            // Process text box changes
            foreach (var textBoxEntry in ControlMappings.TextBoxDimensionMappings.Concat(ControlMappings.TextBoxMappings))
            {
                if (changesTracker.TryGetValue(textBoxEntry.Key, out string newValue))
                {
                    var parameter = familyManager.get_Parameter(textBoxEntry.Value);
                    if (parameter != null)
                    {
                        UpdateFamilyParameter(parameter, newValue);
                    }
                }
            }
            // Process checkbox changes
            foreach (var checkBoxEntry in ControlMappings.CheckBoxMappings)
            {
                if (changesTracker.TryGetValue(checkBoxEntry.Key, out string newCheckValue))
                {
                    var parameter = familyManager.get_Parameter(checkBoxEntry.Value);
                    if (parameter != null)
                    {
                        int checkboxValue = newCheckValue == "1" ? 1 : 0;
                        MessageBox.Show("Changing " + checkBoxEntry.Value.ToString() + " to " + checkboxValue);
                        UpdateFamilyParameter(parameter, checkboxValue.ToString());
                    }
                }
            }
            try
            {
                MessageBox.Show("Committing Tx");
                // parameter setting code
                tx.Commit();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to Commit. Rolling back.");
                Debug.Print("Failed to commit transaction: " + ex.Message);
                tx.RollBack();
            }
        }
    }

    private void UpdateFamilyParameter(FamilyParameter parameter, string value)
    {
        if (parameter.StorageType == StorageType.String)
        {
            MessageBox.Show("Parameter type: String. Updating Paramater: " + parameter.ToString() + " as " + value);
            familyManager.Set(parameter, value);
        }
        else if (parameter.StorageType == StorageType.Integer)
        {
            MessageBox.Show("Parameter type: Integer. Updating Paramater: " + parameter.ToString() + " as " + value);
            if (int.TryParse(value, out int intValue))
            {
                familyManager.Set(parameter, intValue);
            }
        }
        else if (parameter.StorageType == StorageType.Double)
        {
            MessageBox.Show("Parameter type: Double. Updating Paramater: " + parameter.ToString() + " as " + value);
            if (double.TryParse(value, out double doubleValue))
            {
                familyManager.Set(parameter, doubleValue);
            }
        }
        // Add other storage types handling as needed
    }

    public string GetName()
    {
        return "Parameter Update Handler";
    }
}

 

 

0 Likes
579 Views
4 Replies
Replies (4)
Message 2 of 5

Speed_CAD
Collaborator
Collaborator

Hi ChristianMolino,

 

When reviewing the entire code, not in a detailed way, I noticed that the Save() method is missing to save the changes to the family file you are editing. Try adding the Save() to the end of your code, this does not go inside the transaction, it goes outside and after.

Should be:

 

familyDoc.Save();

 

Mauricio Jorquera
0 Likes
Message 3 of 5

ChristianMolino
Enthusiast
Enthusiast

Not quite, but I think you are onto something. That saved the .rfa source file back on my drive, but that's not quite what I am looking to do.

 

Perhaps this is just coming from my lack of knowledge of what Revit is doing in the background, but I have a main project document open, that it is opening the family document from. When I make these changes, I think maybe it is applying the changes to the family document and now do I need to load them back into the main project document instead of doing the save that you had previously mentioned?

 

I think I may have falsely assumed that changing the family doc would immediately show updates in my project document.

0 Likes
Message 4 of 5

Speed_CAD
Collaborator
Collaborator

Exactly, if you are making changes to the rfa file you must load said family in the source document. Updating the rfa file will not change the project document, for that you must load the family.

 

But my question is, do you really need to modify the rfa file? Because if what you are looking for is to update a type parameter in a particular family, it is not necessary to modify the original rfa file, you only have to modify the type parameter of the family already loaded in your project document.

Mauricio Jorquera
Message 5 of 5

ChristianMolino
Enthusiast
Enthusiast

Yup, you are correct, I was able to get it to work by just using the symbol instead! Now I think I need to go back and see if I can do the same for when I access the data to populate the form so I never even need to open the rfa at all. Thanks for the help!

 public void Execute(UIApplication app)
 {
     MessageBox.Show("Executing update with changes count: " + changesTracker.Count); // Check if this shows and has count > 0
     using (Transaction tx = new Transaction(_doc, "Update Parameters"))
     {
         tx.Start();

         if (familySymbol != null)
         {
             // Activate the family symbol if it's not already active
             if (!familySymbol.IsActive)
             {
                 familySymbol.Activate();
                 _doc.Regenerate();
             }

             // Modify the family type parameters
             foreach (var textBoxEntry in ControlMappings.TextBoxDimensionMappings.Concat(ControlMappings.TextBoxMappings))
             {
                 if (changesTracker.TryGetValue(textBoxEntry.Key, out string newValue))
                 {
                     Parameter parameter = familySymbol.LookupParameter(textBoxEntry.Value);
                     if (parameter != null)
                     {
                         parameter.Set(newValue);
                     }
                 }
             }

             foreach (var checkBoxEntry in ControlMappings.CheckBoxMappings)
             {
                 if (changesTracker.TryGetValue(checkBoxEntry.Key, out string newCheckValue))
                 {
                     Parameter parameter = familySymbol.LookupParameter(checkBoxEntry.Value);
                     if (parameter != null)
                     {
                         int checkboxValue = newCheckValue == "1" ? 1 : 0;
                         MessageBox.Show("Changing " + checkBoxEntry.Value.ToString() + " to " + checkboxValue);
                         parameter.Set(checkboxValue);
                     }
                 }
             }
         }

         try
         {
             MessageBox.Show("Committing Tx");
             // parameter setting code
             tx.Commit();
         }
         catch (Exception ex)
         {
             MessageBox.Show("Failed to Commit. Rolling back.");
             Debug.Print("Failed to commit transaction: " + ex.Message);
             tx.RollBack();
         }
     }
 }
0 Likes