Sunday, October 20, 2019
Sizing the ComboBox Drop Down Width
Sizing the ComboBox Drop Down Width          The TComboBox component combines an edit box with a scrollable pick list. Users can select an item from the list or type directly into the edit box.          Drop Down List      When a combo box is in dropped down state Windows draws a list box type of control to display combo box items for selection.         The DropDownCount property specifies the maximum number of items displayed in the drop-down list.         The width of the drop-down list would, by default, equal the width of the combo box.         When the length (of a string) of items exceeds the width of the combobox, the items are displayed as cut-off!         TComboBox does not provide a way to set the width of its drop-down list :(          Fixing The ComboBox Drop-Down List Width      We can set the width of the drop-down list by sending a special Windows message to the combo box. The message is CB_SETDROPPEDWIDTH and sends the minimum allowable width, in pixels, of the list box of a combo box.         To hardcode the size of the drop-down list to, lets say, 200 pixels, you could do:           SendMessage(theComboBox.Handle, CB_SETDROPPEDWIDTH, 200, 0);           This is only ok if you are sure all your theComboBox.Items are not longer than 200 px (when drawn).         To ensure we always have the drop-down list display enough wide, we can calculate the required width.         Heres a function to get the required width of the drop-down list and set it:           procedure ComboBox_AutoWidth(const theComboBox: TCombobox);  const  HORIZONTAL_PADDING  4;  var  itemsFullWidth: integer;  idx: integer;  itemWidth: integer;  begin  itemsFullWidth : 0;  // get the max needed with of the items in dropdown state  for idx : 0 to -1  theComboBox.Items.Count do  begin  itemWidth : theComboBox.Canvas.TextWidth(theComboBox.Items[idx]);  Inc(itemWidth, 2 * HORIZONTAL_PADDING);  if (itemWidth  itemsFullWidth) then itemsFullWidth : itemWidth;  end;  // set the width of drop down if needed  if (itemsFullWidth  theComboBox.Width) then  begin  //check if there would be a scroll bar  if theComboBox.DropDownCount  theComboBox.Items.Count then  itemsFullWidth : itemsFullWidth  GetSystemMetrics(SM_CXVSCROLL);  SendMessage(theComboBox.Handle, CB_SETDROPPEDWIDTH, itemsFullWidth, 0);  end;  end;           The width of the longest string is used for the width of the drop-down list.         When to call ComboBox_AutoWidth?If you pre-fill the list of items (at design time or when creating the form) you can call the ComboBox_AutoWidth procedure inside the forms OnCreate event handler.         If you dynamically change the list of combo box items, you can call the ComboBox_AutoWidth procedure inside the OnDropDown event handler - occurs when the user opens the drop-down list.         A TestFor a test, we have 3 combo boxes on a form. All have items with their text more wide than the actual combo box width. The third combo box is placed near the right edge of the forms border.         The Items property, for this example, is pre-filled - we call our ComboBox_AutoWidth in the OnCreate event handler for the form:           //Forms OnCreate  procedure TForm.FormCreate(Sender: TObject);  begin  ComboBox_AutoWidth(ComboBox2);  ComboBox_AutoWidth(ComboBox3);  end;           Weve not called ComboBox_AutoWidth for Combobox1 to see the difference!         Note that, when run, the drop-down list for Combobox2 will be wider than Combobox2.          The Entire Drop-Down List Is Cut Off For Near Right Edge Placement      For Combobox3, the one placed near the right edge, the drop-down list is cut off.         Sending the CB_SETDROPPEDWIDTH will always extend the drop-down list box to the right. When your combobox is near the right edge, extending the list box more to the right would result in the display of the list box being cut off.         We need to somehow extend the list box to the left when this is the case, not to the right!         The CB_SETDROPPEDWIDTH has no way of specifying to what direction (left or right) to extend the list box.          Solution: WM_CTLCOLORLISTBOX      Just when the drop-down list is to be displayed Windows sends the WM_CTLCOLORLISTBOX message to the parent window of a list box - to our combo box.         Being able to handle the WM_CTLCOLORLISTBOX for the near-right-edge combobox would solve the problem.         The Almighty WindowProcEach VCL control exposes the WindowProc property - the procedure that responds to messages sent to the control. We can use the WindowProc property to temporarily replace or subclass the window procedure of the control.         Heres our modified WindowProc for Combobox3 (the one near the right edge):           //modified ComboBox3 WindowProc  procedure TForm.ComboBox3WindowProc(var Message: TMessage);  var  cr, lbr: TRect;  begin  //drawing the list box with combobox items  if Message.Msg  WM_CTLCOLORLISTBOX then  begin  GetWindowRect(ComboBox3.Handle, cr);  //list box rectangle  GetWindowRect(Message.LParam, lbr);  //move it to left to match right border  if cr.Right  lbr.Right then  MoveWindow(Message.LParam,  lbr.Left-(lbr.Right-clbr.Right),  lbr.Top,  lbr.Right-lbr.Left,  lbr.Bottom-lbr.Top,  True);  end  else  ComboBox3WindowProcORIGINAL(Message);  end;           If the message our combo box receives is WM_CTLCOLORLISTBOX we get its windows rectangle, we also get the rectangle of the list box to be displayed (GetWindowRect). If it appears that the list box would appear more to the right - we move it to the left so that combo box and list box right border is the same. As easy as that :)         If the message is not WM_CTLCOLORLISTBOX we simply call the original message handling procedure for the combo box (ComboBox3WindowProcORIGINAL).         Finally, all this can work if we have set it correctly (in the OnCreate event handler for the form):           //Forms OnCreate  procedure TForm.FormCreate(Sender: TObject);  begin  ComboBox_AutoWidth(ComboBox2);  ComboBox_AutoWidth(ComboBox3);  //attach modified/custom WindowProc for ComboBox3  ComboBox3WindowProcORIGINAL : ComboBox3.WindowProc;  ComboBox3.WindowProc : ComboBox3WindowProc;  end;           Where in the forms declaration we have (entire):           type  TForm  class(TForm)  ComboBox1: TComboBox;  ComboBox2: TComboBox;  ComboBox3: TComboBox;  procedure FormCreate(Sender: TObject);  private  ComboBox3WindowProcORIGINAL : TWndMethod;  procedure ComboBox3WindowProc(var Message: TMessage);  public  { Public declarations }  end;           And thats it. All handled :)    
Subscribe to:
Post Comments (Atom)
 
 
No comments:
Post a Comment