Михаил Продан
При первом знакомстве с delphi несомненно удивляешься великому множеству разных визуальных компонентов. Кнопочки, панельки, надписи и многое другое. Но после нескольких месяцев пользования этой средой разработки появляется желание написать что-то свое. Именно эту задачу мы и попытаемся решить используя инвентарь delphi, который есть в у нас в наличии и, естественно, свое воображение.
Постановка задачи
Для начала определимся, что и как мы будем делать. В этом вопросе большую роль играет ваше воображение, эстетические предпочтения и т.д. Я же в силу своей распущенности предложу Вам в качестве примерного варианта создать кнопку нестандартной формы, а именно - овальной.
Реализация
Наиболее правильным, с точки зрения иерархии vcl, методом решения первого пункта поставленной задачи, будет создание нового компонента, в качестве базового класса которого мы выберем tcustomcontrol. Этот класс является базовым для создания компонентов-надстроек над визуальными объектами windows, и предоставляет методы для отрисовки объектов разных форм. Если же у вас нет необходимости наследовать все особенности поведения объектов windows, то можете в качестве базового класса использовать tgraphiccontrol, наследники которого отрисовываются быстрее, поскольку не должны следить за уймой Виндовских служебных сообщений.
Сам компонент tcustomcontrol определен в модуле controls.pas следующим образом:
tcustomcontrol = class(twincontrol)
private
fcanvas: tcanvas;
procedure wmpaint(var message: twmpaint); message wm_paint;
protected
procedure paint; virtual;
procedure paintwindow(dc: hdc); override;
property canvas: tcanvas read fcanvas;
public
constructor create(aowner: tcomponent); override;
destructor destroy; override;
end;
Здесь самым интересным для нас является метод paint и свойство canvas. Посредством этих двух членов класса tcustomcontrol мы и будет рисовать нашу кнопку.
Кроме этого мы немножко расширим функциональность нашего компонента и придадим ему возможность устанавливать цвет темного и светлого участка своей границы, а также ее толщину, и наконец определим свойство flat, которое отвечает за функциональность аналогичного свойства стандартных компонентов delphi.
Исходя из вышесказанного прототип нашего компонента (tellipsebutton) будет выглядеть следующим образом:
tellipsebutton = class(tcustomcontrol)
private
fdarkcolor,flightcolor,fbackcolor:tcolor;
fsize:integer;
fpushed:boolean;
rgn:hrgn;
fflat:boolean;
fdrawflat:boolean;
fonmouseenter,fonmouseleave:tnotifyevent;
{ private declarations }
protected
procedure setdarkcolor(value:tcolor);
procedure setlightcolor(value:tcolor);
procedure setsize(size:integer);
procedure setbackcolor(value:tcolor);
procedure dblclick;override;
procedure drawflat;dynamic;
procedure drawnormal;dynamic;
procedure drawpushed;dynamic;
procedure wmlbuttondown(var message:twmmouse);message wm_lbuttondown;
procedure wmlbuttonup(var message:twmmouse);message wm_lbuttonup;
procedure wmmousemove(var message:twmmousemove);message wm_mousemove;
procedure cmmouseenter(var message:tmessage);message cm_mouseenter;
procedure cmmouseleave(var message:tmessage);message cm_mouseleave;
procedure cmtextchanged(var message:tmessage);message cm_textchanged;
procedure setflat(value:boolean);
procedure domouseenter;
procedure domouseleave;
{ protected declarations }
public
constructor create(aowner:tcomponent);override;
procedure afterconstruction;override;
destructor destory;virtual;
procedure repaint;override;
procedure paint;override;
{ public declarations }
property canvas;
published
property darkcolor:tcolor read fdarkcolor write setdarkcolor default clblack;
property lightcolor:tcolor read flightcolor write setlightcolor default clwhite;
property backcolor:tcolor read fbackcolor write setbackcolor default clbtnface;
property size:integer read fsize write setsize;
property flat:boolean read fflat write setflat;
property caption;
{events}
property onclick;
property ondblclick;
property onmousemove;
property onmousedown;
property onmouseup;
property onmouseenter:tnotifyevent read fonmouseenter write fonmouseenter;
property onmouseleave:tnotifyevent read fonmouseleave write fonmouseleave;
{ published declarations }
end;
Как видим, здесь помимо базовых конструктора create и метода afterconstruction переопределены и методы paint и repaint.
Вся функциональность этого компонента в основном заключена в динамических методах drawflat, drawnormal, drawpushed, которые отвечают за рисование компонента соответственно в режиме flat, в нормальном приподнятом режиме и в нажатом режиме.
Собственно рисование делается с помощью метода canvas.arc, который рисует часть эллипса заданным цветом. Таким образом мы рисуем одну половину темным цветом, а другую - светлым и получаем эффект выпуклости. Поменяв местами цвета мы достигаем эффекта «нажатия» для нашей кнопки. Ну а использовав в качестве цвета фона - средний между темным и светлым цветами границы - мы получаем ефект flat:
procedure tellipsebutton.drawflat;
var x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.color:=backcolor;
canvas.pen.color:=clgray;
canvas.arc(0,0,width,height,0,height,width,0);
canvas.brush.style:=bsclear;
canvas.ellipse(clientrect);
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;
procedure tellipsebutton.drawnormal;
var i:integer;x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.style:=bsclear;
canvas.brush.color:=backcolor;
canvas.pen.color:=darkcolor;
canvas.arc(0,0,width,height,0,height,width,0);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
canvas.pen.color:=lightcolor;
canvas.arc(0,0,width,height,width,0,0,height);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
canvas.brush.style:=bsclear;
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;
procedure tellipsebutton.drawpushed;
var i:integer;x,y:integer;
begin
canvas.lock;
try
inherited paint;
canvas.brush.style:=bsclear;
canvas.brush.color:=backcolor;
canvas.pen.color:=lightcolor;
canvas.arc(0,0,width,height,0,height,width,0);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,i,height-i,width-i,i);
canvas.pen.color:=darkcolor;
canvas.arc(0,0,width,height,width,0,0,height);
for i:=0 to fsize do
canvas.arc(i,i,width-i,height-i,width-i,i,i,height-i);
canvas.brush.style:=bsclear;
canvas.font.size:=5;
x:=self.clientwidth-canvas.textwidth(caption);
x:=x div 2;
y:=self.clientheight-canvas.textheight(caption);
y:=y div 2;
canvas.textrect(self.clientrect,x,y,caption);
finally
canvas.unlock;
end;
end;
Теперь, оснастив наш компонент необходимыми функциями, мы можем приступить к его «причесыванию», т.е. написанию рутинных методов по присвоению значений свойствам и отладке. Первым делом здесь надо реализовать реакцию компонента на события мыши. Это мы делаем посредством методов wmlbuttondown, wmlbuttonup, wmmousemove.
procedure tellipsebutton.wmlbuttondown;
begin
inherited;
paint;
end;
procedure tellipsebutton.wmlbuttonup;
begin
inherited;
paint;
end;
procedure tellipsebutton.wmmousemove;
begin
inherited;
if csclicked in controlstate then
begin
if ptinrect(clientrect,smallpointtopoint(message.pos)) then
begin
if not fpushed then drawpushed;
fpushed:=true;
end else
begin
if fpushed then drawnormal;
fpushed:=false;
end
end;
end;
Здесь также мы реализуем функциональность свойства flat. (в wmmousemove).
Кроме этого мы используем методы cmmouseenter, cmmouseleave для вызова соответствующих обработчиков событий.
А также реализовываем метод cmtextchanged для правильного отображения текста кнопки:
procedure tellipsebutton.cmtextchanged;
begin
invalidate;
end;
Теперь же дело только за методами paint и repaint, которые мы реализовываем следующим образом:
procedure tellipsebutton.paint;
begin
if not fflat then
begin
if not (csclicked in controlstate) then
drawnormal else drawpushed;
end else
if fdrawflat then drawflat else
if not (csclicked in controlstate) then drawnormal else drawpushed;
end;
procedure tellipsebutton.repaint;
begin
inherited;
paint;
end;
Все. Теперь наш компонент готов к испытаниям. И перед тем как его регистрировать и кидать на палитру компонентов настоятельно рекомендую Вам проверить его функциональность в runtime режиме. В противном же случае вы рискуете повесить всю ide delphi при добавлении компонента на форму.
Проверка компонента
Проверка компонента в runtime режиме не вызовет осложнений даже у новичка. Всего-то лишь надо:
-создать новое приложение
-в секции uses разместить ссылку на модуль с вашим компонентом (ellipsebutton.pas)
-объявить переменную типа tellipsebutton
-создать компонент, заполнить все его свойства и показать.
unit main;
interface
uses
windows, messages, sysutils, variants, classes, graphics, controls, forms,
dialogs, mycontrols;
type
tform1 = class(tform)
ellipsebutton1: tellipsebutton;
procedure formcreate(sender:tobject);
procedure formdestroy(sender:tobject);
private
{ private declarations }
public
{ public declarations }
end;
var
form1: tform1;
implementation
{$r *.dfm}
procedure tform1.formcreate(sender:tobject);
begin
ellipsebutton1:=tellipsebutton.create(self);
ellipsebutton1.parent:=self;
ellipsebutton1.setbounds(10,10,100,100);
ellipsebutton1.visible:=true;
end;
procedure tform1.formdestroy(sender:tobject);
begin
ellipsebutton1.free;
end;
end.
После такой, наглядной проверки и отладки вы можете спокойно регистрировать ваш компонент:
procedure register;
begin
registercomponents('usable', [tellipsebutton]);
end;
И использовать уже в ваших приложениях для быстрого создания эллипсоидных кнопок.
Итоги
Теперь, обладая мастерством рисования и зная методику написания визуальных компонентов для delphi, вы можете преспокойно написать любой замысловатый элемент интерфейса и даже продавать его как отдельный программный продукт за немаленькие деньги.
Ссылки по теме