aboutsummaryrefslogtreecommitdiffstats
path: root/pages/develop/tutorials/csharp/texteditor-cs.md.txt
blob: 5f73ea6f52e14625f967feb7e76f8e0be580ef13 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
---
~~Title: Graphical "Hello World" in C#~~
~~NOCACHE~~
---

# A Simple Text Editor in C# #

In this tutorial you will learn how to build a simple text editor application, including:

* A toolbar with buttons
* A text entry area
* File saving and loading capabilities
* Message popups

|     | WARNING |     |
| --- | ------- | --- |
| ![NOTE](/_media/note-important.png) | **The C# bindings are currently in BETA state**<br>They should only be used for experimenting and **NOT** for any product development.<br>The source code for the tutorials is subject to change in the future. | ![NOTE](/_media/note-important.png) |

## Prerequisites ##

* Read the [Setting Up a C# Development Environment](/develop/setup/csharp/) guide so you are able to build and run EFL C# applications.
* This tutorial builds on top of the previous one, [Graphical "Hello World" in C#](hello-world-gui-cs.md), so you should have read that one first.

## The Code ##

Copy the code below into a file and build it as instructed in the [Setting Up a C# Development Environment](/develop/setup/csharp/) guide. You can also find this file in the [EFL examples repository](https://git.enlightenment.org/tools/examples.git/) in [`apps/csharp/texteditor`](https://git.enlightenment.org/tools/examples.git/tree/apps/csharp/texteditor).

```csharp
/* Simple text editor with a main text box and a toolbar on top:
   +vbox----------------------------------------+
   | +hbox------------------------------------+ |
   | | +btn-+ +btn-+ +btn-+ +box-----+ +btn-+ | |
   | | |NEW | |SAVE| |LOAD| | spacer | |QUIT| | |
   | | +----+ +----+ +----+ +--------+ +----+ | |
   | +----------------------------------------+ |
   | +text------------------------------------+ |
   | |                                        | |
   | |                                        | |
   | |         Main text box                  | |
   | |                                        | |
   | |                                        | |
   | +----------------------------------------+ |
   +--------------------------------------------+
*/

using System;

public class TextEditor
{
    private efl.ui.IWin win;                  // The main window
    private efl.ui.IText editorTextBox;       // The main text entry
    private efl.ui.IButton toolbarButtonNew;  // The "New" button in the toolbar
    private efl.ui.IButton toolbarButtonSave; // The "Save" button in the toolbar
    private efl.ui.IButton toolbarButtonLoad; // The "Load" button in the toolbar

    private bool edited = false;              // Document was edited since last save

    // File to load and save is fixed since we do not use a file selection dialog
    private readonly string filename = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
                                                              "texteditor_example.txt");

    // Quits the application
    private void GUIQuitCb(object sender, EventArgs ea)
    {
        efl.ui.Config.Exit();
    }

    // Enables or disables buttons on the toolbar as required
    private void GUIToolbarRefresh()
    {
        // "New" is enabled if there is text in the text box
        toolbarButtonNew.SetDisabled(string.IsNullOrEmpty(editorTextBox.GetText()));
        // "Save" is enabled if the text has been modified since last save or load
        toolbarButtonSave.SetDisabled(!edited);
        // "Load" is enabled if there is a file to load
        toolbarButtonLoad.SetDisabled(!System.IO.File.Exists(filename));
    }

    // Called when the text in the editor has changed
    private void EditorChangedCb(object sender, EventArgs ea)
    {
        edited = true;
        GUIToolbarRefresh();
    }

    // Shows a modal message popup with an "OK" button
    private void ShowMessage(string message)
    {
        new efl.ui.Popup_Alert_Text (win, (efl.ui.IPopup_Alert_Text epopup) => {
          epopup.SetText(message);
          epopup.SetExpandable(new eina.Size2D(200,200));
          epopup.SetButton(efl.ui.Popup_Alert_Button.Positive, "OK", null);
          epopup.ButtonClickedEvt +=
              (object sender, efl.ui.Popup_Alert.ButtonClickedEvt_Args ea) => {
            // Dismiss popup when the button is clicked
            ((efl.ui.IPopup_Alert_Text)sender).SetParent(null);
          };
        });
    }

    // Adds a button to the toolbar, with the given text, icon and click event handler
    private efl.ui.IButton GUIToolbarButtonAdd(efl.ui.IBox toolbar, string name,
                                               string iconName, EventHandler func)
    {
        return new efl.ui.Button(toolbar, (efl.ui.IButton ebutton) => {
          ebutton.SetText(name);
          ebutton.ClickedEvt += func;
          ebutton.SetHintWeight(0, 1);
          toolbar.DoPack(ebutton);

          // Set the content of the button
          efl.Content.static_cast(ebutton.GetPart("efl.content")).SetContent(
            // Which is an image
            new efl.ui.Image(toolbar, (efl.ui.IImage eimage) => {
              eimage.SetIcon(iconName);
            })
          );
        });
    }

    // Creates a new toolbar, with all its buttons
    private void GUIToolbarSetup(efl.ui.IBox parent)
    {
        // Create a horizontal box container for the buttons
        efl.ui.IBox bar = new efl.ui.Box(parent, (efl.ui.IBox ebox) => {
          // 0 vertical weight means that the toolbar will have the minimum height
          // to accommodate all its buttons and not a pixel more. The rest of the
          // space will be given to the other object in the parent container.
          ebox.SetHintWeight(1, 0);
          ebox.SetDirection(efl.ui.Dir.Horizontal);
          parent.DoPack(ebox);
        });

        // "New" button
        toolbarButtonNew = GUIToolbarButtonAdd(bar, "New", "document-new",
          (object sender, EventArgs ea) => {
              // When this button is clicked, remove content and refresh toolbar
              editorTextBox.SetText("");
              GUIToolbarRefresh();
          });

        // "Save" button
        toolbarButtonSave = GUIToolbarButtonAdd(bar, "Save", "document-save",
          (object sender, EventArgs ea) => {
              // When this button is clicked, try to save content and refresh toolbar
              try {
                System.IO.File.WriteAllText(filename, editorTextBox.GetText());
                edited = false;
                GUIToolbarRefresh();
                ShowMessage("Saved!");
              } catch (Exception e) {
                // If something fails, show the error message
                ShowMessage(e.Message);
              }
          });

        // "Load" button
        toolbarButtonLoad = GUIToolbarButtonAdd(bar, "Load", "document-open",
          (object sender, EventArgs ea) => {
              // When this button is clicked, try to load content and refresh toolbar
              try {
                editorTextBox.SetText(System.IO.File.ReadAllText(filename));
                edited = false;
                GUIToolbarRefresh();
                ShowMessage("Loaded!");
              } catch (Exception e) {
                // If something fails, show the error message
                ShowMessage(e.Message);
              }
          });

        // Spacer box to use all available space not required by buttons
        // (It has a default horizontal weight of 1, whereas all buttons have
        // a horizontal weight of 0).
        // As a result, it pushes the "Quit" button to the right margin and
        // the rest to the left.
        efl.ui.IBox box = new efl.ui.Box(parent);
        bar.DoPack(box);

        // "Quit" button
        GUIToolbarButtonAdd(bar, "Quit", "application-exit", GUIQuitCb);
    }

    // Builds the user interface for the text editor
    public TextEditor()
    {
        // Create a window and initialize it
        win = new efl.ui.Win(efl.App.GetLoopMain(), (efl.ui.IWin ewin) => {
            ewin.SetText("Text Editor");
            ewin.SetAutohide(true);
            ewin.HideEvt += GUIQuitCb;
        });

        // Create a vertical box container
        efl.ui.IBox box = new efl.ui.Box(win);
        win.SetContent(box);

        // Create the toolbar and add it to the box
        GUIToolbarSetup(box);

        // Create the main text entry
        editorTextBox = new efl.ui.Text(box, (efl.ui.IText etext) => {
          etext.SetFont("Mono", 14);
          etext.SetMultiline(true);
          etext.SetEditable(true);
          etext.SetScrollable(true);
          etext.SetHintMin(new eina.Size2D(360, 240));
          etext.ChangedEvt += EditorChangedCb;
          etext.ChangedUserEvt += EditorChangedCb;
          box.DoPack(etext);
        });

        // Initial refresh of the toolbar buttons
        GUIToolbarRefresh();
    }

    // This method won't return until the application quits
    public void Run()
    {
        // Start the EFL main loop
        efl.ui.Config.Run();
    }
}

public class Example
{
#if WIN32
    [STAThreadAttribute()]
#endif
    public static void Main()
    {
        // Initialize EFL and all UI components
        efl.All.Init(efl.Components.Ui);

        var textEditor = new TextEditor();
        textEditor.Run();

        // Shutdown EFL
        efl.All.Shutdown();
    }
}
```

When you run the application, it should look something like this:

![Text Editor application](/_media/playground/texteditor-app/image01.png)

## Walkthrough ##

This tutorial builds on top of the previous one, [Graphical "Hello World" in C#](hello-world-gui-cs.md), so you should be familiar with the basics of creating an EFL application and adding widgets to it. The following sections explain how to build this particular user interface and how to react to the different events.

You can use the navigation menu on the side to jump to the section you are most interested in.

### The User Interface Structure ###

A rough sketch of the application's user interface structure is depicted in the code's opening comment:

```
   +vbox----------------------------------------+
   | +hbox------------------------------------+ |
   | | +btn-+ +btn-+ +btn-+ +box-----+ +btn-+ | |
   | | |NEW | |SAVE| |LOAD| | spacer | |QUIT| | |
   | | +----+ +----+ +----+ +--------+ +----+ | |
   | +----------------------------------------+ |
   | +text------------------------------------+ |
   | |                                        | |
   | |                                        | |
   | |         Main text box                  | |
   | |                                        | |
   | |                                        | |
   | +----------------------------------------+ |
   +--------------------------------------------+
```

The main widget is a vertical container box (`vbox`) with two children: the toolbar and the main text entry.

The toolbar is a horizontal container box (`hbox`) full of buttons. If you are looking at the final image above, bear in mind that the toolbar itself is invisible (as are all box containers); only the buttons are visible.

In between the toolbar buttons there's an empty container box (also invisible) to act as a separator: it uses up all available space, effectively pushing buttons on either side to the far ends of the container. See [The Space Box](#The_Spacer_Box) section for details.

Each time the content of the text box changes, the toolbar is refreshed to enable or disable some of the buttons. For example, the Save button is only enabled (clickable) if there is text to save (i.e. the text box is not empty). See the [Refreshing the Toolbar](#Refreshing_the_Toolbar) section for details.

To keep this example simple a file selection dialog has not been used. Therefore, file saving and loading actions always act on the same file, located in the system's temporary folder. For example: `/tmp/texteditor_example.txt`.

### The Main Method ###

```csharp
    public static void Main()
    {
        // Initialize EFL and all UI components
        efl.All.Init(efl.Components.Ui);

        var textEditor = new TextEditor();
        textEditor.Run();

        // Shutdown EFL
        efl.All.Shutdown();
    }
```

The Main method simply initializes EFL, instantiates a `TextEditor` object and relinquishes control to it. The bulk of the work is done by the `TextEditor` class defined in the example. When `texteditor.Run()` returns EFL is shut down and the application finishes.

`texteditor.Run()` contains the call to `efl.ui.Config.Run()` which will block until EFL is instructed to quit using `efl.ui.Config.Exit()`, as seen in the [Graphical "Hello World" in C#](hello-world-gui-cs.md) tutorial.

This structure is not strictly necessary for such a brief example, but it mimics what a more complex application would normally do.

### Building the Text Editor ###

The constructor for the `TextEditor` class builds the User Interface sketched above. The process is split in this tutorial into different sections for clarity.

First, a window is created as explained in previous tutorials:

```csharp
    public TextEditor()
    {
        // Create a window and initialize it
        win = new efl.ui.Win(efl.App.GetLoopMain(), (efl.ui.IWin ewin) => {
            ewin.SetText("Text Editor");
            ewin.SetAutohide(true);
            ewin.HideEvt += GUIQuitCb;
        });
```

Then the vertical container box is created and set as the content for the window:


```csharp
        // Create a vertical box container
        efl.ui.IBox box = new efl.ui.Box(win);
        win.SetContent(box);
```

The toolbar containing all the buttons is created and added to the box (explained in the next section):

```csharp
        // Create the toolbar and add it to the box
        GUIToolbarSetup(box);
```

Finally, the text entry area is added. This is the main feature of the text editor, so it is worth describing in more detail:

```csharp
        // Create the main text entry
        editorTextBox = new efl.ui.Text(box, (efl.ui.IText etext) => {
          etext.SetFont("Mono", 14);
          etext.SetMultiline(true);
          etext.SetEditable(true);
          etext.SetScrollable(true);
          etext.SetHintMin(new eina.Size2D(360, 240));
          etext.ChangedEvt += EditorChangedCb;
          etext.ChangedUserEvt += EditorChangedCb;
          box.DoPack(etext);
        });
```

* `efl.ui.Text.SetFont()` sets the font family and size for the text area. The font string has to follow `fontconfig`'s convention for naming fonts, as it is the underlying library used to query system fonts (see the output of the `fc-list` command). Alternatively, you can use the full path to a font file.

* `efl.ui.Text.SetMultiline()` selects whether the text area will support more than one line of text (`true` is customary for a text editor).

* `efl.ui.Text.SetEditable()` sets whether the text can be modified by the user or it is only meant for output. If set to `true` all editing facilities (like keyboard input handling and caret positioning) will be enabled.

* `efl.ui.SetScrollable()` selects whether the text area should automatically use scroll bars to accommodate text longer than can be shown in the window. If enabled, the caret will always be kept visible when typing. If disabled, when typing beyond the edge of the window you won't see the new characters.

* `efl.ui.SetHintMin()` sets a minimum size for the text area, as seen in previous tutorials.

Furthermore, handlers are installed for two events: `ChangedUserEvt` is triggered when the text content changes in response to user actions (for example, a key has been pressed). `ChangedEvt` is triggered when the text content changes for any other reason (both events are never triggered together).

This example uses the same handler for both events (`EditorChangedCb()`), which only records the fact that the text has been edited in an internal variable and refreshes the toolbar.

Note how only two objects have been added to the vertical box: the toolbar and the text area. If no further configuration is done these two objects will share the vertical space in the box at 50% each, creating a very tall toolbar. The configuration of the toolbar prevents this, as shown in the next section.

The last step when building the User Interface is to refresh the toolbar, so the buttons reflect their initial states (explained in the [Refreshing the Toolbar](#Refreshing_the_Toolbar) section):

```csharp
        // Initial refresh of the toolbar buttons
        GUIToolbarRefresh();
    }
```

The user interface has now been built and is ready to be shown once the EFL main loop is executed (by calling `efl.ui.Config.Run()` in `TextEditor.Run()`).

The following sections describe each part of the user interface in more detail .

### Building the Toolbar ###

This is done in the `GUIToolbarSetup()` method:

```csharp
    private void GUIToolbarSetup(efl.ui.IBox parent)
    {
        // Create a horizontal box container for the buttons
        efl.ui.IBox bar = new efl.ui.Box(parent, (efl.ui.IBox ebox) => {
          ebox.SetHintWeight(1, 0);
          ebox.SetDirection(efl.ui.Dir.Horizontal);
          parent.DoPack(ebox);
        });

        [... Buttons are added to the toolbar ...]
    }
```

As it can be seen, the toolbar is just a regular horizontal box container. Please note the `SetHintWeight(1, 0)` line: Giving a widget a weight of 0 means that its parent container will allocate only the minimum room to fit it. The rest of the space will be used by the other children of the container, in this case, the text area.

The rest of this method adds the different buttons by using the helper function `GUIToolbarButtonAdd()` described below.

### Adding a Button to the Toolbar ###

Let's examine this very compact method to create and customize a button, while adding it to a parent container:

```csharp
    private efl.ui.IButton GUIToolbarButtonAdd(efl.ui.IBox toolbar, string name,
                                               string iconName, EventHandler func)
    {
        return new efl.ui.Button(toolbar, (efl.ui.IButton ebutton) => {
          ebutton.SetText(name);
          ebutton.ClickedEvt += func;
          ebutton.SetHintWeight(0, 1);
          toolbar.DoPack(ebutton);

          // Set the content of the button
          efl.Content.static_cast(ebutton.GetPart("efl.content")).SetContent(
            // Which is an image
            new efl.ui.Image(toolbar, (efl.ui.IImage eimage) => {
              eimage.SetIcon(iconName);
            })
          );
        });
    }
```

The function contains a single statement, which instantiates an `efl.ui.Button` (as a child of the toolbar) and returns it.

The rest of the work is done in the initialization method:

* The button's label is set with `SetText()`

* The method to call when the button is clicked is set by installing an event handler for the `ClickedEvt` event.

* The button is given a horizontal weight of 0, so its parent (the toolbar) will allocate for it the minimum space.

* The button is added to the toolbar with `DoPack()`.

* Finally, the requested icon is added to the button. Like many other EFL widgets, Buttons are highly customizable. In fact, this example uses an image but any widget (or combination of widgets) could be put inside the Button.

  The price for this flexibility is a bit more setup work. Please read the [Parts tutorial](parts.md) to learn more about this topic (*coming soon*).

### The "New" Button ###

This is how the "New" button is created in the `GUIToolbarSetup()` method:

```csharp
        // "New" button
        toolbarButtonNew = GUIToolbarButtonAdd(bar, "New", "document-new",
          (object sender, EventArgs ea) => {
              // When this button is clicked, remove content and refresh toolbar
              editorTextBox.SetText("");
              GUIToolbarRefresh();
          });
```

Its label is `"New"` and it uses the icon named `"document-new"`. When clicked, it will simply empty the `editorTextBox` and refresh the toolbar.

### The "Save" Button ###

The "Save" button instantiation is similar to that of the "New" button:

```csharp
        // "Save" button
        toolbarButtonSave = GUIToolbarButtonAdd(bar, "Save", "document-save",
          (object sender, EventArgs ea) => {
              // When this button is clicked, try to save content and refresh toolbar
              try {
                System.IO.File.WriteAllText(filename, editorTextBox.GetText());
                edited = false;
                GUIToolbarRefresh();
                ShowMessage("Saved!");
              } catch (Exception e) {
                // If something fails, show the error message
                ShowMessage(e.Message);
              }
          });
```

The clicked event handler does a bit more work, though:

* It performs file operations, so the body of the handler is enclosed in a `try {} catch` clause. In case an exception is thrown its error message is shown using a popup (see the [Showing a Popup Message](#Showing_a_Popup_Message) section).

* The whole content of the `editorTextBox` is retrieved with `GetText()` and written to a file:
  ```csharp
  System.IO.File.WriteAllText(filename, editorTextBox.GetText());
  ```
  `filename` is a read-only variable in this example, for simplicity.

* Once the file has been written, the `edited` flag is set to `false` to indicate that there are no pending changes to commit to disk. This has the effect to disable the "Save" button as shown in the [Refreshing the Toolbar](#Refreshing_the_Toolbar) section. The toolbar is manually refreshed by calling `GUIToolbarRefresh()` so this change is visible immediately.

* A popup is shown to indicate that the operation succeeded (see the [Showing a Popup Message](#Showing_a_Popup_Message) section).

### The "Load" Button ###

The "Load" button is very similar to the "Save" one:

```csharp
        // "Load" button
        toolbarButtonLoad = GUIToolbarButtonAdd(bar, "Load", "document-open",
          (object sender, EventArgs ea) => {
              // When this button is clicked, try to load content and refresh toolbar
              try {
                editorTextBox.SetText(System.IO.File.ReadAllText(filename));
                edited = false;
                GUIToolbarRefresh();
                ShowMessage("Loaded!");
              } catch (Exception e) {
                // If something fails, show the error message
                ShowMessage(e.Message);
              }
          });
```

The operation is the opposite of the "Save" button: the text is read from the file and set as the content of the `editorTextBox` with `SetText()`.

The `edited` flag is also updated, the toolbar refreshed and a popup message is shown.

### The Spacer Box ###

It is more or less traditional that the "Quit" button is located on the right-hand side of the window (think of the `X` button on the top-right corner of every window on most desktops) whereas the rest of the buttons (the "Command" buttons) are stacked on the left side.

A simple mechanism to achieve this in EFL is to insert a **spacer** widget between the command and the quit buttons which uses up all available space, pushing all buttons to either side of the spacer as far as they can go.

Any widget can act as an spacer, the only thing required of it is that it uses up space. To reduce complexity and unwanted side effects, an empty container widget is ideal for the job. For example, a `Box`:

```csharp
        efl.ui.IBox box = new efl.ui.Box(parent);
        bar.DoPack(box);
```

To ensure that all available space is given to the spacer and not distributed evenly among all sibling buttons, the spacer is given a horizontal weight of 1 (it's the default value, so there is no need to use `SetHintWeight()`) whereas the rest of the buttons have a horizontal weight of 0 (as seen in the [Adding a Button to the Toolbar](#Adding_a_Button_to_the_Toolbar) section).

How a container widget distributes available space among its children is a complex topic explained in more detail in a different tutorial (*coming soon*).

### Showing a Popup Message ###

Applications typically convey important informational or error messages through "modal" popup dialogs, which block the application until acknowledged by the user. This would be an example:

![Popup message](/_media/playground/texteditor-app/image02.png)

To achieve this EFL uses the `efl.ui.Popup` class and its derivatives, for instance, the `efl.ui.Popup_Alert_Text` used in this tutorial. This class implements a popup that contains text and optional configurable buttons.

This is how it's used in this tutorial, in the `ShowMessage()` method:

```csharp
    // Shows a modal message popup with an "OK" button
    private void ShowMessage(string message)
    {
        new efl.ui.Popup_Alert_Text (win, (efl.ui.IPopup_Alert_Text epopup) => {
          epopup.SetText(message);
          epopup.SetExpandable(new eina.Size2D(200,200));
          epopup.SetButton(efl.ui.Popup_Alert_Button.Positive, "OK", null);
          epopup.ButtonClickedEvt +=
              (object sender, efl.ui.Popup_Alert.ButtonClickedEvt_Args ea) => {
            // Dismiss popup when the button is clicked
            ((efl.ui.IPopup_Alert_Text)sender).SetParent(null);
          };
        });
    }
```

* The main text is set with `SetText()`.

* By default the popup has the width required to display all buttons and a fixed height. If the text does not fit in that area a vertical scroll bar is shown, which can be inconvenient when there's room enough in the window to make a bigger popup dialog. `SetExpandable()` allows extending the popup size beyond its default size if required, so that no scroll bars are needed when the text is large.

* `SetButton()` customizes the optional buttons of the popup. Alert popups can have 3 buttons: one for Positive answers, one for Negative answers and one that is user-defined. `SetButton()` changes the text and icon of each of these buttons, which are invisible by default until `SetButton()` is called.

* When any of the buttons are pressed, the `ButtonClickedEvt` event is emitted. The event argument specifies which button has been pressed, in case there's more than one button enabled (in this example there is no need to check the event argument because there is only one button).

* In this tutorial pressing the "OK" button dismisses the popup, which is done by removing the widget in the `ButtonClickedEvt` event handler. To delete a widget, the reference held by its parent must be removed using `SetParent(null)`.

### Refreshing the Toolbar ###

Command buttons do not need to be always available. When a certain action cannot be performed its button can be disabled with `SetDisabled()`, graying it out and making it un-clickable. The `GUIToolbarRefresh()` enables or disables each command button based on the internal state of the editor:

```csharp
    private void GUIToolbarRefresh()
    {
        // "New" is enabled if there is text in the text box
        toolbarButtonNew.SetDisabled(string.IsNullOrEmpty(editorTextBox.GetText()));
        // "Save" is enabled if the text has been modified since last save or load
        toolbarButtonSave.SetDisabled(!edited);
        // "Load" is enabled if there is a file to load
        toolbarButtonLoad.SetDisabled(!System.IO.File.Exists(filename));
    }
```

* The "New" button clears the content of the editor, therefore, it only makes sense when the editor is not already empty.

* The "Save" button is only enabled when the content of the editor has been changed. The internal variable `edited` is set to `true` in the `EditorChangedCb` event handler and to `false` whenever the file is saved or loaded.

* The "Load" button is only available when the file to load, which is always the same in this example, exists.

`GUIToolbarRefresh()` is called whenever the internal state of the editor changes to ensure that the user interface reflects it.

## Summary ##

At the end of this tutorial you have learned:

* How to create **complex user interfaces** by nesting widget containers.
* How to create a **toolbar** with buttons located in specific positions.
* How to **set and retrieve** the content of a text entry, for example, to save and load it from a file.
* How to use **message popups**.

## Further Reading ##

[Setting up a C# Development Environment](/develop/setup/csharp/)
:    Read this before trying to develop with the EFL and C#

[Graphical "Hello World" in C#](hello-world-gui-cs.md)
:    Explains the basics of creating an EFL application and its user interface.

[Tutorial Code Examples](https://git.enlightenment.org/tools/examples.git/tree/apps/csharp/texteditor)
:    C# Source code for this tutorial.