11

The VCL form designer offers pink guidelines for aligning controls at their respective text base lines: Guidelines in form designer
But as far as I can tell this doesn't work for labels and checkboxes. Update: It works for labels if you place the controls exactly, e.g. by Ctrl-arrow. It kind of works for checkboxes - see screenshot.

Now, on some forms I'm creating controls in code, e.g.

ed := TEdit.Create(Self);
ed.SetBounds(...);
ed.Parent := SomePanel;

etc. How can I ensure that their text base lines are aligned? I'd like to have this for edits, comboboxes, labels and checkboxes. The result should look like this (without the red line, of course :-)): base line aligned

Edit: My current approach is to call something like AlignTop(8, [Edit1, ComboBox1], [CheckBox1, Label1]); with

procedure ControlArray_SetTop(const AControls: array of TControl; ATop: Integer);
var
  i: Integer;
begin
  for i := Low(AControls) to High(AControls) do
    AControls[i].Top := ATop;
end;

procedure AlignTop(ATop: Integer; const AControls: array of TControl; const ALabelLikeControls: array of TControl);
begin
  ControlArray_SetTop(AControls, ATop);
  ControlArray_SetTop(ALabelLikeControls, ATop + 3);
end;

My goal is to replace it with something more robust and less hacky.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Uli Gerhardt
  • 13,748
  • 1
  • 45
  • 83
  • Even if you align controls in the designer, are they still aligned when you have font scaling, different fonts at runtime from at design time etc? – David Heffernan Aug 03 '11 at 10:39
  • I quickly tested it with TForm.ScaleBy - this works for edits/combos/labels but not checkboxes. That's probably the same difference as with the form designer. Applying TNonClientMetrics.lfMessageFont to the form breaks the alignment. – Uli Gerhardt Aug 03 '11 at 11:24

4 Answers4

5

The guidelines are implemented in designtime code which license prohibits you to ship with your app so you can only use it to learn from it and then reimplement it yourself. Look up

DesignIntf.TBaseComponentGuidelines
DesignEditors.TComponentGuidelines
VCLEditors.TControlGuidelines

classes (in "{RADStudio\version}\source\ToolsAPI directory"). Perhaps it comes down to something simple as

Label1.Top := (Edit1.Top + Edit1.Height) - Label1.Height + GetMagicConstant;  

where GetMagicConstant is similar to TControlGuidelines.GetTextBaseline().

ain
  • 22,394
  • 3
  • 54
  • 74
  • It's hard to see how this helps. All that is present in ToolsAPI, so far as I can see, are a bunch of declarations without implementation. The essence of this answer is the statement that "Delphi has a mechanism for aligning controls". Or have I missed some crucial implementation detail in my reading of the code? If so, could you add it to your answer. – David Heffernan Aug 03 '11 at 12:39
  • @David: In this case, there **is** some implementation, namely `TControlGuidelines.GetTextBaseline`. AFAICS it's never **called** however. – Uli Gerhardt Aug 03 '11 at 13:02
  • @Ulrich With ToolsAPI you can't tell whether or not it's called, because you only see a skeleton of the code. The real implementation that is private to Emba almost surely does call it. – David Heffernan Aug 03 '11 at 13:20
  • Yeah, I know. There are a few potentially relevant routines (GetTextBaseline, CalcVertPos) but without calling context it's hard to tell what's useful. I hope to have a closer look later. – Uli Gerhardt Aug 03 '11 at 13:24
  • 1
    @David There is implementation as well. It seems that SO's philoshopy is that unless you provide "copy / paste ready" answer you better not post (or risk to be downvoted). I don't have time to go througth the code myself but I believe the stuff OP is looking for is there so I posted it in hope he finds it useful... – ain Aug 03 '11 at 13:44
  • @ain Maybe there is sufficient implementation and perhaps I just didn't look hard enough. – David Heffernan Aug 03 '11 at 13:45
  • @ain: I'm glad you posted what you posted even if it might not lead to a solution. I'm not necessarily looking for ready made recipes. (Not that I'd reject them. ;-)) – Uli Gerhardt Aug 03 '11 at 13:57
2

I don't think this logic is exposed in any way for you to call at runtime. I believe it is design time only.

To handle this I would create a dummy form in the designer which had one of each control you worked with. Align them all the way you have in your screenshots. At runtime instantiate this form, but don't show it and read out the Top property for each type of control. Finally you can work out the vertical offset of the Top property from each type of control to each other type of control.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @Ulrich I'll delete it if anyone can come up with a better one. – David Heffernan Aug 03 '11 at 11:32
  • Pragmatic solution if ever I saw one :-). @Ulrich: To make it a bit less hateful, you could have a single form with all controls you use on any form vertically aligned the way you want them to be (including first row of grids and lists for example)... It would at least be a bit more "generic". – Marjan Venema Aug 03 '11 at 11:50
  • If you were doing this for real you would calculate the offsets once. Then you would implement a class that would align an open array of controls that you passed it. The messy logic would be contained and the client code would be clean and concise. I just assumed that part of the solution, – David Heffernan Aug 03 '11 at 11:53
  • @David: This sounds a bit like the interface of my current approach. – Uli Gerhardt Aug 03 '11 at 12:07
  • I answered and hour and a half ago, you could have had the code implementing this all wrapped up long ago! ;-) – David Heffernan Aug 03 '11 at 12:09
  • @David: I guess that the offsets depend on the font used. A more general solution is hard to give, though, since we don't know the exact alignment Windows uses. One can programmatically retrieve baselines of fonts, and assuming the entire font is vertically centered in the control, it should be possible. Programming it probably requires quite a few trials and errors before it works reliably, since I guess some control types add a few pixels offset to the baseline to make the fonts look more pleasing. – Rudy Velthuis Aug 03 '11 at 17:02
  • the beauty of my solution is that it certain to match the designer – David Heffernan Aug 03 '11 at 17:14
  • @Rudy: I've experimented a bit with a copy of `TControlGuidelines.GetTextBaseline` - use EditHandle instead of Handle for comboboxes, call EM_GETRECT for edits and comboboxes etc., but ATM it doesn't work correctly for labels, under W7/Classic and probably in lots of other situations. – Uli Gerhardt Aug 03 '11 at 21:13
1

i wanted to align a label to it's edit box. standing on @ain's shoulders, i used this:

  Label1.Top := edit1.Top + _GetTextBaseline(edit1, tlBottom) - _GetTextBaseline(Label1, tlTop);


  // lifted from TControlGuidelines.GetTextBaseline(AControl: TControl; Align: TTextLayout): Integer;
  function _GetTextBaseline(AControl: TControl; Align: TTextLayout): Integer;
  var
    Canvas: TControlCanvas;
    tm: TTextMetric;
    ClientRect: TRect;
    Ascent, Height: Integer;
  begin
    Canvas := TControlCanvas.Create;
    try
      ClientRect := AControl.ClientRect;
      Canvas.Control := AControl;
      Canvas.Font := TControlFriend(AControl).Font;
      GetTextMetrics(Canvas.Handle, tm);
      Ascent := tm.tmAscent + 1;
      Height := tm.tmHeight;
      case Align of
        tlTop: Result := ClientRect.Top + Ascent;
        tlCenter: Result := (ClientRect.Top + (ClientRect.Bottom - Height) div 2) + Ascent;
        tlBottom: Result := (ClientRect.Bottom - Height) + Ascent;
      else
        Result := 0;
      end;
    finally
      Canvas.Free;
    end;
  end;
X-Ray
  • 2,816
  • 1
  • 37
  • 66
0

If you make the two controls of the same height, align the tops and align the text vertically normally the base line will be aligned even when you change font size and fonts

John Kouraklis
  • 686
  • 4
  • 12