При первом знакомстве с 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 вы можете преспокойно написать любой замысловатый элемент интерфейса и даже продавать его как отдельный программный продукт за немаленькие деньги.
Источник: http://www.gigabyte.iatp.org.ua Автор: Михаил Продан