2

I'm writing an Addin for Revit with the template of Jeremy Tammik, which creates me a Ribbon with Buttons. I'm reading in an excel file, selecting lines and a starting point.

My Ribbon My WinForm

After the User is finished with his input, he should click on a "Done" button (which is missing in my case^^) and it should generate some FamilyInstances in the Project without closing the WinForm.

My Problems:

  • I can't use ShowDialog() right? Because this will create me a modal form and then i can't get any user input since the Revit Window is greyed out
  • I can't use Show() right? Since my Code will just continue

My Question:

Can someone maybe help me to find the right approach for my problem. I'm not quite sure if my approach is even going to work at the end

What i've tried so far:

I tried to make a Transaction in the WinForm Class but i knew this wasn't going to work.
I'm thinking about a separate class which will save the user input and a second ExternalCommand Class which will execute the Transaction to create the FamilyInstances.
I looked into the SDK and found the ModelessForm_ExternalEvent but i was hard for me to understand it...
I also found these posts and tried to understand them:
https://forums.autodesk.com/t5/revit-api-forum/winform-focus-after-user-selection/td-p/7344224
Revit Pick element from WinForm

My Code:

I have an App Class which creates the Ribbon and Buttons:

class App : IExternalApplication
{
    public Result OnStartup(UIControlledApplication a)
    {
        // Ingenieurbau Reiter erstellen
        string engineerTabName = "Ingenieurbau Addin";
        a.CreateRibbonTab(engineerTabName);
        // Gruppen erstellen
        RibbonPanel lswPanel = a.CreateRibbonPanel(engineerTabName, "Lärmschutzwand");
        // Buttons hinzufügen
        AddLswPushButtons(lswPanel);
        AddTestPushButton(lswPanel);

        return Result.Succeeded;
    }

    private void AddLswPushButtons(RibbonPanel panel)
    {
        // Strings erzeugen
        string lswName1 = "LSW Daten einlesen";
        string lswAssemblyName1 = Assembly.GetExecutingAssembly().Location;
        string lswClassName1 = "IngenieurbauAddin1.Lsw.DataInput";

        // ButtonData erzeugen
        PushButtonData lswPushButtonData1 = new PushButtonData(lswName1, lswName1, lswAssemblyName1, lswClassName1);

        // ButtonData dem Panel hinzufügen und in einen PushButton umwandeln
        PushButton lswPushButton1 = panel.AddItem(lswPushButtonData1) as PushButton;

        // ButtonBild einfügen
        lswPushButton1.LargeImage = PngImageSource("IngenieurbauAddin1.Resources.Excel.png");
    }
}

I have a DataInput Class which shows the Form:

[Transaction(TransactionMode.Manual)]
class DataInput : 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;

        // Form instanziieren
        DataInputForm dataInputForm = new DataInputForm(uidoc);
        // Form starten
        dataInputForm.Show();

        return Result.Succeeded;
    }
}

And this is my DataInputForm Class (i know that the PlaceFamily doesn't work):

public partial class DataInputForm : System.Windows.Forms.Form
{
    private UIDocument UIDocument { get; set; }
    private Document Document { get; set; }

    public DataInputForm(UIDocument uiDocument)
    {
        UIDocument = uiDocument;
        Document = uiDocument.Document;
        InitializeComponent();
    }

    private void btnSelectPoint_Click(object sender, EventArgs e)
    {
        Hide();
        SelectionHelper selectionHelper = new SelectionHelper(Document);
        XYZ point = selectionHelper.SelectLineEndPoint(UIDocument);

        labelPointLocalXValue.Text = Math.Round(point.X, 3).ToString();
        labelPointLocalYValue.Text = Math.Round(point.Y, 3).ToString();
        labelPointLocalZValue.Text = Math.Round(point.Z, 3).ToString();
        Show();

        PlaceFamily(point);
    }

    private void PlaceFamily(XYZ point)
    {
        FamilySymbolHelper familySymbolHelper = new FamilySymbolHelper(Document);
        FamilyInstanceHelper familyInstanceHelper = new FamilyInstanceHelper(Document);
        FamilySymbol famSym = familySymbolHelper.FamilySymbolByName("Master-Bauteil");
        try
        {
            using (Transaction t = new Transaction(Document, "Familie platzieren"))
            {
                t.Start();
                FamilyInstance famInst = familyInstanceHelper.FamilyInstanceByPoint(famSym, point);
                t.Commit();
            }
        }
        catch (Exception e)
        {
            TaskDialog.Show("Error", e.Message);
        }
    }
}

I just want to enter the inputs (Excelfile, Lines, Point) and then it should generate me the FamilyInstances. But i'm kinda stuck now ^^.

I appreciate any help.

Vahdet
  • 43
  • 8

2 Answers2

0

You used Form.Show() method in an ExternalCommand, so it is a modeless window. And in this window, there is a button, and the user click it and then pick an end point. Well, problem is: You cannot start a Transaction outside Revit API Context! I suggest you to research Revit API samples, in which there is a directory named as "Modeless Dialog". It clearly demonstrates how to work with modeless dialog via External Event. https://thebuildingcoder.typepad.com/blog/2015/12/external-event-and-10-year-forum-anniversary.html

Jolinpiggy
  • 50
  • 7
  • I'm not quite sure if you did read my question but i know that i'm doing a Transaction outside of the Revit API. I also tried to understand the ModelessForm_ExternalEvent Sample from the SDK. I just wanted to know if this is the only way to solve this? Or can it be done in another way... – Vahdet Jan 05 '19 at 13:38
  • 1
    As far as I know, there are only 2 ways of modifying Revit data base via a modeless dialog - External Event or Idling Event. They are both demonstrated in Revit SDK samples/ModelessDialog/. – Jolinpiggy Jan 07 '19 at 04:50
0

You just need to pass the Revit window handle to the when calling dataInputForm.Show();

Here is a modified code:

[Transaction(TransactionMode.Manual)]
internal class DataInput : IExternalCommand
{
    private static WindowHandle _hWndRevit = null;

    private void SetHandle()
    {
        if (null == _hWndRevit)
        {
            Process process
              = Process.GetCurrentProcess();

            IntPtr h = process.MainWindowHandle;
            _hWndRevit = new WindowHandle(h);
        }
    }

    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements)
    {

        SetHandle();

        UIApplication uiapp = commandData.Application;
        UIDocument uidoc = uiapp.ActiveUIDocument;
        Application app = uiapp.Application;
        Document doc = uidoc.Document;

        // Form instanziieren
        var dataInputForm = new DataInputForm(uidoc);
        // Form starten
        if (_hWndRevit != null)
        {
            dataInputForm.Show(_hWndRevit);
        }
        else
        {
            return Result.Failed;
        }

        return Result.Succeeded;
    }
}

Here is a topic in Jeremy Tammik blog about this issue: https://thebuildingcoder.typepad.com/blog/2009/02/revit-window-handle-and-modeless-dialogues.html

Khaled
  • 39
  • 1
  • 8