Modeless Form wait with code execution till button press

Modeless Form wait with code execution till button press

grubdex
Enthusiast Enthusiast
942 Views
8 Replies
Message 1 of 9

Modeless Form wait with code execution till button press

grubdex
Enthusiast
Enthusiast

I have an ExternalApplication that calls an ExternalCommand. Somewhere during the execution of the command,

I have a very simple modeless form that is basically an "Apply" Button - it's supposed to let the user tell me when he has finished a certain action in the UI. The problem I'm facing is that I can't find a way to pause the execution of code until the user has pressed apply.

 

 

My own simple checkbox form has the following code.

CheckBoxForm.xaml.cs:

public partial class CheckBoxForm : Window
{
public bool isClosed { get; private set; }


public CheckBoxForm()
{

isClosed = false;
InitializeComponent();
}

private void CheckBoxTriggered(object sender, RoutedEventArgs e)
{
isClosed = true;
Close();
}
}

CheckBoxForm.xaml

<Window x:Class="RevitAPIHandler.UI.Forms.CheckBoxForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Apply" Height="140" Width="250" Topmost="True">
<StackPanel Margin="10">
<Label FontWeight="Bold">
<Label.Content>
<AccessText TextWrapping="Wrap" Text="Press here if you're done with your changes." />
</Label.Content>
</Label>
<CheckBox Checked="CheckBoxTriggered">Apply</CheckBox>
</StackPanel>
</Window>

Function that is called from a function of a function of my ExternalCommand. Note that the function still has access to the UIApplication, if that helps.

public void GetUserInput(UIApplication uiapp)
{
// some stuff happening here
// now my checkbox WPF form
_checkBoxForm = new CheckBoxForm();
_checkBoxForm.Show(); // this starts modeless. User should now do stuff in the UI, and, once finished, press the checkmark button.
// Other stuff happening here. BUT I want to wait with the execution of this stuff until the user has checked the checkbox in the modeless form

return;
}

 

I'm wondering whether there is a solution to my problem or whether I'm tackling the problem the wrong way. I've looked at the SDK Modeless samples with Idling and ExternalEventHandler as well as many posts in this forum, but all examples I find seem to incorporate the modeless form on the level of the ExternalApplication itself, whereas for me it's supposed to be a small step within a single command.

0 Likes
943 Views
8 Replies
Replies (8)
Message 2 of 9

mhannonQ65N2
Collaborator
Collaborator

Why does the form have to be modeless? It seems like it would be easier in this situation for it to be modal.

0 Likes
Message 3 of 9

grubdex
Enthusiast
Enthusiast

I don't think the user would be able to interact with the Revit UI while a modal form is open

0 Likes
Message 4 of 9

RPTHOMAS108
Mentor
Mentor

You have to be outside of the API context one way or another to interact with the Revit UI in a non-API way.

 

This either means opening a modeless window and Revit waiting for an ExternalEvent or closing a Modal one.

 

You can achieve your ambitions with an ExternalEvent since it is just as easy to close a modeless dialogue within an ExternalEventHandler as it is to close a modal one.

 

However, the Apply button seems a bit redundant since all it is doing in effect is starting a command or continuing one i.e. what if the user never presses the Apply button? All the other changes made by the user would still be committed regardless. The only thing you could do is prevent the user from saving during that period before Apply is pressed but in that situation, you can't undo instead of Apply so the Apply would be inevitable one way or another. You could perhaps OpenAndActivateDocument on the old file (not saved since your initial command started). I don't know if that would work as a way of reverting to what you had before you started. However you would have to save the document before you started the command. You could implement some form of 'This command requires you to save the document first.' dialogue.

 

Start command via IExternalCommand

       Prompt to save document first if not last item

       Prevent synchronisation and saving

       Show modeless form (user does 'other' things in UI)

Start context via IExternalEventHandler (Apply or Cancel activated)

       Restore the ability to save and synchronise

       Close modeless form

             Apply: Continue doing anything else required via IExternalEventHandler

             Cancel: Restore previously saved document to replace active document

   

I guess I need to know the real purpose of this apply button.

Message 5 of 9

grubdex
Enthusiast
Enthusiast

Thanks for the reply. What I'm trying to achieve is to create two "snapshots" of the same FamilyInstances in a selected room to be able to compare them for eventual changes by the user. So basically a first snapshot of the elements in the room at the start of the command, then allowing the user to change elements (while the modeless window is open), then taking a second snapshot upon them pressing the Apply button. I'm not set on the modeless Apply button, but it seems to me to be the only sensible way for the user to tell the command they are "done" with changes and the second snapshot can be taken. If you know a better way to achieve this, please let me know!

As for the current approach, I did try the approach with an external event as an extra class that is then being passed to my CheckBoxForm, but that didn't achieve what I wanted, i.e. the code still continued its execution without waiting - but maybe I did something wrong, as this is my first try at modeless forms. Here is my approach with an ExternalEvent

 

The CheckBoxForm code-behind

public partial class CheckBoxForm : Window
{
public bool isClosed { get; private set; }

private ExternalEvent _ExEvent;
private SyncExternalEvent _Handler;

public CheckBoxForm(ExternalEvent exEvent, SyncExternalEvent handler)
{
_ExEvent = exEvent;
_Handler = handler;
isClosed = false;
InitializeComponent();
}

private void CheckBoxTriggered(object sender, RoutedEventArgs e)
{
_ExEvent.Raise();
Close();
}
}

The adjusted GetUserInput function

public void GetUserInput(UIApplication uiapp)
{
// stuff happening here...
// getting the elements at the beginning
var elementsStart = GetSelectedElements(room);
// then, tell the user to change stuff
SyncExternalEvent handler = new SyncExternalEvent();
ExternalEvent externalEvent = ExternalEvent.Create(handler);
var checkForm = new CheckBoxForm(externalEvent, handler);
checkForm.Show();
// somehow wait here??
var elementEnd = GetSelectedElements(room);
// more stuff happening here
}

 And the new ExternalEventHandler class

 

public class SyncExternalEvent: IExternalEventHandler
{
public void Execute(UIApplication app)
{
TaskDialog.Show("Hello","Test");
}

public string GetName()
{
return "Handler to let user do changes on model.";
}
}

 

0 Likes
Message 6 of 9

RPTHOMAS108
Mentor
Mentor

I think that is possible and your task is not as complicated as I was thinking.

 

I think the only thing you are getting wrong is the idea of waiting and how that can be done in practice. I see the order of execution for all that more like below:

 

  1. Within IExternalApplication.OnStartUp register external event. It can be called at anytime via a static method. *Adding and removing external events outside of IExternalApplication.OnStartUp from my point of view is an unnecessary complication.
  2. Start your IExternalCommand that takes the first image and show the modeless dialogue
  3. IExternalCommand context ends and returns UI to user for them to modify the document
  4. <This is the waiting period>
  5. User presses Apply button and external event queued
  6. Revit responds to external event request and starts instance of your IExternalEventHandler
  7. Within IExternalEventHandler you can then close the modeless dialogue, take the second image and optionally make further changes to the document.

 

*I say this because you can register one single external event and it can be used for many different things in the same session by the add-in that registered it. So adding and removing external events elsewhere is like removing the plumbing from under a sink each time you empty it and putting it back when you need to use it next. It is all generic and abstract, not specific to a task,

 

 

 

 

 

0 Likes
Message 7 of 9

grubdex
Enthusiast
Enthusiast

So I guess this would mean that my form is also on the level of the ExternalApplication and not the ExternalCommand?

I guess it would also mean the first part of the workflow is part of my command (get snapshot, show modeless dialogue), the second part is part of a separate ExternalEventHandler that is not part of the ExternalCommand?


So I guess logic-wise, my app would still be open (even though the command has ended), and I then finish the rest of the command inside an externaleventhandler? Or would the ExternalEventHandler be called within another ExternalCommand?

0 Likes
Message 8 of 9

RPTHOMAS108
Mentor
Mentor

No your form is completely separate from IExternalApplication it triggers the event via a static method on IExternalApplication. Your form is also completely separate from IExternalCommand the initial context that shows it.

 

The second part of your statement is correct. In the end it is about how the user perceives your command not really about if it actually stopped and started. They perceive it as active because the form is still showing.

 

You could have the form in a static field within IExternalApplication but equally you could have the form stored in a static field within Form (or some other class). I don't think that detail is that important since it is opened by IExternalCommand. It is probably more conventional to add it as field in IExternalApplication.

 

In your case you can close the form within the apply button handler in the form (after requesting external event). If you need to close it from elsewhere then that is slightly different. The only purpose of the static form field in your case is to check the current status of the form in order to prevent it being opened more than once (to prevent opening during 4 above).

0 Likes
Message 9 of 9

Kennan.Chen
Advocate
Advocate

@grubdex wrote:

So I guess this would mean that my form is also on the level of the ExternalApplication and not the ExternalCommand?

I guess it would also mean the first part of the workflow is part of my command (get snapshot, show modeless dialogue), the second part is part of a separate ExternalEventHandler that is not part of the ExternalCommand?


So I guess logic-wise, my app would still be open (even though the command has ended), and I then finish the rest of the command inside an externaleventhandler? Or would the ExternalEventHandler be called within another ExternalCommand?


I will do the same thing. Separation is the key. The real business code is defined in an IExternalEventHandler, accepting a bunch of parameters from former step. Your UI is only responsible to collect those parameters and raise the IExternalEventHandler with them when the user clicks "Apply".