Форум программистов, компьютерный форум, киберфорум
C++ Builder
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.53/15: Рейтинг темы: голосов - 15, средняя оценка - 4.53
37 / 0 / 2
Регистрация: 04.02.2015
Сообщений: 3
1

Создание компонента на основе TButton. Кнопка с иконками в стиле XP

20.03.2015, 14:09. Показов 2811. Ответов 1
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
1. Введение. Настройка проекта для работы со стилями XP.
Что же нам надо сделать для того, чтобы стандартные элементы отображались не в классическом стиле, а в стиле XP? Всего лишь использовать новую версию библиотеки Windows commctrl32.dll. Как это сделать уже много раз описывалось на просторах интернета. Самое простое, это добавить файл манифеста. Например, если исполняемый файл нашего проекта называется Project1.exe, то создаём в том же каталоге файл Project1.exe.manifest содержащий примерно следующее:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>    
<assembly    
xmlns="urn:schemas-microsoft-com:asm.v1"    
manifestVersion="1.0">    
<dependency>    
   <dependentAssembly>    
       <assemblyIdentity    
           type="win32"    
           name="Microsoft.Windows.Common-Controls"    
           version="6.0.0.0"    
           processorArchitecture="*"    
           publicKeyToken="6595b64144ccf1df"    
           language="*"    
       />    
   </dependentAssembly>    
</dependency>    
</assembly>
Проведём эксперимент. Создадим проект. Положим на форму кнопку TButton. Запускаем компиляцию и видим форму с кнопкой в классическом стиле. Создаём в каталоге с исполняемым файлом файл Project1.exe.manifest с содержимым, как указано выше. Снова запускаем компиляцию и видим уже кнопку со скруглёнными углами. Гламурную и красивую. Всё очень просто. (Пока не начинаешь копать глубже ).
А что же сделать, чтобы наша кнопка отображалась на форме в design time тоже в стиле XP? Как же взгляд художника на форму при проектировании? Не запускать же каждый раз компиляцию, чтобы оценить труды по размещению элементов по фэншую. А всё элементарно. Разукрасим старичка Builder-a тем же самым способом. Создадим файл манифеста bcb.exe.manifest в папке Borland-a. Перезапускаем Builder и вот оно. Теперь и в нём все стандартные элементы в стиле XP.
Что хотелось бы ещё заметить. Для полного понимания того что происходит, не надо стесняться читать MSDN. Поскольку данное действие это всего лишь верхушка айсберга следует изучить:
1. Концепцию манифестов. Работа с DLL без них в сложных приложениях крайне некомфортна, а в некоторых случаях и невозможна.
2. Ресурсы приложения. Можно спрятать файл манифеста внутрь файла. А так же захламить своё приложение множеством очень нужных и полезных вещей – иконками, картинками, текстами и пр., которые можно будет извлекать в процессе работы приложения по имени или идентификатору и не таскать с собой 100 маленьких файликов с иконками и т.д.
3. Стандартные элементы Windows, такие как окна, кнопки, списки и пр. Чтобы не изобретать велосипед, а пользоваться уже готовыми объектами. Например, нужна нам кнопка с картинкой вместо текста. TBitBtn или TSpeedButton? Можно. Но они самодельные. Если мы ими воспользуемся, то они останутся классического вида, а не в стиле XP. Эти элементы прорисовываются средствами VCL. Но достаточно отправить стандартной кнопке TButton два системных сообщения BM_SETSTYLE с параметром BS_BITMAP и BM_SETIMAGE с указателем на BITMAP и вот она кнопка моей мечты.
4. И собственно компилятор. В общем, вместе учим матчасть.

2. Создание собственного визуального компонента. Заголовочный файл

Как создавать свои компоненты описано во множестве руководств.
Создадим новый проект типа «Package». И создадим собственно компонент с именем MImageListButton на основе компонента TButton.
Жмём Compile и Install и в общем-то компонент готов. Хоть сейчас добавляй его на форму. Это по сути полная копия TButton, хоть и с другим именем. Все свойства и методы унаследованы.
Теперь нам надо создать свои свойства и методы. Создавать их лучше с помощью соответствующего визарда в ClassExplorer. Так будет меньше ошибок. Переменные (поля нашего класса) создаются с помощью визарда NewField. С его помощью мы добавим переменные для внутреннего использования в классе. Методы класса (в том числе конструктор и деструктор) добавляются с помощью визарда NewMetod. Если мы переопределяем родительский метод с помощью кнопочки Call Inherited вызов родительского метода автоматически добавляется. И наконец свойства класса, которые помимо всего прочего будут доступны в Object Ispector в Design time добавляются с помощью NewProperty. Там можно сразу создать поле, с которым будет связано свойство, задать обработчики его методов Set и Get.
Итак, в секции privat класса создаём следующие поля:
C++
1
2
3
4
5
6
7
8
9
10
        BUTTON_IMAGELIST FImageListStruct;
        TImageList* FImageList;
        HICON FEmptyIcon;
        int FIconBorder;
        int FIconIndexPush;
        int FIconIndexHot;
        int FIconIndexNormal;
        int FIconIndexDisable;
        int FIconIndexFocus;
        SAlignIcon FIconAlign;
FImageListStruct – структура, которая передаётся кнопке для отображения иконок. Она создаётся статически и существует, пока существует класс.
FImageList – это ссылка на объект TImageList, из которого мы будем брать иконки для нашей кнопки.
FEmptyIcon – пустая иконка, которая будет отображаться, если нам не надо показывать картинку на кнопке. По сути её можно использовать как иконку по умолчанию.
FIconBorder – размер границы вокруг иконки.
FIconIndexNormal (Push, Hot, Disable и Focus) – это индекс соответствующей иконки из TImageList или -1, если отображаем иконку по умолчанию (пустую). Из названий я думаю понятно какая иконка за что отвечает. Normal – обычное состояние кнопки не в фокусе, Push – кнопка нажата, Hot – курсор мыши над кнопкой, Disable – кнопка недоступна и Focus – кнопка в фокусе. Именно в этой последовательности они должны располагаться в свойстве FImageListStruct.himl.
FIconAlign – расположение иконки в кнопке.
Да, чуть не забыл. Зачем мы всё это делаем? А потому, что наш старый Builder не знает таких вещей. Соответственно мы должны добавить в MImageListButton.h ещё описания некоторых констант:
Константы Button Control Messages, отсутствующие в файле winuser.h (или commctrl.h) Borland.
C++
1
2
3
4
5
6
#define BCM_FIRST 0x1600
#define BCM_GETIDEALSIZE        (BCM_FIRST + 0x0001)
#define BCM_SETIMAGELIST        (BCM_FIRST + 0x0002)
#define BCM_GETIMAGELIST        (BCM_FIRST + 0x0003)
#define BCM_SETTEXTMARGIN       (BCM_FIRST + 0x0004)
#define BCM_GETTEXTMARGIN       (BCM_FIRST + 0x0005)
Структура, BUTTON_IMAGELIST, уже упоминавшаяся.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct {
  HIMAGELIST himl;
  RECT       margin;
  UINT       uAlign;
} BUTTON_IMAGELIST, *PBUTTON_IMAGELIST;
И энумератор, отвечающий за расположение иконки в кнопке.
 enum SAlignIcon  {ALIGN_LEFT,
ALIGN_RIGHT,
ALIGN_TOP,
ALIGN_BOTTOM,
ALIGN_CENTER
};
#endif
Так же в секции privat находятся декларации методов записи свойств:
        void __fastcall SetImageList(TImageList* value);
        void __fastcall SetIconBorder(int value);
        void __fastcall SetIconAlign(SAlignIcon value);
        void __fastcall SetIconIndexPush (int value);
        void __fastcall SetIconIndexHot (int value);
        void __fastcall SetIconIndexNormal (int value);
        void __fastcall SetIconIndexDisable (int value);
        void __fastcall SetIconIndexFocus (int value);
В секции protected методы, которые могут наследоваться:
C++
1
2
3
4
        void __fastcall SetStyle();
        void __fastcall CreateButtonIcon(int index, int index_replace);
        __fastcall ~MImageListButton();
        virtual void __fastcall CreateWnd(void);
SetStyle – посылает сообщение BCM_SETIMAGELIST кнопке о загрузке иконок. CreateButtonIcon – Заменяет в списке иконку на другую из FImageList или на иконку по умолчанию. index – индекс иконки в FImageList или «-1» для пустой иконки, index_replace – номер иконки в списке кнопки.
~MImageListButton – деструктор.
CreateWnd – добавлена просто для демонстрации, как вызывается наследуемый метод. В этой функции можно описать действия до или после непосредственного вызова функции WinAPI CreateWindow, которая создаёт кнопку и присваивает ей Handle.

В секции public общедоступные методы:
C++
1
2
3
        __fastcall MImageListButton(TComponent* Owner);
        void __fastcall ClearStyle();
        HIMAGELIST GetHIML();
MImageListButton – конструктор класса;
ClearStyle – возврат к классическому состоянию кнопки. Просто вызывает метод Set свойства ImageList с нулевым указателем на TImageList.
GetHIML – возвращает указатель на FImageListStruct.himl.

И наконец в секции __published описаны свойства, которые будут отображаться в Object Ispector:
C++
1
2
3
4
5
6
7
8
        __property TImageList* ImageList  = { read=FImageList, write=SetImageList, nodefault };
        __property int IconBorder  = { read=FIconBorder, write=SetIconBorder, default=1 };
        __property int IconIndexNormal  = { read=FIconIndexNormal, write=SetIconIndexNormal, default=-1  };
        __property int IconIndexPush  = { read=FIconIndexPush, write=SetIconIndexPush, default=-1 };
        __property int IconIndexHot  = { read=FIconIndexHot, write=SetIconIndexHot, default=-1 };
        __property int IconIndexDisable  = { read=FIconIndexDisable, write=SetIconIndexDisable, default=-1 };
        __property int IconIndexFocus  = { read=FIconIndexFocus, write=SetIconIndexFocus, default=-1 };
        __property SAlignIcon IconAlign  = { read=FIconAlign, write=SetIconAlign, default=ALIGN_LEFT };
Обратите внимание на параметр по умолчанию default/nodefault. Хоть он и не обязательный задавать его обязательно . Он определяет значение, которое не будет сохраняться в файле .dfm формы приложения. Если параметр не задан, то он автоматически считается равным нолю. Например, если мы зададим значение __property int IconIndexFocus = { read=FIconIndexFocus, write=SetIconIndexFocus }; в таком виде (default=0), в Object Ispector присвоим этому значению «0», то после закрытия и открытия Builder-a значение его будет равно «-1», т.к. в конструкторе класса мы инициализируем значение IconIndexFocus как «-1».
Так же default/nodefault имеет ещё один эффект. Если значение свойства равно значению по умолчанию, то при компиляции не вызывается метод Set свойства. Т.е. мы не передадим в запущенное приложение настроенное в Object Ispector значение свойства.
Параметр nodefault означает вызов метода Set в любом случае.
И ещё, нельзя в методах Set свойств рассчитывать на какой-то порядок инициализации. Необходимо реализовывать метод Set таким образом, чтобы он мог вызываться индивидуально и не вызывать ошибок в приложении.
Вложения
Тип файла: rar Компонент MImageListButton.rar (2.3 Кб, 14 просмотров)
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
20.03.2015, 14:09
Ответы с готовыми решениями:

Создание компонента класса TButton
Нужно создать компонент класса TButton

При добавлении компонента к примеру кнопки. В коде Button1: TButton; выдает ошибку что TButton не известно.
Прошу у вас помощи. Rad Studio XE 3 выдает глупую ошибку. При добавлении компонента к примеру...

Создание своего компонента на основе компонента Timer
нужно создать свой компонент какбы который наследует компонент Таймер нужно хотя бы добавить...

Создание компонента на основе TLabel
можете подсказать как в билдере инсталировать компонет? потмоу что в учебнике котором я читаю -...

1
37 / 0 / 2
Регистрация: 04.02.2015
Сообщений: 3
20.03.2015, 14:11  [ТС] 2
3. Создание собственного визуального компонента. Реализация.

Начнём реализацию методов нашего класса.
Конструктор. Инициализируем в нём все значении наших полей и создаём пустую иконку. Описание создания иконки и объекта ImageList я опишу в заключительной части.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__fastcall MImageListButton::MImageListButton(TComponent* Owner)
        : TButton(Owner)
{
//Инициализация значений полей
FImageList=NULL;
FIconBorder=1;
FIconIndexPush=-1;
FIconIndexHot=-1;
FIconIndexNormal=-1;
FIconIndexDisable=-1;
FIconIndexFocus=-1;
FIconAlign=ALIGN_LEFT;
 
FImageListStruct.himl=NULL;
FImageListStruct.margin.left =FIconBorder;
FImageListStruct.margin.top=FIconBorder;
FImageListStruct.margin.right  =FIconBorder;
FImageListStruct.margin.bottom  =FIconBorder;
FImageListStruct.uAlign=FIconAlign;
 
// Создание пустой иконки
BYTE ANDmaskIcon[32];
BYTE XORmaskIcon[32];
memset(ANDmaskIcon,0x00,32);
memset(XORmaskIcon,0xFF,32);
FEmptyIcon = CreateIcon(NULL, 16,  16, 1,1, ANDmaskIcon,  XORmaskIcon);
}
Деструктор. Здесь мы при необходимости уничтожаем только объект FImageListStruct.himl. Все остальные объекты имеют собственные деструкторы или освобождаются системой (например иконки).
C++
1
2
3
4
__fastcall MImageListButton::~MImageListButton()
{
       if (!FImageListStruct.himl) ImageList_Destroy( FImageListStruct.himl  );
}
Метод SetStyle(). Задача этого метода послать кнопке сообщение (если её handle уже создан) о присвоении ей ImageList. Затем унаследованным методом Repaint() принудительно перерисовываем кнопку.
C++
1
2
3
4
5
6
7
8
9
10
void __fastcall MImageListButton::SetStyle()
{
 
       if (HandleAllocated())
       {
       if (FImageListStruct.himl!=NULL)
        SendMessage(Handle,BCM_SETIMAGELIST,0,(LPARAM)&FImageListStruct);
        Repaint();
       }
}
Метод CreateButtonIcon. Задача этого метода взять index иконку из FImageList и вставить её в index_replace FImageListStruct.himl. Если index=-1 или больше количества иконок FImageList, то вставляется пустая иконка.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
void __fastcall MImageListButton::CreateButtonIcon(int index, int index_replace)
{           
int index_local;
HICON Im;
if (FImageListStruct.himl==NULL) return;
 
if ((index<FImageList->Count)&&(index>-1)) index_local=index; else index_local=-1;
 
 if (index_local>=0)  Im = ImageList_GetIcon((_IMAGELIST*)FImageList->Handle,index_local, ILD_NORMAL);
 else    Im = FEmptyIcon;
 
ImageList_ReplaceIcon(FImageListStruct.himl,index_replace,Im );
 }
Метод SetImageList. Присвоение полю FImageList ссылки на существующий объект TImageList из которого будут браться наши иконки. При необходимости уничтожается экземпляр FImageListStruct.himl, что кстати автоматически приведёт к изменению стиля кнопки на стиль по умолчанию (хотя может быть это не совсем корректно и сначала надо послать кнопке сообщение SendMessage(Handle,BCM_SETIMAGELIST,0,(LPARAM)NULL)). Затем вновь создаётся FImageListStruct.himl и по очереди инициализируются иконки кнопки. Индекс «-1» означает, что иконка не заменяется, а добавляется в конец списка. См. примечания в заключительной части статьи. В конце вызывается метод SetStyle() для перерисовки кнопки.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall MImageListButton::SetImageList(TImageList* value)
{
        FImageList = value;
        if (!FImageListStruct.himl)  {ImageList_Destroy( FImageListStruct.himl  );  FImageListStruct.himl=NULL;   }
        if (!FImageList)  return;
        if (!FImageList->HandleAllocated())  return;
        FImageListStruct.himl = ImageList_Create(FImageList->Width,FImageList->Height , ILC_COLOR|ILC_MASK   , 5, 0);
        CreateButtonIcon(FIconIndexNormal,-1);
        CreateButtonIcon(FIconIndexHot,-1);
        CreateButtonIcon(FIconIndexPush,-1);
        CreateButtonIcon(FIconIndexDisable,-1);
        CreateButtonIcon(FIconIndexFocus,-1);
        SetStyle();
}
Метод SetIconBorder. Присвоение полю FIconBorder значения границы вокруг иконки в кнопке, изменение структуры FImageListStruct.margin и вызов SetStyle() для отрисовки.
C++
1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall MImageListButton::SetIconBorder(int value)
{
        if (value>=0)
        FIconBorder=value;
        else FIconBorder= 0;
 
        FImageListStruct.margin.left =  FIconBorder;
        FImageListStruct.margin.top=    FIconBorder;
        FImageListStruct.margin.right = FIconBorder;
        FImageListStruct.margin.bottom =FIconBorder;
        SetStyle();
}
Методы SetIconIndex…. Методы меняют соответствующую иконку на новую и прорисовывают кнопку заново. Все они похожи как один.
C++
1
2
3
4
5
6
void __fastcall MImageListButton::SetIconIndexPush (int value)
{
        FIconIndexPush=value;
        CreateButtonIcon(FIconIndexPush,1);
        SetStyle();
}
Метод SetIconAlign. Назначает расположение иконки в кнопке.
C++
1
2
3
4
5
6
void __fastcall MImageListButton::SetIconAlign(SAlignIcon value)
{
       FIconAlign= value;
       FImageListStruct.uAlign=FIconAlign;
       SetStyle();
}
Метод ClearStyle. Отключает TImageList и перерисовывает кнопку в стиле по умолчанию просто воспользовавшись методом SetImageList.
C++
1
2
3
4
void __fastcall MImageListButton::ClearStyle()
{
       SetImageList(NULL);
}
Метод GetHIML. Возвращает FImageListStruct.himl. Мало ли понадобится?
C++
1
2
3
4
HIMAGELIST MImageListButton::GetHIML()
{
        return FImageListStruct.himl;
}
Метод CreateWnd. Вызывает наследуемый метод CreateWnd(), который собственно создаёт кнопку, и назначает ей Handle. После этого он вызывает метод SetStyle().
C++
1
2
3
4
5
void __fastcall MImageListButton::CreateWnd(void)
{
TButton::CreateWnd();
SetStyle();
}
Всё остальное добавлено визардом и трогать мы это не будем.
Вот в общем-то и всё. Теперь компилируем проект и инсталлируем его в палитру Borland Builder.
Пробуем создать новый проект. Помещаем на форму наш MImageListButton и TImageList. Добавляем в TImageList иконки, назначаем в Object Ispector MImageListButton их номера, границу и выравнивание и если вы добавили Builder-у манифест bcb.exe.manifest, то вы увидите изменения уже в режиме design time. Компилируем и смотрим. Не запускается? Падает с ошибкой «Error reading ImageList1->Bitmap: Failed to read ImageList data from stream.»? Так вы забыли добавить к своему проекту манифест. Создаём манифест Project1.exe.manifest и всё прекрасно работает.

4. Заключение.
В заключении хотелось бы рассказать о некоторых особенностях Icon и ImageList. Одну мы только что увидели, когда запускали проект без манифеста. TImageList, если его иконки используются в компоненте, использующей стили библиотеки commctrl32.dll версии 6 и выше не умеет грузить свой Bitmap из потока и вызывает остановку приложения. Помните об этом.
TImageList инкапсулирует свойства стандартного ImageList. Соответственно его можно использовать в API функциях Windows через его Handle. Например, ImageList_GetIcon((_IMAGELIST*)FImageList->Handle,0, ILD_NORMAL);
Кстати, заметьте, что проверка существования объекта по его Handle выполняется в Builder через вызов метода HandleAllocated(), а не обращением напрямую к Handle, так как это может привести к непреднамеренному созданию объекта.
Ну, с ним вроде всё в порядке, а вот стандартный ImageList имеет особенность при создании. В MSDN сказано, что параметр cInitial в функции HIMAGELIST ImageList_Create( int cx, int cy, UINT flags, int cInitial, int cGrow ); распределяет указанное количество иконок при создании, но если вы попытаетесь воспользоваться ими, это приведёт к ошибке, а вызов ImageList_ReplaceIcon(HIMAGELIST himl, int i, HICON hicon ) вернёт значение -1 и ничего не сделает. Поэтому, в начале надо добавить cInitial количество иконок в ImageList с помощью параметра ImageList_ReplaceIcon i=-1. Значение i=-1 означает добавление иконки в конец списка.
Распределённый HIMAGELIST необходимо удалять по окончании работы с ним, что мы и делаем в деструкторе функцией ImageList_Destroy( HIMAGELIST himl ).
А вот объект Icon удалять совершенно не обязательно (кроме случая, если объект Icon создан с помощью функции CreateIconIndirect). Windows сделает это самостоятельно, когда иконка более не понадобится.
Ну и напоследок. Объект Icon восходит к истокам. И его структура имеет свои особенности. Пустую иконку мы создали так: создали битовую маску AND, заполненную единицами и битовую маску XOR заполненную нулями. Т.е. чёрно-белую маску. Количество бит на пиксель иконки 1, количество масок XOR одна. Когда бит в маске AND 1, а в XOR 0 мы получим просто цвет фона.
Удачи в разработке!
0
20.03.2015, 14:11
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
20.03.2015, 14:11
Помогаю со студенческими работами здесь

Создание класса на основе компонента
Требуется создать класс на основании компонента Panel. public class &lt;ClassName&gt; :...

Создание собственного компонента на основе TEdit
Мне нужно создать пакет со своим компонентом в c++ builder'e на основе edit'a. В обработчик...

Создание нового компонента на основе существующего (наследование);
Ребята, Вы моя последняя надежда! Осталось последнее задание в курсовой! Все уже зделал, но вот это...

Пропадают иконки стандартных приложений в стиле metro и еще маленькая неприятность с иконками
Собственно, купил себе трансформер ASUS T200TA и на установленной восьмерке, время от времени, на...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
2
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru