Tuesday, December 02, 2008

Dynamically Moving Controls

I have a class that provides a file selection control. It consists of a label, a textbox, and a command button. The user can either type the name of the file or click the button to bring up a file dialog.

image

Obviously, a more descriptive label than "File" would be useful, but it would be painful to manually change the caption and then adjust the textbox so the two don't overlap. To make this dynamic, the class has a cLabelCaption property you can set. That property has an Assign method that calls a custom AdjustControls method. AdjustControls set the caption of the label to the value in cLabelCaption and then changes the Left and Width properties of the textbox to make room for the caption. The Init method also calls AdjustControls so entering a value for cLabelCaption in the Properties window adjusts the controls as well.

The problem is that this didn't always work. Sometimes the label and textbox were sized properly and sometimes the label overlapped the textbox. In tracking this down, I discovered that the problem occurred when cLabelCaption was changed before the form the control is on was visible. Changes to the size of controls isn't applied until the form actually appears. So, my calculations which relied on the Width of the label, which I assumed changed automatically when Caption is changed because AutoSize is .T., didn't work.

The solution is to manually determine the width of the label if the form isn't visible yet. You could use TXTWIDTH(), but as I blogged about a couple of years ago, the GDI+ GdipMeasureString function is more accurate. My SFGDIMeasureString class is a wrapper for that function.

Here's the code I now use in AdjustControls. Note the handling of txtFile.Anchor; any time you manually change the position of a control that has anchoring turned on, you need to do this or anchoring won't work properly for that control.

local loSFGDI

with This
if not empty(.cLabelCaption) and ;
not .lblFile.Caption == .cLabelCaption
.lblFile.Caption = .cLabelCaption
    if not Thisform.Visible
      loSFGDI = newobject('SFGDIMeasureString', ;
          'SFGDIMeasureString.prg')
      .lblFile.Width = loSFGDI.GetWidth(.lblFile.Caption, ;
        .lblFile.FontName, .lblFile.FontSize, ;
        iif(.lblFile.FontBold, 'B', ''))
endif not Thisform.Visible
   .txtFile.Anchor = 0
  .txtFile.Left   = .lblFile.Width + 5
   .txtFile.Width  = .Width - .txtFile.Left - .cmdGetFile.Width
   .txtFile.Anchor = 10
endif not empty(.cLabelCaption) ...
endwith

No comments: