При первом знакомстве с 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 Автор: Михаил Продан