Сделайте нарисованные владельцем вкладки TPageControl лучше, как без владельца

Я использую Delphi7, PageControl с owner-draw. Я не могу получить такой простой и красивый вид вкладок, как я вижу на PageControls, нарисованных не владельцем. Что плохо: при использовании владелец-рисования я не могу рисовать на "всей" области заголовка вкладки, маленькая рамка размером 1-2 пикселя вокруг заголовка вкладки закрашивается ОС.

1) Delphi не является владельцем-ничьей, тоже выглядит нормально (используется XPMan):

Delphi Sys

2) Delphi owner-draw, вы видите, что не весь заголовок вкладки может быть окрашен (используется XPMan):

Розыгрыш владельца Delphi

Я рисую текущую вкладку синим цветом, а другие - белым. Только пример. Код:

procedure TForm1.PageControl1DrawTab(Control: TCustomTabControl;
  TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
  c: TCanvas;
begin
  c:= (Control as TPageControl).Canvas;
  if Active then
    c.Brush.Color:= clBlue
  else
    c.Brush.Color:= clWhite;
  c.FillRect(Rect);    
end;

2b) Рисование владельца Delphi в реальном приложении (используется XPMan):

Delphi Real App

Почему мне нужно использовать владелец-розыгрыш? Просто. Чтобы нарисовать кнопку X на заголовках вкладок, закрасить верхнюю линию пользовательским цветом, нарисовать значки из списков изображений.

Я ищу способ рисовать ВЕСЬ прямоугольник заголовков вкладок, а не уменьшенный прямоугольник, который предоставляется событиям рисования для владельца PageControl. Я попытался увеличить прямоугольник, заданный событиями рисования владельцем, но это не помогает, ОС все равно перерисовывает этот тонкий кадр размером 1-2 пикселя вокруг заголовков табуляции.

2 ответа

Решение

Вкладки владельца нарисованы родным "вкладка управления" (TPageControl в VCL, хотя его асцендент соответственно назван TCustomTabControl - можно догадаться, почему креативное именование..), как ожидается, будет окрашено его родительским контролем при обработке WM_DRAWITEM сообщения, как описано здесь.

VCL берет на себя нагрузку от родителя, изменяя сообщение CN_DRAWITEM сообщение и отправка его на сам элемент управления. В этом процессе VCL больше не вмешивается. Это просто вызывает OnDrawTab обработчик сообщения, если он назначен кодом пользователя, с передачей соответствующих параметров.

Итак, границы между вкладками рисует не VCL, а сама ОС. Кроме того, очевидно, что это не делает это во время обработки WM_DRAWITEM сообщения, но позже в процессе покраски. Вы можете проверить это, поставив пустой WM_DRAWITEM обработчик на родительском элементе управления страницы. В результате, что бы мы ни рисовали в обработчике событий, оно позже получит границы от ОС.

То, что мы можем попробовать, это попытаться предотвратить эффект отрисовки ОС, ведь у нас есть контекст устройства (как Canvas.Handle). К сожалению, этот маршрут также является тупиком, потому что VCL после возврата обработчика события восстанавливает состояние контекста устройства.

Таким образом, у нас есть единственный способ полностью отказаться от OnDrawTab событие и действуя на CN_DRAWITEM сообщение. Ниже в примере кода используется класс Interposer, но вы можете создавать подклассы управления любым удобным вам способом. Удостоверься что OwnerDrawn установлено.

type
  TPageControl = class(comctrls.TPageControl)
  protected
    procedure CNDrawitem(var Message: TWMDrawItem); message CN_DRAWITEM;
  end;

  TForm1 = class(TForm)
    ..

..

procedure TPageControl.CNDrawitem(var Message: TWMDrawItem);
var
  Color: TColor;
  Rect: TRect;
  Rgn: HRGN;
begin
  Color := 0;  
  // draw in different colors so we see where we've drawn
  case Message.DrawItemStruct.itemID of
    0: Color := $D0C0BF;
    1: Color := $D0C0DF;
    2: Color := $D0C0FF;
  end;
  SetDCBrushColor(Message.DrawItemStruct.hDC, Color);

  // we don't want to get clipped in the passed rectangle
  SelectClipRgn(Message.DrawItemStruct.hDC, 0);

  // magic numbers corresponding to where the OS draw the borders
  Rect := Message.DrawItemStruct.rcItem;
  if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then begin
    Inc(Rect.Left, 2);
//    Inc(Rect.Top, 1);
    Dec(Rect.Right, 2);
    Dec(Rect.Bottom, 3);
  end else begin
    Dec(Rect.Left, 2);
    Dec(Rect.Top, 2);
    Inc(Rect.Right, 2);
    Inc(Rect.Bottom);
  end;
  FillRect(Message.DrawItemStruct.hDC, Rect,
      GetStockObject(DC_BRUSH));

  // just some indication for the active tab
  SetROP2(Message.DrawItemStruct.hDC, R2_NOTXORPEN);
  if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then
    Ellipse(Message.DrawItemStruct.hDC, Rect.Left + 4, Rect.Top + 4,
      Rect.Left + 12, Rect.Top + 12);

  // we want to clip the DC so that the borders to be drawn are out of region
  Rgn := CreateRectRgn(0, 0, 0, 0);
  SelectClipRgn(Message.DrawItemStruct.hDC, Rgn);
  DeleteObject(Rgn);

  Message.Result := 1;
  inherited;
end;


Вот как это выглядит здесь:
введите описание изображения здесь

Из того, что я могу сказать, вы просто ищете тематическую картину своего приложения. В Delphi 7 все, что вам нужно сделать для достижения этой цели, - это добавить манифест приложения, который определяет использование comctl32 версии 6. Простой способ сделать это - добавить компонент TXPManifest в одну из ваших форм или модулей данных, или просто для ссылки на модуль XPMan в вашем проекте.

Поскольку вы хотите, чтобы система рисовала ваш элемент управления страницей, вы не должны рисовать владельца.

Другие вопросы по тегам