Компонент AngleText (повернений текст) в CBuilder

Для першого разу ми створимо компонент для відображення тексту На цей раз ми зможемо відображати текст тим шрифтом, який вибере користувач (як буде видно далі, з деякими обмеженнями), і поверненим на той кут, який користувачу заманеться Наш компонент може використовуватися для підписів на графіках, в Web-сторінках, і на будь-яких інших формах, де є потреба у відображенні тексту не горизонтально

Для цього першого компонента ми розберемо весь процес від початку до кінця, щоб у вас зявилося чітке уявлення про те, як це все відбувається в середовищі CBuilder У розмовах про подальші компонентах я буду опускати деталі того чи іншого кроку розробки, рекомендуючи вам звернутися до цієї частини, якщо вам будуть потрібні більш докладні розяснення Як ви побачите надалі, процес написання компонента залишається одним і тим же, незалежно від типу компонента

Перший крок у розробці компонента, як ми зясували раніше в цьому розділі, це визначити, що він

повинен робити і як Тобто сформулювати проблему Так навіщо ж ми створюємо компонент У разі компонента AngleText проблема, яку ми спробуємо вирішити, полягає у відображенні вертикальної рядка тексту для підписи під віссю ординат (Y) Для вирішення цієї проблеми ми повинні повернути рядок на 90 градусів від горизонталі Це і буде приватним рішенням

Отже, приватне рішення проблеми полягає в тому, щоб повертати текст на якийсь кут при відображенні Це не важко зробити (власне, ми це вже робили, коли говорили про використання модулів Delphi в наших додатках), маніпулюючи обєктом шрифт (font), присвоєним полю Canvas У даному випадку ми створимо у компоненту власне властивість Canvas, щоб можна було працювати з ним, а не з полем форми, на якій він розташований

В принципі, наступним кроком мало б стати узагальнення проблеми і рішення, але в нашому випадку більш загального рішення, ніж приватне, просто не існує Замість того, щоб ламати собі голови над узагальненням проблеми, ми зосередимося на властивостях, які будуть необхідні нашому компоненту для виконання поставлених перед ним завдань

Як і стадія опрацювання проблеми, стадія проектування має лише одне рішення, яке в даному випадку є приватним Це рішення ставить перед нами дві великі проблеми По-перше, ми повинні визначити, якими властивостями буде володіти наш компонент Залежно від цього нам треба буде прийняти друге рішення – від якого класу VCL буде наслідувати наш компонент

Компоненти володіють двома видами властивостей Перший – це властивості, успадковані від класу базового компонента і представлені в спадкоємця Ці властивості надають базові можливості, засновані на базовому класі компонента VCL ви зможете їх використовувати без праці, вибравши відповідний базовий клас Для компонента AngleText нам би теж хотілося отримати легкий доступ до деяких властивостях для використання їх в компоненті Другий вид властивостей – це нові властивості, які ми визначили спеціально для цього компонента Ці властивості унікальні для компонента і не залежать від вибору базового класу

Коли ви створюєте компонент, ви наслідуєте його від базового класу VCL Цей вибір базового класу впливає на властивості, доступні вам при написанні компонента Всі компоненти повинні принаймні наслідувати від TComponent, базового класу всіх компонентів в VCL, який не надає майже що ніяких можливостей В якості альтернативи ви можете вибрати базовий клас компонента, який буде робити велику частину роботи за вас Від вибору класу залежить, яку частину роботи будете робити ви і яка частина буде зроблена за вас Як правило, ви будете вибирати клас найвищого рівня, який задовольняє критеріям вашого компонента Тобто ви не будете наслідувати від класу комбінованого списку, якщо хочете написати нову кнопку Ви, швидше за все, виберіть клас TButton

Отже, все, про що ми говорили, зводиться до наступного – для того, щоб визначити властивості для класу, треба спочатку вибрати базовий клас компонента Але для того, щоб вибрати базовий клас компонента, треба спочатку зрозуміти, якими ж власне властивостями повинен володіти наш компонент Схоже на замкнене коло, чи не так Не хвилюйтеся – все не так погано, як здається Вибрати клас, від якого наш компонент буде наслідувати, куди простіше, ніж здається на перший погляд Якщо ви хочете створити візуальний компонент, який повинен буде сам себе відмальовувати, вам треба наслідувати від одного з класів TCustomxxx Якщо ви працюєте над компонентом, який представляє з себе кнопку, але з деякими змінами (наприклад, кнопку, у якої подія натиснення повторюється, якщо кнопка миші натиснута на цій кнопці протягом певного проміжку часу – тобто як на клавіатурі), вам варто

успадковувати від TCustomButton

У разі класу TAngleText нас цікавлять базові можливості керуючих елементів, які включають в себе наявність властивостей для тексту, поля, шрифту, кольору і т п Отже, базовим для класу TAngleText стане клас TCustomControl Цей базовий клас надасть нам всі базові властивості керуючих елементів, які нам будуть потрібні

Давайте повернемося до визначення того, які ж властивості нам треба надати компоненту TAngleText Насамперед, нам потрібно властивість для тексту (Text), який ми збираємося повертати Властивість Text – Одне з базових властивостей, належать класу TCustomControl, так що тут ніякої роботи для нас немає Все, що нам треба, це представити це властивість (пізніше ми розглянемо, як це зробити за пару секунд), і воно буде працювати, як обіцяно Наступне очевидне властивість, яка нам треба визначити, це кут (Angle), на який буде повернений текст Ця властивість ми назвемо Angle

Розібравшись з двома найпростішими властивостями, давайте подивимося, які ще властивості нам треба визначити для нашого компонента Всі компоненти VCL автоматично мають властивості для позначення позиції і батька Це властивості Left (відступ зліва), Top (верхній відступ), Heigh (висота), Width (ширина) і Parent (батько) Жодне з цих властивостей вам, розробнику компонентів, визначати вже не треба

Перше, про що слід замислитися – те, яким чином малюється рядок під кутом Для цього нам потрібні три речі По-перше, що відображається рядок Ця властивість Text нашого компонента По-друге, потрібен кут, на який рядок буде повернута Це вже певний нами властивість Angle І, нарешті, нам потрібна позиція, в якій буде відображатися рядок, тобто базова точка нашої рядка Ми могли б визначити цю точку автоматично, але це не найкраще рішення Деякі користувачі можуть побажати, щоб рядок зявлялася відцентрувати по вертикалі, інші захочуть відобразити її вгорі компонента, треті – Внизу Замість того, щоб самим вирішувати це за всіх, ми надамо кінцевому користувачеві (тобто програмісту, який буде використовувати наш компонент) визначення базової точки З точкою як такої працювати важко, тому ми надамо дві властивості – X-координату і Y-координату базової точки

Наступним після базової точки аспектом, який нам треба розглянути, стане власне отрисовка компонента Для того, щоб змінювати відображення компонента, нам потрібен шрифт для відображення Ми могли б просто використовувати батьківський шрифт, але це було б невиправданим обмеженням волі користувача Тому ми представимо властивість Font класу TCustomControl для кінцевого користувача Точно так само ми повинні надати користувачеві можливість змінювати колір фону компонента, щоб компонент міг, якщо в цьому є необхідність, виділятися на формі, в якій розташований Для цього ми представимо властивість Color Отже, на даний момент у нас є шість властивостей, обраних для компонента Три з них надає базовий клас TCustomControl (Font, Text і Color), а три повинні бути втілені нашим компонентом (Angle, XPos і YPos) Настав час перейти до власне кодуванню

CBuilder – не такий вже великий помічник у створенні компонентів Майстер компонентів (Component Wizard) може бути використаний для створення самого найпростішого скелета компонента, але після цього нічим допомогти вже не може Ми зробимо дещо подібне самостійно трохи пізніше, а поки власне створення компонента принесе вам деяку користь і покаже, для чого і як служать окремі частини

Майстер компонентів CBuilder служить для створення скелета компонента в CBuilder Ви можете припустити, що подібний інструмент повинен перебувати в меню Tools (інструменти), але це не так Виберіть Component | New в головному меню CBuilder, і побачите вікно Майстра компонентів, показане на рис 141 Це найпростіше вікно дозволить вам визначити імя компонента, базовий клас компонента і сторінку палітри, в якій ви будете відображати компонент От і все, що ви можете визначити в Майстрі компонентів У ньому немає полів введення ні для властивостей, які ви хочете додати в компонент, ні для додаються методів, ні для подій, які мають оброблятися Все це вам доведеться визначати самим, але не лякайтеся – ми крок за кроком проробимо весь процес у цій главі

Рис 141 Майстер компонентів CBuilder

Для нашого прикладу введіть імя компонента TAngleText Виберіть компонент TCustomControl в якості базового класу компонента і залиште пропоновану за замовчуванням сторінку Samples в поле вибору сторінки палітри Натисніть кнопку OK, і компонент буде автоматично згенерований і додано в ваш проект Це дуже корисно, тому що дає нам можливість протестувати компонент прямо в проекті до того, як він буде синсталлірован в системі А налагоджувати і тестувати компонент у проекті набагато простіше, ніж робити це після того, як він синсталлірован

Додавання властивостей в компонент це перший і дуже важливий крок у його втіленні Існує два способи додавання властивостей в компонент, кожен відноситься до одного з двох типів властивостей, які в ньому присутні По-перше, ви можете додавати свої власні властивості, що ми і проробимо для властивостей Angle, XPos і YPos Коли ви визначаєте свої власні властивості, ви несете відповідальність за визначення їх типу, а також можливостям читання і запису в них З другим видом властивостей, батьківськими (або зумовлені – predefined) властивостями, працювати куди простіше Давайте і почнемо з додавання більш простих, батьківських, властивостей

У заголовному файлі вашого компонента ви знайдете рядок __ published Всі пункти, що знаходяться в цій секції, будуть відображені в Object Inspector середовища CBuilder, коли компонент буде виділений на формі у вікні редактора форм Якщо ви додаєте опис поза цією секції, властивість буде доступно програмісту під час виконання (звичайно, якщо воно буде знаходитися в секції public заголовного файлу), але не буде відображатися під час проектування Властивості, що розташовуються поза секції published, відомі як властивості, доступні тільки під час виконання (runtime-only)

Для додавання батьківського властивості, що надається базовим класом, ви просто вносите його в компонент, передуючи ключовим словом __ property (властивість) Крім того, що це ключове слово повинне бути присутнім, на даний момент вам більше нічого про нього знати не треба Для батьківських властивостей зазвичай додається ключове слово published, після якого слід імена батьківських властивостей, які ви хочете представити в клас компонента Для нашого

випадку треба додати ті три батьківських властивості, про які ми говорили, в заголовний файл:

__published:

__property Text

__property Font

__property Color

В даному випадку ми не змінюємо ніяких частин властивості, але це допускається – ви можете змінювати у батьківському властивості все, що завгодно, крім його типу Зміни можуть торкнутися функцію read (читання), write (запис) і значення, що задається за замовчуванням Ми пізніше коротко розглянемо, як це робиться

Після того, як ми додали батьківські властивості в компонент, наступним кроком стане додавання специфічних властивостей нашого компонента Давайте спочатку розберемося зі змінами, які треба внести в заголовний файл, а потім займемося втіленням (implementation) Наступні рядки додайте в секцію published заголовного файлу для класу компонента TAngleText:

__published:

__property double Angle={read=FAngle, write=FAngle, default=0}

__property int         XPos={read=FXPos, write=SetXPos}

__property int         YPos={read=FYPos, write=SetYPos}

У даному випадку наведені вище опису властивостей визначають нові властивості для класу компонента, які будуть доступні під час проектування Властивість Angle визначено як має тип double Загалом вигляді формат вираження __ property має наступний вигляд:

__property <тип> <ІмяСвойства> = {[read =

<ФункціяЧтеніяІліЗначеніе>] [,write=&ltФункцияЗаписиИлиЗначение&gt][,default=&ltзначение&gt]}

де <тип> – Допустимий тип C + + для цієї властивості Зазвичай типом властивості є один з базових типів C + +, такий, як short, long, int і т п

<ІмяСвойства> – Імя властивості під цим імям властивість зявиться і в Object Inspector

<ФункціяЧтеніяІліЗначеніе> – Це або функція, яка використовуватиметься для читання значення властивості, або саме значення Ми зупинимося на цьому трохи пізніше

<ФункціяЗапісіІліЗначеніе> – Те ж саме, що і функція читання, але ставиться до

зміни значення властивості

<Значення> – Значення компонента за замовчуванням, що відображається в Object Inspector Відзначте, що значення за замовчуванням НЕ встановлюють власне властивість компонента, вони тільки відображаються в Object Inspector під час проектування

Так що ж, насправді, представляють із себе функції читання (Read) і запису (Write) Тут ми підійшли до самого критичного відмінності між властивістю і змінної-членом класу (member variable) Незважаючи на те, що у вашому коді, использующем компонент, властивості проявляють себе як прості змінні-члени класу, вони, насправді, представляють із себе щось більше Змінні-члени класу просто дозволяють користувачеві присвоювати значення властивостей компоненту і модифікувати їх Ви можете не дозволити їм безпосередньо привласнювати

значення, використавши функції доступу, але це зробить ваш код неудобочітаемимі Якщо моє властивість компонента є, наприклад, масивом цілих чисел, то чому я повинен писати код типу:

myComponent-&gtSetArrayElement(3,12)

якщо мені куди більше подобається такий запис: myComponent-> Array [3] = 12

Перше значення (3) – це номер елемента масиву або його значення Ви не зможете сказати цього напевно, чи не звірившись з документацією Для випадку з двовимірним масивом справи йдуть і того гірше:

myComponent-&gtSet2Darray(1,2,12)

Знову-таки, що тут номер рядки Що тут номер стовпчика Що є прісваеваемого значенням Уявіть, що замість цього можна написати просто:

myComponent-&gtArray[1][2] = 12

Чи не здається вам, що така запис спричинить за собою куди менше помилок Ну звичайно це так Використовуючи функції Read і Write подібні речі пишуться простіше Крім того, якщо ви не хочете, щоб користувач міг змінювати значення властивості компонента, ви просто опускаєте функцію Write, і користувач не зможе його змінити Чи не здається вам, що це набагато більше приємно, що турбуватися про функції доступу, що використовують Set (встановити) і Get (отримати) В якості примітки зауважу, що ви насправді можете опустити функцію Read, залишивши користувачеві можливість змінювати значення властивості за допомогою функції Write Не знаючи вже, навіщо вам може знадобитися надання користувачу такого собі чорного ящика, але можливість така є

Значення за замовчуванням може бути визначено за допомогою пропозиції default = у вираженні, визначальному властивість (тобто вираженні __ property) Але це не встановить значення властивості в значення, вказане в цьому реченні, а тільки помістить його в Object Inspector, коли ваш компонент буде вперше створений Якщо ви хочете ініціалізувати значення за допомогою пропозиції default =, вам доведеться встановити значення властивості в конструкторі класу

Я сподіваюся, ви зрозуміли, чому слід використовувати функції Read і Write у вашому компоненті, але тепер постає питання – як їх використовувати Якщо вас не хвилює те, яке значення користувач встановить для властивості (як це не хвилює нас у разі властивості Angle), то ви можете просто привласнити самої змінної значення функції Read або Write Для властивості Angle ми так і вчинимо У разі ж, якщо ви хочете якимось чином фільтрувати вводяться значення, ви привласнюєте функцію-член класу нашої функції Давайте поки додамо в клас компонента змінні для властивостей, щоб вам було простіше сприймати що відбувається У секцію приватних оголошень (private) заголовного файлу додайте наступні рядки:

private:

double FAngle int FXPos

int FYPos

Ці змінні-члени класу – не більше ніж нормальні змінні C + +, до яких ви, напевно, звикли, працюючи з класами C + + У кінцевого користувача (програміста) немає до них

прямого доступу Є загальноприйнятим використання префікса F при роботі зі змінними, які представляють властивості компонента Ця спадщина оригіналів компонентів Delphi, але тим не менше вельми корисне для використання угоду

Ці змінні ви будете використовувати в своєму коді З іншого боку, властивості будуть безпосередньо використовуватися кінцевим користувачем Як це поєднується У випадку прямих властивостей, таких, як Angle, функції Read і Write визначають, що коли користувач змінює значення властивості, написавши такий рядок коду:

pAngleText-&gtAngle = 900

то цей код автоматично присвоює змінній-члену класу FAngle значення 900 Це відбувається без вашої участі при посередництві базового класу і компілятора С + +, вбудованого в CBuilder Водночас, коли програміст пише рядок коду такого змісту:

pAngleText-&gtXPos = 100

відбувається щось зовсім інше У цьому випадку викликається функція компонента Write Якщо ви памятаєте, властивість XPos використовувало функцію, названу SetXPos для установки значень Коли користувач намагається записати значення в властивість, зване XPos, значення перетвориться у виклик функції Вам треба дописати два рядки, що містять прототипи таких викликів функцій, в заголовний файл Отже, додайте наступні два рядки в секцію protected заголовного файлу:

virtual void     fastcall SetXPos(int XPos ) virtual void     fastcall SetYPos(int YPos )

Коли ви пишете функцію Set (або Write), в цю функцію має передаватися один параметр З цього правила бувають винятки, наприклад, для випадку, коли властивість являє собою масив, але ці варіанти ми розглянемо трохи пізніше в цій главі

Зверніть увагу на використання модифікатора __ fastcall для функцій Всі функції властивостей Read і Write повинні використовувати модифікатор fastcall Якщо ви його не використовуєте, то в кращому випадку будуть відбуватися дивні речі, а в гіршому – середа видасть виняткову ситуацію Отже, не забудьте __ fastcall

Втілення цих функцій до дивного нескладно Все, що вам треба, це визначити, які дані ви дозволите передавати в змінні-члени класу, а які ні Це залишається повністю на ваш розсуд Хоч ви і не можете повернути помилку з методів Read і Write, можна згенерувати виняткову ситуацію у випадку, якщо дані мають неприпустимі значення Крім тих випадків, коли некоректні дані будуть в змозі викликати серйозний збій програми, я б не радив так чинити у ваших компонентах

Ось як виглядає втілення цих двох методів: void __ fastcall TAngleText :: SetXPos (int XPos)

{

if ( XPos &lt 0 || XPos &gt Width ) return

FXPos = XPos

}

void __fastcall TAngleText::SetYPos(int YPos )

{

if ( YPos &lt 0 || YPos &gt Height ) return

FYPos = YPos

}

Як видно з наведеного коду, функція просто перевіряє, що задані вами значення лежать всередині кордонів компонента Це досить поширений випадок Якщо значення є припустимим, змінної-члену класу присвоюється нове значення Якщо значення не є допустимим, метод просто повертає управління Що ж відбувається з змінної-членом класу в цьому випадку Нічого Ви надійно захистили власне властивість компонента від отримання некоректних даних, причому набагато краще і простіше, ніж Set і Get

Наступною справою буде ініціалізація змінних в класі Як і у всіх класах C + +, нам важливо ініціалізувати змінні до того, як вони будуть використані Для компонентів це зможе ще й встановити відповідність зі значеннями, які ми вибрали для вказівки за замовчуванням в описі властивості (якщо, звичайно, такі є) Додайте наступний код в конструктор класу:

__fastcall TAngleText::TAngleText(TComponent* Owner)

: TCustomControl(Owner)

{

FXPos = -1

FYPos = -1

Angle = 0

}

Зверніть увагу, що ми не ініціалізували, не отримували, не встановлювали властивостей Text, Font або Color Ці властивості відносяться до компонентів низького рівня і не започатковано там Правда, ви могли б ініціалізувати їх у своєму власному конструкторі, що замістило б установки базового класу

На даному етапі у нас є повністю функціонуючий, але ще більш даремний компонент Цей компонент може створюватися, встановлювати властивості і навіть ініціалізувати їх Єдине, чого він не може, так це відобразити себе як повернений текст Давайте виправимо це невелике непорозуміння

Найбільш важливим аспектом кожного компонента є зовнішній вигляд його керуючого елемента Для компонента, весь сенс якого полягає тільки у візуальному відображенні чогось (рядки, в нашому випадку), він набуває ще більш важливе значення У разі компонента, що посяде від TCustomControl, метод обробник, що викликається для відображення керуючого елемента, називається Paint Метод Paint НЕ вимагає параметрів, так як вам доводиться використовувати властивість класу компонента Canvas для власне малювання Це також дозволяє компоненту при необхідності відображати себе прямо на принтер

На жаль, не існує ніякого іншого способу додати новий метод компонента, окрім як застосувавши грубу силу У цій області є над чим попрацювати творцям CBuilder, і майже напевно в наступних версіях положення буде виправлено А поки що вам доведеться зробити це своїми руками

Для початку треба визначити, який метод компонента вам треба замістити Це можна зробити,

вивчивши методи класу компонента, перераховані в контекстної допомоги по цьому компоненту Виберіть один з них і скопіюйте текст прототипу методу в буфер обміну (clipboard), потім відкрийте заголовний файл класу і додайте наступний рядок в секцію protected (більшість заміщаються методів будуть вступати в дію саме в цій секції заголовного файлу компонента):

virtual void     fastcall Paint(void)

Зверніть увагу на використання ключового слова virtual Тільки віртуальні методи можуть бути заміщені успадковуються класом На щастя, більшість обробників в класах CBuilder втілені у вигляді віртуальних методів

Після того, як прототип визначений у заголовному файлі, прийшов час додати робочу частину методу в вихідний файл класу Додайте наступний метод в кінець вихідного файлу компонента TAngleText:

void __fastcall TAngleText::Paint(void)

{

/ / Встановлюємо кут повороту тексту

LOGFONT LogRec

GetObject(Font-&gtHandle,sizeof(LogRec),&ampLogRec)

/ / Примітка: кут в десятках градусів

LogReclfEscapement = Angle * 100

/ / Перевіряємо, задана чи позиція за замовчуванням

if (FXPos == -1 ) FXPos = Width / 2 if (FYPos == -1 ) FYPos = Height / 2

Canvas-&gtFont-&gtHandle = CreateFontIndirest(&ampLogRec) Canvas-&gtBrush-&gtColor = Color

Canvas-&gtTextOut( FXPos, FYPos, Text )

}

Цей код досить прямолінійний у тій частині, де встановлюються властивості Canvas для шрифту і кольору, а потім малюється текст в заданій користувачем позиції Зміна шрифту здійснюється зміною частини lfEscapement структури, яка визначає кут (в десятках градусів) повороту тексту Інша частина коду це звичайна магія Windows

На цьому творча частина розробки компонента TAngleText закінчена Наступний етап – це тестування компонента, що здійснюється для того, щоб упевнитися в тому, що він працює правильно перед тим, як інсталювати його

Джерело: Теллес М – Borland C + + Builder Бібліотека програміста – 1998

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*