Archive for the ‘combobox’ category

Align values separated by TAB in dropdown

December 24, 2006

We have put a ID-value pair items to TComboBox (using AddItem() method) and want to show both the ID and a value to user (using Additem() Item parameter). We might put the name of the value and append the ID in parentheses like this:

combobox_tabbed_1.png

Not very nice, is it? Another option is to use tabulator (#9) to align the id-value pairs. Let’s try this out:

combobox_tabbed_2.png

UGHH! The tabulator is just ignored. Luckily we have a Win32 API function TabbedTextOut():

The TabbedTextOut function writes a character string at a specified location, expanding tabs to the values specified in an array of tab-stop positions. Text is written in the currently selected font.

Now, we must write our own TComboBox.OnDrawItem event handler:

procedure TForm1.ComboBox1DrawItem(
  Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  cb: TComboBox;
  s: string;
  t: integer;
begin

  // check control type
  Assert(Control is TComboBox);

  // tab position (in pixels)
  t := 50;
  cb := TComboBox(Control);

  // take the font
  cb.Canvas.Font.Assign (cb.Font);

  // take colours
  if (odFocused in State) or
     (odSelected in State) then begin
    cb.Canvas.Brush.Color := clHighlight;
    cb.Canvas.Font.Color := clHighlightText;
  end else begin
    cb.Canvas.Brush.Color := cb.Color;
    if (odDisabled in State) then
      cb.Canvas.Font.Color := clGrayText
     else
      cb.Canvas.Font.Color := cb.Font.Color;
  end;

  // get the item we have to draw
  s := cb.Items[Index];

  // show only the first value as selected value
  if odComboBoxEdit in State then
    s := Copy(s, 1, Pos(#9, s) - 1);

  cb.Canvas.FillRect(Rect);

  // ah boy, win32 rules to some extent
  TabbedTextOut(cb.Canvas.Handle, Rect.Left,
    Rect.Top, pChar(s), Length(s), 1,
    t, Rect.Left);

end;

Let’s take a look at the result:

combobox_tabbed_3.png

Cool! 😉

Mind You: TComboBox.Style must be either csOwnerDrawFixed or csOwnerDrawVariable, otherwise the OnDrawItem event is not fired.

Change the dropdown width of a combobox

December 16, 2006

Sometimes it may happen that a combobox on form cannot be stretched enough and we have items in the list that won’t fit in the dropdown list. Here’s an example of the situation:

combobox_droppedwidth_1.png

Delphi’s VCL does not provide us with an option to change the droppeddown list’s width. However we can send a message called CB_SETDROPPEDWIDTH to the TComboBox. From the Win32 API Reference:

An application sends the CB_SETDROPPEDWIDTH message to set the maximum allowable width, in pixels, of the list box of a combo box with the CBS_DROPDOWN or CBS_DROPDOWNLIST style.

One should do this once after creating the TComboBox and the best place for this is of course TForm’s OnCreate event handler:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ComboBox1.Perform(CB_SETDROPPEDWIDTH, 150, 0);
end;

Where wParam of the message is list’s width in pixels. Now, let’s try it:

combobox_droppedwidth_2.png

TDateTimePicker and NULL dates

September 6, 2006

Using TDateTimePicker control with database can be a PITA. The main problem is that <NULL> dates are shown as 30.12.1899 and after posting changes to DB, the same date will appear in the DB as well. Here’s a hack I did using an empty TComboBox.

Hide the TDateTimePicker with <NULL> dates:

procedure TForm1.DisplayData;
begin
  ..
  if DataModule1.IBDataset1.FieldByName('DateColum').IsNull then
    ComboBox1.BringToFront()
  else begin
    ComboBox1.SendToBack();
    DateTimePicker1.Date :=
      DataModule1.IBDataset1.FieldByName('DateColumn').AsDateTime;
  end;
  ..
end;

In the OnEnter event handler hide the hack TComboBox and make the TDateTimePicker as the active control:

procedure TForm1.ComboBox1Enter(Sender: TObject);
  Combobox1.SendToBack();
  ActiveControl := DateTimePicker1;
end;

Let the OnEnter event handler to handle OnChange and OnClick events as well.

Now that we have TDateTimePicker visible, user can change a date and we must save it to database using the OnChange event handler:

procedure TForm1.DateTimePicker1Change(Sender: TObject);
begin
  DataModule1.IBDataSet1.FieldByName('DateColumn').AsDateTime :=
    StrToDate(DateToStr(DateTimePicker1.Date));
end;

And of course we want to clear the date in DB as well and we will do this in TDateTimePicker’s OnKeyDown event handler by cathring the Delete-key:

procedure TForm1.DateTimePicker1KeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = vk_Delete then begin
    DataModule1.IBDataSet1.FieldByName('DateColum').Clear();
    ComboBox1.BringToFront();
    // move control away from here
    ActiveControl := NextControlInTheForm;
  end;
end;