2

I have a piece of code that does some calculations and then calls the form.show command. Now I have a library (the revit api) that does not allow me to store variables in a project without being in the main thread.

The logical solution for this is to get the spawned thread to call the main thread using say a producer/consumer pattern with code looking a bit like this:

form.Show(owner);
while(AppIsRunning){
    if(clicked)
        commit();
    else   
        Thread.sleep(100);
}

However when I do this the gui does not load fully (black background, no text in buttons ext.).

I have also tried doing this using the evoke method

    private void BtnOK_Click(object sender, System.EventArgs e)
    {
        Commit();
        Invoke(Commit);
    }

    private void Invoke(Action commit)
    {
        commit.Invoke();
    }

However this just tells me that it's not the main thread that's executing the commit function. Is there another way to do this or am I just making an error.

Just to be clear I have a form.show(owner) command that throws an error if it's not executed by the main thread. I also have a commit() function that must be excused by the main thread or it throws an error. The execution must wait until a button press. But the main thread polling the gui thread for changing causes the program to hang. According to my google search it' s also possible to do something involving an external event to get back into the right context but the example given was using python to invoke c# code, is there a good way to raise an external event to get back into a given thread in c#?

Edit: based on some suggestions I have created the following code:

public class ThreadManager
{
    static List<ThreadAble> orders = new List<ThreadAble>();
    public static bool running = false;
    public static void execute(ThreadAble action)
    {
        orders.Add(action);

    }

     static System.Timers.Timer timer;
    public static void RegisterAPIThreadAndHold(ExternalCommandData commandData)
    {

        UIApplication uiapp = commandData.Application;

        uiapp.Idling += Application_Idle;


    }


    private static void Application_Idle(Object o,IdlingEventArgs e)
    {
        if (orders.Count != 0)
        {
            ThreadAble f = orders.First();
            orders.Remove(f);
            f.execute();
        }
    }
}
public interface ThreadAble {
      void execute();        
}

However this does not appear to actually run when I use it as public override Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)

   Form frm = new OverviewForm(ExternalCommandData commandData);
   frm.show()
    ThreadManager.RegisterAPIThreadAndHold(commandData);
    ThreadManager.Execute(new run_ThrowError())

where ThrowError.execute() is Throw new Exception(" this is actually being executed" );

Thijser
  • 2,625
  • 1
  • 36
  • 71
  • I hope this is clear enough, if you have any questions feel free to comment. – Thijser Nov 02 '16 at 11:58
  • you should do your work on another thread, not the UI thread. – Daniel A. White Nov 02 '16 at 12:00
  • @DanielA.White Well I have a button.OnClick event which I can attach threads to but that seems to use the gui thread. How do I move that to the main thread? – Thijser Nov 02 '16 at 12:02
  • you should create your own thread or use one off the thread pool. – Daniel A. White Nov 02 '16 at 12:02
  • @DanielA.White how exactly? I mean I have my main thread which has to be the one calling the GUI thread (otherwise it throws an exception) and then from the GUI thread I want the main thread to execute some code again. – Thijser Nov 02 '16 at 12:03
  • theres lots of patterns and examples online. – Daniel A. White Nov 02 '16 at 12:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/127179/discussion-between-thijser-and-daniel-a-white). – Thijser Nov 02 '16 at 12:06
  • The framework has the notion of SynchronizationContext, and there's a WindowsFormsSynchronizationContext (implicit in the winforms context) especially suited for winforms UI thread synchronization, you shouldn't have to roll too much of your own, threads, threads managers, etc. This is all in relation with Control class' Invoke, BeginInvoke, etc methods. Example here: http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I – Simon Mourier Nov 11 '16 at 06:53

3 Answers3

1

Your first example could work if you will replace Thread.Sleep by the System.Windows.Forms.Application.DoEvents(). It should give time to paint GUI and do not froze application completly.

form.Show(owner);
while(AppIsRunning){
    if(clicked)
        commit();
    else   
    {
        System.Windows.Forms.Application.DoEvents();
        // Thread.sleep(100);
    }
}

But this is not perfect solution to achieve this. Better would be calling Dispatcher.Invoke command inside your dialog to perform MainThread operations. You can use i.e. GalaSoft library - please refer to DispatcherHelper object documentation and samples.

smartobelix
  • 748
  • 5
  • 16
0

The two ways to do this I'm aware of are with the External Event or the Idling event.

With the idling event, you'll register it, and while it is registered, your code (in the main thread) will get a callback from Revit every time that it's not busy with something else. Often a few times per second.

Once you are in the Idling callback, then you're able to create transactions and interact with the model. So your callback checks the state of the form and decides whether there is something to do.

The External Event works similarly in terms of registration, but you're able to request a trigger of the callback.

Jeremy Tammik must have 20 posts on thebuildingcoder.typepad.com on Modeless dialog / Revit stuff.

Matt
  • 1,043
  • 5
  • 8
  • Can you give an example of how to use this on an external event or with an idling event? I saw the post on thebuildingcoder.typepad.com however I'm not sure I understood it as his example code seemed to be going for a different language https://darenatwork.blogspot.nl/2015/06/embedding-webserver-in-autodesk-revit_30.html – Thijser Nov 04 '16 at 08:07
0

For a simple solution to this, please refer to the Revit SDK ModelessDialog ModelessForm_ExternalEvent sample application. It demonstrates exactly what you are asking for.

Jeremy Tammik
  • 7,333
  • 2
  • 12
  • 17
  • I looked at your code in the example on your website (at least I hope I got the right one). And set up something like this: public static void RegisterAPIThreadAndHold(ExternalCommandData commandData) { UIApplication uiapp = commandData.Application; uiapp.Idling += Application_Idle; } However this is not executing any ideas (I will update my question) – Thijser Nov 09 '16 at 13:17