Отправляет email-рассылки с помощью сервиса Sendsay

Borland C++ Builder - всякая всячина

  Все выпуски  

Borland C++ Builder всякая всячина (№14. Списки - красим, оживляем)


Служба Рассылок Subscribe.Ru

Приветствую всех получателей рассылки Borland C++ Builder - всякая всячина!

№14. Списки - красим, оживляем

 

"...В каюте первого класса Остап, лежа с башмаками на кожаном диване и задумчиво глядя на пробочный пояс, обтянутый зеленой парусиной, допрашивал Ипполита Матвеевича:
- Вы умеете рисовать? Очень жалко. Я, к сожалению, тоже не умею. - Он подумал и продолжал:
- А буквы вы умеете рисовать? Тоже не умеете? Совсем нехорошо!.."
(И.Ильф и Е.Петров "Двенадцать стульев")

 

Перед тем, как начать выпуск, хочу сообщить, что сделал, наконец, какое-то подобие сайта. Адрес: http://www.homeline.ru/vasco. Выложил туда все выпуски рассылки, поскольку, во-первых, ко-многим выпускам у меня есть примеры, а во-вторых, в некоторых выпусках были исправлены найденные ошибки, что сделать в самой рассылке я, естественно, не могу. Так что, кому интересно - добро пожаловать...

Вспомнил я тут про одну тему, по которой у меня в закромах лежал пример, и решил ее оформить "не отходя от кассы": речь идет о модификации стандартного компонента работы со списками - TListBox. В самом деле, иногда приходится "заталкивать" в списки ТАКИЕ вещи, что без цветового оформления каждого пункта списка не обойдешься. Например, вы делаете несколько цветовых вариантов оформления вашей программы и хотите, чтобы пользователь мог выбрать один из вариантов оформления в списке. В этом случае наиболее удобно будет назвать каждый из вариантов (пунктов списка) соответственно его цветовому оформлению (например "красное на черном") и раскрасить этот пункт списка его цветами: красный текст, черный фон (представляю, какая гадость получится).

Итак, создаем новый проект, закидываем на главную форму два компонента: список TListBox - ListBox1 и список картинок (о нем - позже) TImageList - ImageList1 (он находится на вкладке Win32).

В файл заголовка Unit1.h перед объявлением класса главной формы добавляем объявление класса, описывающего элемент списка:

//---------------------------------------------------------------------------
 
class TColorItem
{
public:
    AnsiString Name;    //Название элемента, отображаемое в списке
    TColor FontColor;   //Цвет текста элемента
    TColor BGColor;   //Цвет фона элемента
    TColorItem(AnsiString AName, TColor AFontColor, TColor ABGColor);   //Конструктор класса
};
//---------------------------------------------------------------------------

Конструктор в данном классе необходим только для более удобной инициализации создаваемого элемента. Кстати, в модуле Main.cpp необходимо создать его описание:

//---------------------------------------------------------------------------
 
TColorItem::TColorItem(AnsiString AName, TColor AFontColor, TColor ABGColor)
{
    Name= AName;
    FontColor= AFontColor;
    BGColor= ABGColor;
}
//---------------------------------------------------------------------------

Кроме того, для удобства желательно прописать в файле заголовка директиву препроцессора
#define COLOR_COUNT 5
которя будет определять количество элементов в списке. Я думаю, мы обойдемся в данном примере статическим списком и не будем изобретать динамических структур типа TList.

На этом подготовительную работу завершим.

Модификация класса формы будет заключаться в следующем:

//---------------------------------------------------------------------------
 
class TForm1 : public TForm
{
__published:   // IDE-managed Components
    TListBox *ListBox1;
    TImageList *ImageList1;
private:   // User declarations
    TColorItem *ColorList[COLOR_COUNT];
public:   // User declarations
    __fastcall TForm1(TComponent* Owner);
    __fastcall ~TForm1();
};
//---------------------------------------------------------------------------

То есть мы объявили массив указателей на объекты типа TColorItem, которые создадим в конструкторе формы и уничтожим в также объявленном деструкторе формы.

Код конструктора и деструктора должен выглядеть примерно так:

//---------------------------------------------------------------------------
 
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    ColorList[0]= new TColorItem("красное на черном", clRed, clBlack);
    ColorList[1]= new TColorItem("белое на синем", clWhite, clBlue);
    ColorList[2]= new TColorItem("черное на бирюзовом", clBlack, clAqua);
    ColorList[3]= new TColorItem("красное на зеленом", clRed, clGreen);
    ColorList[4]= new TColorItem("желтое на синем", clYellow, clBlue);
    ListBox1->Clear();
    int i= 0;
    while (i<COLOR_COUNT)
    {
      ListBox1->Items->Add(ColorList[i]->Name);
      i++;
    }
}
//---------------------------------------------------------------------------
 
__fastcall TForm1::~TForm1()
{
    int i= 0;
    while (i<COLOR_COUNT)
    {
      delete ColorList[i];
      i++;
    }
}
//---------------------------------------------------------------------------

В первых пяти строчках конструктора мы создаем пять объектов типа TColorItem, инициализируем их соответствующими значениями имени, цвета текста и цвета фона, и присваиваем их адреса соответствующим элементам списка ColorList. За такое программирование, вообще-то, руки отрывают, на, я думаю, для такого короткого примерчика сойдет и так. Далее мы очищаем наш главный список ListBox1 и в цикле, выполняющемся пять раз, добавляем в него имена цветовых элементов (Name) из списка ColorList.

С деструктором - еще проще: опять-таки в цикле из пяти итераций перебираем указатели из списка ColorList и удаляем объекты, адреса которых содержат эти указатели.

Осталось сотворить последний - основной метод - прорисовки элементов списка ListBox1 в соответствии с заданными параметрами. Для этого фоспользуемся свойством компонента TListBox OnDrawItem. Оно определяет, какой метод будет вызываться при необходимости нарисовать на экране один из элементов списка. Для создания метода сделайте двойной щелчок мышью на свойстве OnDrawItem (Object Inspector, вкладка Events). Кроме того, чтобы при перерисовке списка вызывался этот метод, вы должны изменить еще одно свойство - Style со значения lbStandard на значение lbOwnerDrawFixed. Если вы этого не сделаете, список ListBox1 будет прорисовываться только стандартными средствами Windows.

Еще одна проблема нестандартных списков - указатель на выделенный элемент. В стандартном списке такие элементы выделяются простой инверсией (обращением цветов). В нашем случае это может привести к серьезным искажением отображаемых данных, поэтому мы применим другой способ: будем слева от выделенного элемента рисовать маленькую стрелочку. В любом графическом редакторе (хоть в Image Editor) нарисуйте маленький (порядка 7х7 символов) равнобедренный треугольник, направленный вершиной вправо и сохраните его как файл (.bmp). Сделайте двойной щелчок на списке картинок ImageList1 и, нажав в открывшейса форма кнопку "Add...", добавьте в список созданную вами картинку. Это и будет наш указатель на выделенный элемент списка.

Теперь я приведу полный текст метода прорисовки элемента списка а затем поясню алгоритм его работы (это даже алгоритмом назвать сложно):

//---------------------------------------------------------------------------
 
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
    TRect &Rect, TOwnerDrawState State)
{
    if (dynamic_cast<TListBox *>(Control))
    {
      TListBox *List= dynamic_cast<TListBox *>(Control);
      TCanvas *pCanvas= List->Canvas;
      if (Index>=0 && Index<COLOR_COUNT)
      {
        pCanvas->Font->Color= ColorList[Index]->FontColor;
        pCanvas->Brush->Color= ColorList[Index]->BGColor;
      }
      pCanvas->FillRect(Rect);
      if (State.Contains(odSelected))
      {
        int UpShift= (List->ItemHeight - ImageList1->Height) / 2;
        ImageList1->Draw(pCanvas, 0, Rect.Top + UpShift, 0, true);
      }
      Rect.Left+= ImageList1->Width;
      pCanvas->TextOut(Rect.Left, Rect.Top, List->Items->Strings[Index]);
    }
}
//---------------------------------------------------------------------------

В первых двух строчках анализируется полученный методом параметр Control - указатель на объект, инициировавший вызов данного метода (ListBox1). И если он принадлежит к классу TListBox, создается указатель типа TListBox на этот объект (об использовании оператора dynamic_cast я рассказывал в выпуске №3). Необходимость таких действий в нашем проекте - спорная вещь, поскольку к списку можно было бы обратиться и напрямую через указатель ListBox1, просто у меня это уже вошло в привычку, так как в больших проектах приходится "вешать" один обработчик событий на несколько элементов формы (иногда - элементов разных классов).

Далее, полученный указатель мы используем для получения опять-же указателя на "полотно" (Canvas) нашего списка, на котором, собственно, мы и будем рисовать все, что нам заблагорассудится. И, опять проверка. На этот раз мы проверяем, не вышло ли значение полученного параметра Index (номер прорисовываемого элемента) за границы массива цветовых элементов (ColorList). Если все в порядке, то цветам шрифта и фона полотна мы присваеваем значения цвета шрифта и фона соответствующего элемента цветового массива ColorList. Используя полученное значение фона, закрашиваем на полотне прямоугольник под прорисовываемый элемент. Границы этого прямоугольника мы молучаем в качестве параметра Rect, причем в координатной системе нашего списка ListBox1.

Теперь нам надо нарисовать указатель на выделенный элемент списка. Для этого мы используем полученный параметр State. Он определяет несколько характеристик текущего состояния прорисовываемого элемента списка. В частности, если он содержит среди прочих значение odSelected, то данный элемент считается выделенным. Проверяется это с помощью метода Contains класса Set (не о нем сегодня речь), который возвращает true, если параметр содержит указанное (odSelected) значение. В общем, если данный элемент выделен, то мы, во-первых, с помощью переменной UpShift "нормализуем" картинку нашего указателя, то есть задаем ей такое смещение от верха прямоугольника, чтобы стрелка "росла" посередине выделенного элемента. Для этого мы используем свойство ItemHeight списка ListBox1, определяющее высоту элементов в пикселах и свойство Height списка картинок ImageList1, задающее высоту всех картинок в списке. После этого мы можем написовать стрелку на холсте, используя метод Draw списка картинок ImageList1. Первый параметр этого метода определяет указатель на холст, на котором будет нарисована картинка, затем два параметра определяют левый верхний угол, от которого он начнет "танцевать", потом мы указываем индекс нужной картинки в списке картинок (0), и, последний параметр определяет, будет ли указанная картинка нарисована, как "разрешенная" (true) или как "запрещенная" (false).

Нам осталось только вывести текст элемента. Для этого мы "поправим" левый край прямоугольника на ширину картинки со стрелкой, чтобы ее не закрывал текст элемента. После этого мы вызываем метод TextOut холста списка для вывода строки текста прорисовываемого элемента. Этот метод получает в качестве параметров координаты верхнего левого угла на холсте для начала рисования и, собственно, строку, которую нужно вывести на холсте. Правый край длинной строки "отпадет сам собой", на холсте отобразится только та часть, которая влезет в него по ширине.

Ну, и, классические уже слова: откомпилируйте проект и запустите приложение. Попробуйте выделить элементы?.. Если все работает, то я могу идти спать, время уже недетское...

Да, пример проекта к этому выпуску я выложил на сайт, кому подойдет - забирайте...



С уважением, Васильев Евгений...
Почта: vasco@homeline.ru
Сайт: http://www.homeline.ru/vasco


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное