This is a short post to help with questionnaire type applications, specifically:
- how to align checkboxes to the top of the text?
- how do you avoid accidentally selecting questions when scrolling?
- how to increase touch sensitivity?
I find the built in list controls a little problematic in this regard, and the checkbox itself is not too flexible. FMX, however, is very flexible.
An Explanation of Method
Essentially, this is what we are trying to achieve:
There is a base layout, its hittest property is set to false so scrolling gestures pass through to the parent control. It is also aligned to top, so we can stack it vertically.
Next we create a second layout, aligned to the left, and we set its width to 50, and we add it to our base layout. This creates a column divide for us. We set its hittest to true so we can capture the tap and click events to toggle our checkbox.
The checkbox is added to the second layout, positioned to the top left as desired, and we set its hittest to true. This allows the checkbox to look more consistent with the underlying platform, and to participate in any related special effects. For example, the highlighting on hover in Windows 10:
Lastly we add the label to the base control, it too has its hittest property set to false.
This effectively gives us two columns, one for selecting the checkbox – a wide area for touch – and the much larger column containing the question text and used for scrolling. We can easily toggle the checkbox, and smoothly scroll the questions without risk of accidentally setting the checkbox.
One of the problems with using a multi-line label, is that it is difficult to avoid a line beginning with a space on a word wrap. I find setting the label alignment to client, after the layout size has been calculated, to help somewhat. The only real solution is to find a component that trims whitespace at the start of a row.
This is the complete function which does the layout:
function TQuizFrame.CreateQuestion(id: integer; text: string) : TCheckBox; begin // base layout - the row var baseLayout := TLayout.Create(Scroller); baseLayout.Parent := Scroller; baseLayout.Align := TAlignLayout.Top; baseLayout.Margins.Left := 4; baseLayout.Margins.Top := 8; baseLayout.Margins.Right := 16; baseLayout.Margins.Bottom := 8; baseLayout.Tag := id; baseLayout.HitTest := false; // layout to arrange the checkbox - left column (selection) var checkboxLayout := TLayout.Create(baseLayout); checkboxLayout.Parent := baseLayout; checkboxLayout.Align := TAlignLayout.Left; checkboxLayout.Size.Width := 50; checkboxLayout.HitTest := true; checkboxLayout.OnTap := LayoutOnTap; checkboxLayout.OnMouseDown := LayoutMouseDown; // the checkbox var box := TCheckBox.Create(checkboxLayout); box.Parent := checkboxLayout; box.Width := 20; box.Height := 20; box.Position.Y := 3; box.Position.X := (checkboxLayout.Width - 20) / 2.0 - 3; box.HitTest := true; checkboxLayout.TagObject := box; checkboxLayout.AddObject(box); // the label - right column (scrolling) var lbl := TLabel.Create(baseLayout); lbl.Parent := baseLayout; lbl.Position.X := checkboxLayout.Width; lbl.Position.Y := 2; lbl.Width := Scroller.Width - checkboxLayout.Width - 16; lbl.TextSettings.VertAlign := TTextAlign.Leading; lbl.TextSettings.WordWrap := true; lbl.AutoSize := True; lbl.Text := text; lbl.HitTest := false; baseLayout.AddObject(checkboxLayout); baseLayout.AddObject(lbl); baseLayout.TagObject := box; baseLayout.Height := lbl.Height + 16; // align the label now the layout is set - slight improvement lbl.Align := TAlignLayout.Client; Result := box; end;
And the two wired up event handlers:
procedure TQuizFrame.LayoutOnTap(Sender: TObject; const PointF: TPointF); begin var checkbox := ((Sender as TLayout).TagObject) as TCheckBox; checkbox.IsChecked := not checkbox.IsChecked; end; procedure TQuizFrame.LayoutMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin var checkbox := ((Sender as TLayout).TagObject) as TCheckBox; checkbox.IsChecked := not checkbox.IsChecked; end;
The following screenshots are from a Pixel 4a, the left image is using a wide left column and places the checkbox is to the top left. The right image places the checkbox aligned to the top center.
Personally I prefer the smaller width for the first column. In order to avoid accidental selection we prevent scrolling in the first column, but we should minimize the impact of this privation. If the first column is too wide, the user will be frustrated that scrolling doesn’t work. But we need it wide enough to easily touch. I also prefer the checkbox aligned horizontally to the center.
I hope this tip helps.