Programming Gambas from Zip/FormArranging
Making Controls Expand When a Form is Resized[edit | edit source]
Gridviews and TableViews are often made to stretch and shrink when the window they are in is resized. A form, which is a window, knows how to resize and arrange the controls that are in it. Even when the form opens any controls that can be arranged and expanded will be.
Make a small form with one TableView.
Set the arrangement property of the form to Vertical. Set the expand property of the tableview to True. Run the program (f5). Change the size of the window and notice how the size of the tableview changes with it.
Adjust the padding property of the form to 8. Run the program again (F5). The space between the tableview and the edge of the form has increased. The margin inside the form is the padding. Table cells also have padding.
Add a button under the tableview. Run the program again. Move the button to one side of the tableview. Again run the program. Change the arrangement property to Horizontal and try again. Change it back to Vertical.
Delete the button. Add a HBox. Add a button to the HBox. (Alternately, Right-click the button and choose “Embed in a container”, then right-click the container—which is a panel—and Change into... a HBox.)
Controls in a HBox are arranged horizontally.
The spring will push the button to the right as far as it will go. Experiment with and without the spring.
If you make the button wider or narrower it stays whatever width you give it in the HBox.
The button expands vertically to fill the HBox from top to bottom. Change HBox’s height and see. HBoxes expand their controls to fill their height. In panels the button stays the same height but you cannot use springs.
A Spreadsheet to Average Student Marks[edit | edit source]
tv1 is a tableview. Its expand property is set to True.
At the bottom is a HBox. Inside, from left to right, is a label labC, a boldface label labAverage, a spring, and a Quit button called bQuit whose text property is “Quit”.
As each number is entered the average is recalculated. Blank cells are skipped.
LabC shows the count and labAverage the average.
Public Names As New String Public Scores As New Float Public Sub bQuit_Click() Quit End Public Sub Form_Open() Names = ["Mereka AIKE", "Ernest AIRI", "John AME", "Stanley ANTHONY", "Natasha AUA", "Veronica AUFA", "John Taylor BUNA", "Romrick CLEMENT", "Philomena GAVIA", "Richard GHAM"] tv1.Rows.Count = Names.count Scores.Resize(Names.Count) tv1.Columns.Count = 2 tv1.Columns.Alignment = Align.Right End Public Sub CalculateAverage() Dim i, n As Integer Dim t As Float For i = 0 To Scores.Max If Scores[i] = -1 Or IsNull(tv1[i, 1].text) Then Continue 'skip new but unfilled-in lines n += 1 'number of scores t += Scores[i] 'total Next If n = 0 Then Return labC.Text = "N= " & n labAverage.Text = "Avg= " & Format(t / n, "#0.00") End Public Sub tv1_Click() If tv1.Column = 1 Then tv1.Edit 'numbers column is editable; Enter goes down End Public Sub tv1_Activate() 'double-clicked a cell If tv1.Column = 0 Then tv1.Edit 'edit a name End Public Sub tv1_Save(Row As Integer, Column As Integer, Value As String) tv1[Row, Column].text = Value If Column = 1 Then Scores[Row] = Value CalculateAverage Else Names[Row] = Value Endif End Public Sub tv1_Data(Row As Integer, Column As Integer) If Column = 0 Then tv1[row, 0].text = Names[Row] If Row Mod 2 = 0 Then tv1[Row, Column].Background = &hDDDDFF 'light blue tv1.Columns.Width = -1 'Automatically set width based on contents End Public Sub tv1_Insert() tv1.Rows.Count += 1 Scores.Add(-1) Names.Add("") tv1.MoveTo(tv1.Rows.max, 0) tv1.Edit End
Right at the start two public arrays are created. Names is a list of the student names. Scores is a list of their results. They match: the first mark goes with the first name, the second mark with the second name, and so on.
The tv1_Data(Row As Integer, Column As Integer) event fires every time a cell needs to be redrawn. It supplies you with Row and Column. It can be thought of as painting the cell.
There is a special consideration with the _DATA event: it does not paint cells that it doesn’t need to. This is great for displaying large numbers of lines. If there are 100,000 lines and you are only showing 15 of them, only the cells on those 15 lines fire the _DATA event, not all hundred thousand. Be careful if you used the _DATA event to put the numbers into the cells! There may be data in only 15 of those 100,000 cells. Here there is no problem, because we are typing the numbers ourselves and every time we finish typing a new number it is put into the cell in the _SAVE event. (tv1[Row, Column].text = Value). When we come to putting values in using the _DATA event from a database, though, we shall only put data into the cells we see. Then we have to remember to do calculations on the internally-held data, not on the displayed contents of cells. To get into good habits, I have used the DATA[ ] array to hold the scores, and this is used in the calculation of averages. If comes down to this: if you are sure all the data is in the cells, use them; if not, use the data where you know for sure it is.
The following lines create a contextual menu for the tableview with four entries:
Public Sub tv1_Menu() Dim mn, su As Menu 'main menu and submenu mn = New Menu(Me) 'brackets contain the parent, the main window su = New Menu(mn) As "MenuCopyTable" 'submenu of mn; alias is MenuCopyTable su.Text = "Copy table..." 'first submenu's text su = New Menu(mn) As "MenuCopyNames" su.Text = "Copy names..." 'second submenu's text su = New Menu(mn) As "MenuDeleteRow" su.Text = "Delete Row" 'third submenu's text su = New Menu(mn) As "MenuRefresh" su.Text = "Refresh" 'fourth submenu's text mn.Popup End Public Sub MenuDeleteRow_Click() Names.Remove(tv1.Row) Scores.Remove(tv1.Row) tv1.Rows.Remove(tv1.Row) End Public Sub MenuCopyTable_Click() 'clicked the Copy Table menu item Dim z As String For i As Integer = 0 To Names.Max If Scores[i] = -1 Then Continue z = If(IsNull(z), "", z & gb.NewLine) & Names[i] & gb.Tab & Scores[i] Next Clipboard.Copy(z) Message("Table copied") End Public Sub MenuCopyNames_Click() 'clicked the Copy Names menu item Dim z As String For i As Integer = 0 To Names.Max If IsNull(Names[i]) Then Continue z = If(IsNull(z), "", z & gb.NewLine) & Names[i] Next Clipboard.Copy(z) Message("Names copied") End Public Sub MenuRefresh_Click() tv1.Clear 'clear the data tv1.Rows.Count = Names.Count 'reset number of rows to match Names[ ] For i As Integer = 0 To Names.Max tv1[i, 0].Text = Names[i] tv1[i, 1].Text = Scores[i] If i Mod 2 = 0 Then tv1[i, 0].Background = &hFFDDFF tv1[i, 1].Background = &hFFDDFF Endif Next End
The _Menu() event belongs to tv1, the tableview. This event fires when the object is right-clicked (to show a menu). The _Click() event belongs to TableviewMenu. What is that? It is the alias by which the menu su is known. Aliases make one think of secretive men in dark trenchcoats, but it is just the name by which it is known.
mn and su only exist for the duration of the popup menu because they are in the _Menu() event. As soon as you click any item on the popup menu, that sub finishes and mn and su disappear. Luckily, we have said that su is also known as TableviewMenu. The menu itself, when it was created with New, has that name. So any clicks the menu gets are handled by TableviewMenu_Click().
Menus—whether main menus or submenus or menu items— have several events, methods and properties. See Gambas help for:
You can tell a menu to Close, Hide, Popup, Show or Delete.
You can tell the program to do things when the menu is clicked, after it hides or just before it shows. Usually it is when the menu is clicked that the menu gets to work.
Some properties are boolean (e.g. enabled, checked and visible), others are strings (e.g. text, name), pictures (picture) or variants, which are any type (tag).