In the previous part of this tutorial series we’ve learned how to create BIM elements in Archicad, but we used hard-wired values for settings. In this part we will create a user interface to allow users to define maze generation settings.
The code for this tutorial is available here.
The tutorial is divided into multiple parts:
- Part 1: Create elements, handle undo scope.
- Part 2: Create a dialog to manipulate the functionality.
- Part 3: Store dialog data in preferences.
The final dialog will look like this:
Dialog Framework (DG)
To create user interface in Archicad it is highly recommended to use Archicad’s own Dialog framework (DG). It has several advantages:
- Platform Independent: You have to write your code only once, and it will work on Windows and macOS platforms without any modification.
- Handles working units: All of the DG dialogs handle working unit settings, so your dialog will work for people from all around the world.
- Fits in Archicad: All dialogs in Archicad are written with DG, so they will provide a native-like user experience for Archicad users. You don’t have to care about formatting, they will just look beautiful.
Creating a New Dialog
Let’s see how to create a new modal dialog in Archicad. Basically it takes takes three steps:
- Define the resource for the dialog.
- Write the code for the dialog.
- Open the dialog, and use the results.
Define the Resource for the Dialog
The first step is to define the resource for the dialog. You can do it by using Archicad’s internal resource format: grc. This is a platform independent format for describing strings, dialogs, icons and other resources.
Our dialog’s resource will look like this:
'GDLG' ID_ADDON_DLG Modal 40 40 250 452 "Maze Settings" {
/* [ 1] */ Button 150 419 90 23 LargePlain "OK"
/* [ 2] */ Button 50 419 90 23 LargePlain "Cancel"
/* [ 3] */ Icon 15 10 220 160 10002
/* [ 4] */ LeftText 10 180 230 23 LargeBold vCenter "Grid Settings"
/* [ 5] */ LeftText 10 210 130 23 LargePlain vCenter "Number of Rows"
/* [ 6] */ PosIntEdit 150 210 90 23 LargePlain "1" "50"
/* [ 7] */ LeftText 10 240 130 23 LargePlain vCenter "Number of Columns"
/* [ 8] */ PosIntEdit 150 240 90 23 LargePlain "1" "50"
/* [ 9] */ LeftText 10 270 130 23 LargePlain vCenter "Cell Dimension"
/* [ 10] */ LengthEdit 150 270 90 23 LargePlain "1.00" "50.0"
/* [ 11] */ Separator 10 305 230 2
/* [ 12] */ LeftText 10 317 230 23 LargeBold vCenter "Options"
/* [ 13] */ CheckBox 10 347 230 23 LargePlain "Group placed elements"
/* [ 14] */ CheckBox 10 372 230 23 LargePlain "Place slab under walls"
/* [ 15] */ Separator 10 407 230 2
}
It looks a bit scary at first, but its structure is pretty straightforward.
The first line tells the compiler that it defines a dialog resource (GDLG
) for a modal dialog. It also specifies the position, the dimensions and the title of the dialog. After that every line defines a control. All of them specifies the position (top left corner), dimensions (width and height) and some control-dependent properties.
Here are some interesting parts:
- If the dialog has OK and Cancel buttons, they always have to be the first two items, so the default behavior for pressing enter and escape key works automatically when the dialog is open.
- For
Icon
controls you have to specify the ID of the icon. It usually refers to aGICN
resource in another, language-independent resource file. LeftText
is simply a plain static text control with left-aligned text.- The
PosIntEdit
control can accept only integers, and the minimum and maximum value can be specified as well. LengthEdit
is used to display a value referring to length. It is highly recommended to use this control when you work with length values, because it automatically handles unit conversion. The minimum and maximum value can be specified here, too.
For dialog resources there is always a connected resource, and it’s the help resource (DLGH
). The goal of the help resource is to define help anchors for the dialog. These help anchors are not used by Add-Ons at the moment, but they can also define the tooltip text for every control. In the example below every tooltip text is empty.
'DLGH' ID_ADDON_DLG DLG_Maze_Settings {
1 "" Button_0
2 "" Button_1
3 "" Icon_0
4 "" LeftText_0
5 "" LeftText_1
6 "" PosIntEdit_0
7 "" LeftText_2
8 "" PosIntEdit_1
9 "" LeftText_3
10 "" LengthEdit_0
11 "" Separator_0
12 "" LeftText_4
13 "" CheckBox_0
14 "" CheckBox_1
15 "" Separator_1
}
The Observer Pattern
The dialog framework uses the observer pattern. It means that observer classes can attach to event source classes, and they will get a notification in case of certain events. It’s possible to write dialog and observer classes separately, but in this tutorial we will do it in one class.
Write the Code for the Dialog
The first step is to create a structure to store everything that can be set via the dialog. The MazeSettings
class will store the row and column count and the size of maze cells. We also define two options: one is to group all of the elements, and the other is to place a slab under the elements.
class MazeSettings : public GS::Object
{
public:
MazeSettings (UInt32 rowCount, UInt32 columnCount, double cellSize, bool createGroup, bool createSlab);
~MazeSettings ();
UInt32 rowCount;
UInt32 columnCount;
double cellSize;
bool createGroup;
bool createSlab;
};
The next step is to write a class for the dialog. The dialog class will get the initial settings in the constructor, and it has only one public function that returns the new settings based on user input. The dialog class should be inherited from several classes:
ModalDialog
: It defines some functionalities needed for a modal dialog.PanelObserver
: It means that our dialog wants to respond to some panel events.ButtonItemObserver
: Same as above, our dialog wants to respond to some button events (specifically: pressing the OK or the Cancel button).CompoundItemObserver
: It’s a utility class to make it easy to attach all observers with one line.
It also defines some private class members for each control in the dialog, and of course it stores the current settings.
class MazeSettingsDialog : public DG::ModalDialog,
public DG::PanelObserver,
public DG::ButtonItemObserver,
public DG::CompoundItemObserver
{
public:
MazeSettingsDialog (const MazeSettings& mazeSettings);
~MazeSettingsDialog ();
const MazeSettings& GetMazeSettings () const;
private:
virtual void PanelOpened (const DG::PanelOpenEvent& ev) override;
virtual void PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool* accepted) override;
virtual void ButtonClicked (const DG::ButtonClickEvent& ev) override;
DG::Button okButton;
DG::Button cancelButton;
DG::PosIntEdit rowEdit;
DG::PosIntEdit columnEdit;
DG::LengthEdit cellSizeEdit;
DG::CheckBox groupElementsCheck;
DG::CheckBox placeSlabCheck;
MazeSettings mazeSettings;
};
Let’s see how it works. The constructor intializes all of the controls with their ids, stores the initial settings, and it attaches the class as an observer to certain event sources. The destructor detaches the class from event sources.
MazeSettingsDialog::MazeSettingsDialog (const MazeSettings& mazeSettings) :
DG::ModalDialog (ACAPI_GetOwnResModule (), MazeDialogResourceId, ACAPI_GetOwnResModule ()),
okButton (GetReference (), OKButtonId),
cancelButton (GetReference (), CancelButtonId),
rowEdit (GetReference (), RowEditId),
columnEdit (GetReference (), ColumnEditId),
cellSizeEdit (GetReference (), CellSizeEditId),
groupElementsCheck (GetReference (), GroupElementsCheckId),
placeSlabCheck (GetReference (), PlaceSlabCheckId),
mazeSettings (mazeSettings)
{
AttachToAllItems (*this);
Attach (*this);
}
MazeSettingsDialog::~MazeSettingsDialog ()
{
Detach (*this);
DetachFromAllItems (*this);
}
When the dialog is fully initialized, the PanelOpened
function will be called. This is the place to initialize controls. In this example the function sets every settings value into the corresponding dialog control.
void MazeSettingsDialog::PanelOpened (const DG::PanelOpenEvent&)
{
rowEdit.SetValue (mazeSettings.rowCount);
columnEdit.SetValue (mazeSettings.columnCount);
cellSizeEdit.SetValue (mazeSettings.cellSize);
groupElementsCheck.SetState (mazeSettings.createGroup);
placeSlabCheck.SetState (mazeSettings.createSlab);
}
When the user clicks on a button, the ButtonClicked
function will be executed. After clicking on the OK or the Cancel button, it will request to close the dialog with different return codes.
void MazeSettingsDialog::ButtonClicked (const DG::ButtonClickEvent& ev)
{
if (ev.GetSource () == &okButton) {
PostCloseRequest (DG::ModalDialog::Accept);
} else if (ev.GetSource () == &cancelButton) {
PostCloseRequest (DG::ModalDialog::Cancel);
}
}
Before the dialog closes, the PanelCloseRequested
function will be called. This is a good place to modify settings values based on the controls. We only have to do this if the event is accepted (the user clicked on OK).
void MazeSettingsDialog::PanelCloseRequested (const DG::PanelCloseRequestEvent& ev, bool*)
{
if (ev.IsAccepted ()) {
mazeSettings.rowCount = rowEdit.GetValue ();
mazeSettings.columnCount = columnEdit.GetValue ();
mazeSettings.cellSize = cellSizeEdit.GetValue ();
mazeSettings.createGroup = groupElementsCheck.IsChecked ();
mazeSettings.createSlab = placeSlabCheck.IsChecked ();
}
}
Open the Dialog
Now we have all the code for the dialog so we can use it. Opening a modal dialog is very simple. We just have to create an instance, call the Invoke
method, and check if the user clicked OK or Cancel.
The function below opens the dialog, and stores the settings in an output parameter. If the dialog is closed by pressing OK it will return true, otherwise it will return false. It also defines the initial settings for the dialog so it will be filled with valid values after it is opened.
static bool GetMazeSettingsFromDialog (MazeSettings& mazeSettings)
{
MazeSettings initialMazeSettings (10, 20, 1.0, true, true);
MazeSettingsDialog mazeSettingsDialog (initialMazeSettings);
if (mazeSettingsDialog.Invoke ()) {
mazeSettings = mazeSettingsDialog.GetMazeSettings ();
return true;
} else {
return false;
}
}
Summary
This was only the top of the iceberg. Dialog creation has much more possibilities, like creating resizable, dockable palettes, extend existing dialogs and a lot more. You can find more details in the documentation that comes with the API Development Kit.
You can find more resources here:
- Find out more details from Archicad API Reference.
- If you are feeling stuck, you can ask your questions in our Developer Forum.
- For some more tutorials check the Add-On Developer Blog.
- Follow us on Twitter to get the latest news.