Выбрать главу

 end

 else if Msg.Msg = WM_PAINT then

 begin

  // Проверяем, был ли запрошен контекст устройства

  // обработчиком, стоящим раньше по цепочке, и если не

  // был, то запрашиваем его.

  NeedDC := Msg.WParam = 0;

  if NeedDC then Msg.WParam := BeginPaint(Panel.Handle, PS);

  // Вызываем старый обработчик WM_PAINT. Его нужно

  // вызывать обязательно до того, как мы начнем рисовать

  // на поверхности что-то свое, т.к. в противном случае

  // это что-то будет закрашено стандартным обработчиком.

  POldPanelWndProc(Msg);

  // При использовании графических функций API самое

  // неудобное - это вручную создавать и уничтожать кисти,

  // карандаш и т.п. Поэтому здесь создается экземпляр

  // класса TCanvas для рисования на контексте устройства

  // с дескриптором, полученным при вызове BeginPaint.

  PanelCanvas := TCanvas.Create;

  try

   PanelCanvas.Handle := Msg.WParam;

   FanelCanvas.Pen.Style := psClear;

   PanelCanvas.Brush.Style := bsSolid;

   PanelCanvas.Brush.Color := clWhite;

   PanelCanvas.Ellipse(10, 10, Panel.Width - 10, Panel.Height - 10);

   PanelCanvas.Brush.Color := clYellow;

   PanelCanvas.Rectangle(100, 100, Panel.Width - 100, Panel.Height - 100);

  finally

   PanelCanvas.Free;

  end;

  // В данном случае панель содержит визуальный неоконный

  // компонент TLabel. Отрисовка неоконных компонентов

  // происходит при обработке WM_PAINT родительского

  // компонента, т.е. здесь она была выполнена при вызове

  // стандартного обработчика. Таким образом, сделанный

  // рисунок закрасил не только фон панели, но и

  // неоконные компоненты. Чтобы компоненты были поверх

  // рисунка, их приходится перерисовывать еще раз,

  // вызывая protected-метод PaintControls. Это не очень

  // эффективно, т.к. получается, что компоненты рисуются

  // дважды: в стандартном обработчике и здесь. Но

  // другого способа решить проблему, видимо, нет. Если

  // бы на панели лежали только оконные компоненты,

  // вызывать PaintControls не понадобилось, поскольку то, что

  // мы рисуем на панели, не может затереть поверхность

  // лежащих на этой панели других окон.

  TFakePanel(Panel).PaintControls(Msg.WParam, nil);

  // Если мы получали контекст устройства, мы же должны

  // освободить его.

  if NeedDC then

  begin

   EndPaint(Panel.Handle, PS);

   Msg.WParam := 0;

  end;

 end

 else FOldPanelWndProc(Msg);

end;

Так как в наш обработчик поступают все сообщения, передающиеся в оконную процедуру панели, начинается он с проверки того, какое сообщение пришло. Сначала реализуем реакцию на WM_RBUTTONDBLCLK просто перемещаем метку Label1 на то место, где пользователь щелкнул мышью. Затем обнуляем результат, давая понять системе, что сообщение полностью обработано. Вызов унаследованного обработчика в данном случае не выполняем, потому что никакая унаследованная реакция на данное событие нам не нужна. Обработка сообщения WM_PAINT сложнее. Сначала необходимо разобраться с контекстом устройства, на котором будет производиться рисование. Вообще говоря, обработчик WM_PAINT должен получать этот контекст с помощью функции BeginPaint. Но если до написанного нами кода сообщение WM_PAINT уже начало обрабатываться, то контекст устройства уже получен, а вызывать BeginPaint два раза нельзя. В этом случае контекст устройства передаётся через параметр сообщения WParam. Соответственно, обработка сообщения WM_PAINT начинается с того, что мы проверяем, равен ли нулю параметр wParam, и если равен, то получаем контекст устройства, а если не равен, используем то, что передано.

Унаследованный обработчик закрашивает всю панель целиком, поэтому его нужно вызывать до того, как мы нарисуем что-то свое, иначе он просто закрасит то, что мы нарисовали. Так что следующий шаг — это вызов стандартного обработчика сообщений панели, указатель на который мы сохранили в поле FOldPanelWndProc. Только после этого можно что-то рисовать.

Примечание

Перекрывая обработку сообщения WM_PAINT, мы лишаем код VCL возможности полностью контролировать процесс перерисовки. В частности, это означает что значение свойства DoubleBuffered будет игнорироваться, двойной буферизации не будет. Поэтому еще раз напоминаем, что программа PanelMsg — это учебный пример, помогающий разобраться с механизмами взаимодействия VCL и Windows API, но не являющийся образцом для подражания. Если в реальной жизни потребуется рисовать что-то непосредственно на панели, нужно порождать от класса TPanel наследника и перекрывать в нем метод Paint.

Теперь можно нарисовать что-то свое. Здесь мы рисуем большой белый круг, а на его фоне — желтый прямоугольник. Для этого используем класс TCanvas способом, который был продемонстрирован в листинге 1.17 (см. разд. 1.1.11). Если бы мы остановились на этом, то увидели бы интересную картину: нарисованные фигуры лежат поверх текста метки Label1. Объяснение этому очень простое: метка является неоконным визуальным компонентом и рисуется на поверхности своего родительского компонента при обработке его сообщения WM_PAINT. А поскольку стандартный обработчик у нас вызывается до того, как рисуются круг и прямоугольник, любой неоконный компонент будет перекрыт ими. К оконным компонентам это, разумеется, не относится, они лежат над родительской панелью, и то, что мы рисуем на этой панели, не может оказаться над ними.

полную версию книги