Digs - Персональная территория

Авторский проект Артема Глазкова
? 
        Версия для печати (цвет)  

Использование материалов
Заметка #21
14 сентября 2006

Создаем ComboBox-2


    Для тех, кто не читал первую часть заметки, советую сначала обратиться к ней. Иначе ничего не будет понятно.

    А для тех, кто прочитал, расскажу, что можно теперь с этим делать.

    Многокнопочный combobox
    Следующая задача, организовать удобный класс предок для всех многокнопочных комбобоксов.
    Все кнопки выровнены по правому краю компонента (с учетом рамки) и занимают всю высоту клиентской области. Поэтому единственный параметр, характеризующий кнопку, это ширина. Количество кнопок хранится в переменной FBtnCount, которая будет устанавливаться в конструкторе. Всю функциональность отдельной кнопки реализуют три метода:

function  BtnWidth(BtnIndex : Integer) : Integer; virtual;
function  BtnDown(BtnIndex, X, Y : Integer) : boolean; virtual;
procedure BtnDraw(BtnIndex : Integer; R: TRect; Pressed : Boolean); virtual;

    Во всех трех методах BtnIndex это индекс кнопки от нуля до FBtnCount-1. Нулевой индекс принадлежит самой левой кнопке.
    Функция BtnWidth возвращает ширину кнопки. BtnDown вызывается в момент клика мышки. Здесь X и Y локальные координаты внутри кнопки. Функция возвращает true, если нажатие произошло. BtnDraw будет заниматься отображением кнопки.
    Первым делом нужно дописать метод Resize, так как теперь клиентская область уменьшится за счет появившихся кнопок и затем устанавливать значение какой-нибудь переменной, чтобы правильно нарисовать нажатую кнопку.

procedure TMyMultiBtnComboBox.Resize;
var i : Integer;
begin
  inherited;
  for i:=0 to FBtnCount-1 do
    Dec(FClientRect.Right,BtnWidth(i));
end;
    
    Ну и, собственно, осталось дописать две процедуры.

procedure TMyMultiBtnComboBox.PaintClient;
begin

end;

procedure TMyMultiBtnComboBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var i,x1 : Integer;
begin
  inherited;
  if Button<>mbLeft then exit;
  //если список виден, то закрываем
  if ListVisible then CloseUp
    else
      //иначе ищем кнопку
      begin
        x1 := FClientRect.Right;
        for i:=0 to FBtnCount-1 do
          if (x>=x1)and(x<x1+BtnWidth(i)) then
            begin
              //если удалось нажать, то запоминаем индекс
              if BtnDown(i,X-x1,Y-FClientRect.Top) then
                begin
                  FBtnPressed := i;
                  Invalidate;
                  exit;
                end
            end
          else Inc(x1,BtnWidth(i));
      end;
end;
    
    Процедура PaintClient отвечает за перерисовку клиентской области. Процедура же перерисовки компонента теперь выглядит приблизительно так:

procedure TMyMultiBtnComboBox.Paint;
var i,x : Integer;
begin
  inherited;
  PaintClient;
  //рисуем кнопки
  x := FClientRect.Right;
  for i:=0 to FBtnCount-1 do
    begin
      BtnDraw(i,Rect(X,FClientRect.Top,
                     X+BtnWidth(i),FClientRect.Bottom),
              FBtnPressed=i);
      inc(X,BtnWidth(i));
    end;
end;
    
    Что касается SpinButton, упомянутого в первой части, то при обработке BtnDown нужно еще проверить значение параметра Y, чтобы понять, какая именно из двух кнопок была нажата.

    Добавление строки ввода
    Сама вставка производится просто. Создаем компонент TEdit, устанавливаем BorderStyle равный bsNone и вставляем его в комбобокс. После этого в процедуре Resize настраиваем положение строки ввода:

procedure TMyComboBox.Resize;
begin
  inherited;
  FEdit.SetBounds(FClientRect.Left,FClientRect.Top,
                  FClientRect.Right - FClientRect.Left,
                  FClientRect.Bottom - FClientRect.Top);
end;
    
    Другая задача, которую предстоит выполнить, это правильно обработать сообщения клавиатуры. Здесь непосредственно компонентом TEdit не обойтись. Хотя бы потому, что нужно правильно обработать Escape.
    
TMyComboBoxEdit = class(TEdit)
private
  procedure CMCancelMode(var Message: TCMCancelMode); message CM_CANCELMODE;
  procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
  procedure WMChar(var Message: TWMChar); message WM_CHAR;
protected
  procedure KeyDown(var Key: Word; Shift: TShiftState); override;
end;
    
    CMCancelMode, WMKeyDown и WMChar не расписываю. Они приблизительно такие же, как в самом компоненте.

procedure TMyComboBoxEdit.KeyDown(var Key: Word; Shift: TShiftState);
const
  Keys : array[1..7] of Integer =
     (VK_UP,VK_DOWN,VK_PRIOR,VK_NEXT,VK_HOME,VK_END,VK_RETURN);
var i : Integer;
begin
  //если список виден, то некоторые клавиши передаем ему
  if (Parent as TMyComboBox).ListVisible and
    not (ssAlt in Shift) then
    for i:=1 to 7 do
      if Key=Keys[i] then
        begin
          (Parent as TMyComboBox).FDropList.KeyDown(Key,Shift);
          Key := 0;
          exit;
        end;
  // Alt+Down открывает/закрывает список
  if (Key=VK_DOWN)and (ssAlt in Shift) then
    begin
      if (Parent as TMyComboBox).ListVisible
        then (Parent as TMyComboBox).CloseUp else
        with (Parent as TMyComboBox) do DropDown(ListClass);
    end
  else inherited;
end;
    
    Здесь ListClass это функция, которая возвращает ссылку на класс, реализующий выпадающий список.
    Ну и в самом компоненте можно обработать OnChange редактора для реализации поиска в списке, если он открыт.

    Заключение
    При написании списка никаких особенностей, за исключением того, что описано в первой части, нет. Поэтому и не стал приводить примеры.


© 2005-16, Powered By Digs (Написать письмо, vk)