Skip to content

Generic MessageBox

tpecholt edited this page Jun 10, 2023 · 1 revision

Let's demonstrate some important features of ImRAD on a generic messageBox dialog.

For the purpose of this article generic message box is a simple modal dialog with title, message and a set of buttons. All can be configured. The invocation should look something like this:

messageBox.title = "Hello";
messageBox.message = "Move your breakfast to the trash bin?";
messageBox.buttons = ImRad::Yes | ImRad::No | ImRad::Cancel;
messageBox.OpenPopup([](ImRad::ModalResult mr) {
   ...
   });

Here we already use some useful types defined in imrad.h - button identifiers and ModalResult which is a dialog's analogy to the program exit code so we don't need to define our own types.

Let's create a new dialog file by pressing the New File / Modal Popup button. In ImGui modal popups are windows which when shown cause the background to dim out and effectively block any user interaction with other windows until the current popup is dismissed. Save it as message_box.h. ImRAD will generate message_box.cpp as well, implements window class named MessageBox and defines a global dialog instance called messageBox.

The dialog has two parts. Message text and a row of buttons. Both of them should be centered horizontally.

Selectable widget

First let's add the label widget. ImGui offers two widgets here - Text which is able of text wrapping but has no alignment options and Selectable which doesn't have text wrap but offers alignment control in both directions. Choose Selectable and set its horizAlignment property to AlignHCenter. The widget should be already expanded to the right edge of the window because of its size_x = 0 (in ImGui some other widgets use size_x = -1 to achieve expansion to the right edge of the parent window):



Because the label is configurable by the user we need to use property binding mechanism. In ImGui some properties such as Checkbox or Combo value use implicit two-way binding as the relevant ImGui call requires a pointer to the variable to be passed in. ImGui then takes care of updating that variable whenever the state changes and vice versa. Other properties are made one-way bindable (e.g. variable to widget state but not the other way round) by the ImRAD code generator.

You can activate property binding by pressing a small white rectangle on the right side of the property. This rectangle turns orange when binding is activated. Clicking that rectangle opens a Binding dialog:



Here we need to enter the binding expression for the text field. This is essentially a valid C++ expression which will be exported in the dialog code as is. In the case a target property is of a string type the binding expression takes a form of a format string which is similar to the C++20 std::format functionality but it requires mentioning the variable names directly in the substitution {} sub-expressions. That allows you to combine multiple values or format them.

Note: ImRad::Format doesn't currently interprets formatting flags in any way. To enable that download the popular fmt library and compile generated code in your app with IMRAD_WITH_FMT.

Support for std::format has not been added yet as that is still available only to fraction of users using the newest compilers.

.

As we just want to assign whatever was passed in by the user at the dialog invocation we create a new field called message. Double clicking that variable from the table will add it to the binding expression (which can also be edited manually).



The text widget is complete.

Button row

Next comes the button row and that is more tricky. Buttons don't have alignment property and it's also possible to have several of them shown at once not just the OK button. Buttons which are not selected should be hidden. In ImGui this is traditionally achieved by calculating the position of the buttons explicitly but a more convenient way is to employ the Table widget as a layout mechanism which is also often recommended by the ImGui creator.

Note: Soon ImRAD will implement a horizontal layout helper which will take care of setting up the table for you. Until then use following steps.

.

First insert a Table widget bellow the text. Don't worry about the header row we will turn it off later when table content is added.



Then configure the columns property so that first and last column are stretchable and the middle column is fixed. This will give us the centering effect.



Then we need to insert the buttons to the middle column. ImRAD currently doesn't support specifying column directly but there is a nextColumn property instead. Checking this property causes the widget and all subsequent widgets to be moved to the next column. So after all 4 buttons are inserted (in order OK, Yes, No, Cancel) set nextColumn property of the first button and set also sameLine properties in the subsequent buttons so that they will all appear on the same line. Set their size_x = 80. The result should look like this:



To show only the buttons selected by the user we use property binding one more time this time for the visible property. Activate the binding dialog and create a new int field called buttons. Alternatively create the field from the Class Wizard. Use that field in the visible binding expression so OK button will set visible=buttons & ImRad::Ok, Yes button will set visible=buttons & ImRad::Yes and so on. & here is a C bitwise-and operator which can be used to test if a selected flag is part of a flag set. Later when you inspect the generated code you will see that the button widgets are put inside if statement blocks depending on the binding expression added.

To complete the button functionality set their modalResult fields. Doing that will instruct ImRAD to generate code which will automatically close the dialog and send the modal result code in a callback. That will happen for all modal results except Cancel. In case of Cancel ImRAD generates additional code which ensures the button will also push when pressing the Escape key but callback will not be called as pushing Cancel is similar to closing the dialog through the X button which also doesn't cause a callback call. The button shortcut can be changed by editing its shortcut property.

Finally disable table's header row as well as table borders through the flags field. To create more space between the text and buttons increase spacing property of the table to 2.

Usage

Then go back to the main window and activate AlwaysAutoResize from the flags property so the dialog resizes as needed (depending on the label and buttons dimensions). Use property binding to create new title variable and set title property to {title}.



The dialog is more or less complete. To use the dialog in your app #include generated "message_box.h" and compile generated "message_box.cpp" alongside your project. After that call messageBox.Draw() from your ImGui drawing loop. The dialog invocation can be done as shown on top of this page.

When testing the dialog you will notice that for short messages the dialog may look too narrow. For now the minimal dialog width can be achieved by inserting a dummy widget with a desired minimal width. In the future window will have a size constraints property.

Have fun by using this message box in your ImGui application!

Note: windows developers may notice strange compile errors when they try to use this dialog from the main program source. The reason is #include <windows.h> defines bunch of macros which clash with your identifiers. MessageBox, min, max are prominent examples. You can #undef these macros after including as you are probably not going to use these...

Clone this wiki locally