TlistView и как его побеждать
Друзья, хочу поделиться своим опытом с TlistView. Многое из того, что я тут напишу Вы легко найдете и многие знаете, но некоторые вещи я с трудом отрыл и в не самом готовом виде.
Сначала опишу, что мне следовало сделать:
Была задача создать список чудо-строк. Чудо-строки это неокторого рода информация, которая для Вас никакого интереса не представляет, просто будем называть их чудо-строками. Количество чудо-строк заведомо неизвестно, количество полей из которых соcтоит строка тоже неизвестно. Первое поле – ID, остальные поля какие угодно и в любом количестве.
Необходимо, чтобы в зависимости от определенного признака строки выводились с такими цветом:
1. первый столбец темно красный, остальные светло красные
2. первый столбец темно зеленый, остальные светло зеленые
3. + к цвету фона должен в зависимости от некоего другого признака меняться цвет текста (черный/серый) и жирность текста
Цвет выделителя (выделенной строки) должен быть не стандартным. Важно! Первую колонку никогда не выделять, чтобы было ясно какая эта запись-красная или зеленая. Подробнее, как выглядит мой TlistView – на рисунке.
Так же надо, чтобы сохранялись в ИНИ файле ширины колонок, их порядок следования и “отображаемость” (видимость).
1. Почему я предпочел TListView компонентам TStringGrid и TDbGrid
2. Проблемы с которыми я встретился - столбцы:
2.1 Скрытие столбцов
2.2 Порядок следования столбцов (первый двигать нельзя – запрет на передвижение первого столбца)
2.3 Ширина столбцов
2.4 Сортировка при нажатии на столбец. Отображение стрелочек
3. Проблемы с которыми я встретился - кастомизация:
3.1 Изменить цвет ячеек, причем так, чтобы одна ячейка была одного цвета, а другая другого
3.2 Изменить цвет ячеек - пол дела, но вот беда - как изменить цвет выделителя (выделенного элемента) со стандартного на свой. При выделенной строке, надо, чтобы первый столбец не выделялся
3.3 Как писать в TListView своим шрифтом (не стандартным в системе Windows)? Если его устанавливаю, то он не помещается в строку.
4. Глюки TListView
4.1 Глюки TListView при определении колонки, если они перетсавляли местами
Надо сказать, что чем мне понравился RlistView, тиак это тем, что каждая строка компонента может связаться с неким обхектом. Это очень удобно. У меня каждая строка это class, у которого есть переменные – поля записи (а точнее колонки с их значениями):
TMyStr = class
public
Ntfn_id: string; // логическое ID
Selected: boolean; // покахывает выделеоно оно в гриде или нет
info_fields: TStringDynArray; // массив значения полей
end;
И есть массив StrList с этими TMyStr (то есть массив всех строк).
При заполнении TlistView я дополнительно связываю каждую строку ListView с эти объектом:
ListView.Items.SubItems.Add(StrList .info_fields[0])
ListView.Items.SubItems.Add(StrList .info_fields[1])
…
if ListView.Columns.Items.Caption=’ID’ then begin //нашли кононку
// первая колнка всегда первая должна быть!
ListView.Columns.Items.Index:=0;
break;
end;
end;
В итоге если пользователь перетащил, скажем пятую колонку на первое место, то она становилась не первой, а второй. Далее подобным образом я перебирал все колонки и сохранял их порядок следования в массив. Порядок следования лежит в переменной Index (ListView.Columns.Items.Index);
// первая колнка всегда первая должна быть!
My_param_mus:=ListView.Columns.Items.Width;
end;
P.S. На самом деле вместо My_param_mus лучше использовать массив объектов. Где объект имеет свойства ширины колонки, названия колонки, видимости и порядка следования.
Тут тоже был подводный камень, так как у меня был не один TListView, а несколько, мне нужно было сохранять размеры колонок в момент изменения размера колонок, но такого события нет. Помимо всего прочего, забегая вперед, скажу что это событие так же было необходимо и для своей отрисовки, почему именно написано в пунктах 3.1-3.4.
Тут мне помогла статья и компонент: http://www.cracklab.ru/pro/faq.php?pg=2450&ln=50. Тут описано как ввести события для TlistView: OnColumnResize, OnBeginColumnResize, OnEndColumnResize. В итоге на событие OnColumnResize я повесил код, который перебирает все колонки и сохраняет их ширину.
PS на самом деле этот код я использовал не напрямую, а встроил в другой, но об этом ниже.
Тут мне помогла статья: http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1366 с компонентом TatwListView. Она мне очень помогла, тут все хорошо расписано и с примерами (советую скачать). Создается новый компонент на вкладке Samples. Сортировки у меня были написаны свои, еще когда я работал со StringGridом, поэтому я не стал заморачивать как сортировать методами TListView, а просто ловил стандартное события нажатия на заголовке ListView – OnColumnClick. У меня была переменная, показывающая направление сортировки и я просто делал в этом событии следующее: смотрел на флаг и рисовал соответствующие стрелки силами компонента
Var
AO: TArrowOptions;
Begin
…
TatwListView
if sort_asc_desc then begin
sort_asc_desc:=false;
AO.ArrowType:= atDown;
end else begin
AO.ArrowType:= atUp;
sort_asc_desc:=true;
end;
ListView.ArrowOptions:= AO;
Затем просто сортировал своими процедурами в своих массивах, после чего перевыводил содержимое listView в отсортированном виде.
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
bcolor1:TColor;
begin
v_notific:=Item.Data; // найду свой объек-строку с которым связана данная строка TlistView
if v_notific.BadGood_flag then begin
bcolor1 := $006666FF; //темно красный
end else begin
bcolor1 := $33CC66; // темно зеленый
end;
Sender.canvas.Brush.Color:=bcolor1;
End;
Таким образом в зависимости от признака первая колонка была темно зеленый или темно красной.
Далее в событии OnCustomDrawSubItem я делал тоже самое, но цвет ставил светло красный или светло зеленый. В итоге первая колонка была темно красной или темно зеленой, а все остальные светлыми.
if cdsSelected in State then begin
Sender.canvas.Font.color:=clRed;
end;
Не прокатят, так как виндовс все равно перерисует все таким цветом, каким он хочет. Поэтому вариант один – рисовать самому все в этой ф-ци, устанавливая флаг DefaultDraw:=false. Но и тут у меня возникли проблемы. Нужно чтобы цвет первой колонки отличался от цвета других колонок, первая колонка не выделялась, потому идем дальше.
Ставим свойство OwnerDraw в True и юзаем событие OnDrawItem, которое вызывает после всех виндовс перерисовок и я могу рисовать все что хоче и никто ничего не перерисует, но рисовать на 100% придется самому. Основная проблема, как рисовать SubItems?- ведь их координат нет. Они вычисляются исходя из ширины колонок:
Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var
vid: string;
bcolor1,bcolor2,fcolor:TColor;
v_Rect:TRect;
v_notific: TNotification;
canvasq:TCanvas;
i:integer;
w:integer;
R: TRect;
begin
v_notific:=Item.Data;
if v_notific.BadGood_flag then begin
bcolor2 := $00CCCCFF; // светло красный
bcolor1 := $006666FF; //темно красный
end else begin
bcolor2 := $00CCFFCC; // светло зеленый
bcolor1 := $33CC66; // темно зеленый
end;
if v_notific.UNneeded_flag then begin
Sender.canvas.Font.color:=clSilver;//clSilver;//clWhite;
end else begin
Sender.canvas.Font.color:=clBlack;
end;
if (v_notific.Looked_flag=false) and (v_notific.UNneeded_flag=false) then begin
Sender.canvas.Font.Style:=[fsbold];//clWhite;
end;
// если выделена срока, то ставлю свой цвет!
if odSelected in State then begin
bcolor2:=$00FFB34E;
end;
v_Rect:=Rect;
v_Rect.Bottom:=Rect.Bottom+2;
Sender.canvas.Brush.Color:=bcolor1;
Sender.canvas.FillRect(Rect);
v_Rect:=Rect;
w:=0;
with (Sender as TCustomListView ) do begin
canvasq:=Canvas;
canvasq.TextOut(Rect.Left+5, Rect.Top, item.Caption);
v_Rect.Left:=v_Rect.Left+w;
w:=Rect.Left;
for i:=0 to Item.SubItems.Count-1 do begin
w:=w+Column.Width;
v_Rect.Left:=w;
canvas.Brush.Color:=bcolor2;
canvas.FillRect(v_Rect);
canvas.TextRect(v_Rect,v_Rect.Left+5,v_Rect.Top,Item.SubItems);
end;
end;
end;
Текст, возможно, не особо ясен, но главное тут показано как перерисовать ListView полностью под свои требования.
Могу лишь сказать что есть такая штука - VirtualTreeView. Офигенная штука, да еще и бесплатная, и профи написанная, да еще и новая версия относительно недавно вышла.
Могу лишь сказать что есть такая штука - VirtualTreeView. Офигенная штука, да еще и бесплатная, и профи написанная, да еще и новая версия относительно недавно вышла.
Спасибо,я поищу). Я просто с Дельфи недавно и не особо знаю что куда. Просто тут хотелось поделиться опытом извращенства с ListView, так как в сети нет точных ответов на все эти вопросы. А то что дЕЛЬФИ КОМПОНЕНТЫ стандартные слабоваты это факт. Говорят, что ни в одной компании, которая софт пишет, не юзают стандатртных колмпонентов.
Скорее с точностью до наоборот. Использование "навороченных" компонентов без особой необходимости - уже не есть хорошо. Да и компаний "которая софт пишет" и при том использует делфи (или билдер - один хрен) можно пересчитать по пальцам одной руки - в основном это удел самопальных проектов мелких отделов разработки с непомерно большими амбициями.
Это ты зря. Довольно много крупных проектов на Delphi/Builder, которые завоевали рынок и до сих пор развиваются (к примеру, ORTEMS). Другое дело, что новых проектов не появляется - предпочитают другие средства разработки.
Что касается использования сторонних компонентов, то тут все верно - не любит народ к внутренним глюкам своего проекта добавлять еще и глюки сторонних компонентов, да и пользователей учить потом... Используются в основном по необходимости подправленные стандартные компоненты. Ну или в крайнем случае создаются свои).
makbeth, здорово. давно тебя не видно. ))
[/offtop]
может быть - мне лично не приходилось сталкиваться с требованием к разработчику - знание Делфи/Билдер - хотя есть исключение - мелкие банки и собственные отделы разработок компаний. Так что это ИМХО и не холивара ради. :)
Да не, я и сам не в пику ответил ;) Все это в общем то объяснимо. В России крупных девелоперских контор то и нет. Все как раз и ограничивается собственными "шарашками" (кстати, для мелкого софта Delphi самое оно - дешево). А "в европах" борланд, к сожалению, не смог конкурировать с Microsoft и Sun. Ну эта тема тоже пережевана тыщу раз :)
Ну и чтобы совсем не скатиться в оффтоп...
Yurec, вот оффсайт Virtual Trieview: http://www.soft-gems.net/
Там, кстати, можно найти и VT, заточенный под базы данных.
Стандартный TListView же никак не подходит под отображение записей БД, поскольку получается такое нехилое дублирование данных (по крайней мере, в твоем варианте).
Да не, я и сам не в пику ответил ;) Все это в общем то объяснимо. В России крупных девелоперских контор то и нет. Все как раз и ограничивается собственными "шарашками" (кстати, для мелкого софта Delphi самое оно - дешево). А "в европах" борланд, к сожалению, не смог конкурировать с Microsoft и Sun. Ну эта тема тоже пережевана тыщу раз :)
Ну и чтобы совсем не скатиться в оффтоп...
Yurec, вот оффсайт Virtual Trieview: http://www.soft-gems.net/
Там, кстати, можно найти и VT, заточенный под базы данных.
Стандартный TListView же никак не подходит под отображение записей БД, поскольку получается такое нехилое дублирование данных (по крайней мере, в твоем варианте).
Ок, спасибо, я учту это!
Есть такой глюк в TListView:
1. Создаем vsreport TlistView
2. Пишем такоф обработчик при нажатии на колонку OnColumnClick
procedure TNotificationClass.MyListViewColumnClick(Sender: TObject;
Column: TListColumn);
ShowMEssage(Column.Caption);
end;
3. Запускаем прогу, жмем на колонку, получаем имя (точнее Caption) колонки
4. Перетаскиваем колонку (надо чтобы было включено заранее свойство FullDrag)
5. Жмем на колонку и получаем заголовок совсем другой колонки.
Модеоируется ошибка "на УРА". То есть до полного Delphi не переставлляет колонки у себя в голове, м в качестве Column: TListColumn передается старая колонка, которая ранбше стояла на этом месте.
Как победить:
Просто вычилим сами по какой колонке жмакнули:
procedure TNotificationClass.MyListViewColumnClick(Sender: TObject;
Column: TListColumn);
var
x,i,vorder: Longint;
vstr:string;
begin
x:=c_ListView.ScreenToClient(mouse.CursorPos).x;
for i:=0 to c_ListView.Columns.Count-1 do begin
vleft:=vleft+c_ListView.Columns.Width;
if vleft > x then begin
vorder:=i;
vstr:=c_ListView.Columns.Caption;
break;
end;
end;
ShowMessage('Нажали на колонку №'+inttostr(vorder)+chr(10)+'Заголовок ее '+vstr );
end;
качайте TVirtualTreeView с http://www.soft-gems.net/
хороший материал о том, как его использовать тут:
http://forum.vingrad.ru/act-Print/client/html/f-84/t-97620.html
Добавлю здесь, чтоб было все в одном месте, хоть и на плюсах
//---------------------------------------------------------------------------
inline bool __fastcall IsEnu(char Symbol)
{
return ((64 < Symbol && Symbol < 91) || (96 < Symbol && Symbol < 122));
}
//---------------------------------------------------------------------------
inline bool __fastcall IsRus(char Symbol)
{
return ((-65 < Symbol && Symbol < 0) || (Symbol == -88)); //char(-88) = 'ё'
}
//---------------------------------------------------------------------------
void __fastcall DrawTextSpec(TCanvas *pCanvas, TRect &Rect, AnsiString Text)
{
int offset = 3;
for (int i = 1; i <= Text.Length(); ++i)
{
if (IsEnu(Text)) pCanvas->Font->Color = RGB(127, 51, 0);
else if (IsRus(Text)) pCanvas->Font->Color = RGB(13, 107, 0);
else pCanvas->Font->Color = clWindowText;
if (i != 1) offset += pCanvas->TextWidth(Text[i-1]);
pCanvas->TextOut(Rect.Left + offset, Rect.Top, Text);
}
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::ListViewDrawItem(TCustomListView *Sender,
TListItem *Item, TRect &Rect, TOwnerDrawState State)
{
Sender->Canvas->Font->Color = clWindowText;
DrawTextSpec(Sender->Canvas, Rect, Item->Caption);
TRect recSubItem;
for (int i = 0; i < Item->SubItems->Count; ++i)
{
ListView_GetSubItemRect(ListView->Handle, Item->Index, i+1, LVIR_BOUNDS, &recSubItem);
DrawTextSpec(Sender->Canvas, recSubItem, Item->SubItems->Strings);
}
}