• Реклама

работа с ListView C++ Builder

Болтовня ничего не стоит. Покажите мне код.
Линус Торвальдс

работа с ListView C++ Builder

Сообщение sh4d0ff » 26 фев 2012, 10:52

TListView - один из моих любимых компонентов c++ builder, находится на вкладке Win32 между TreeView и HeaderControl. Я использую его практически во всех своих проектах, очень мощная и функциональная таблица/список.. но с ним нужно разобраться и в этой теме я опишу некоторые интересные возможности ListView.

Я не буду описывать мелочи вроде определения индекса текущего элемента или создание subitem'ов, информации на эту тему полно в интернете, постараюсь раскрыть достаточно сложные моменты, которые когда-либо вызывали у меня трудности.
Изображение
Аватара пользователя
sh4d0ff
Администратор
 
Сообщения: 127
Зарегистрирован: 19 фев 2012, 15:00
Благодарил (а): 2 раз.
Поблагодарили: 0 раз.

Re: работа с ListView C++ Builder

Сообщение sh4d0ff » 26 фев 2012, 10:55

Сортировка

TListView - ViewStyle = vsReport

Если вы сталкивались с задачей сортировки в таблице ListView, то наверняка обратили внимание на ошибку сортировки чисел и вообще кривость этого действа, в этом посте я предоставлю код качественной сортировки колонок в TListView

Для начало нам надо подключить к проекту математику:

Код: Выделить всё
    #include <Math.hpp>


И объявить булеву переменную, которая нам понадобится для определения направления сортировки, а так же техническую переменную типа int - номер столбца:

Код: Выделить всё
    bool rev=true;
    int ColumnToSort;


Обрабатываем события клика по столбцу:

Код: Выделить всё
    void __fastcall TForm1::ListView1ColumnClick(TObject *Sender,
          TListColumn *Column)
    {
    if ((rev==true)||(ColumnToSort != Column->Index)){rev=false;}
    else if((rev==false)||(ColumnToSort != Column->Index)){rev=true;}
    ColumnToSort = Column->Index;
    ((TCustomListView *)Sender)->AlphaSort(); //функция сортировки "стандартная"
    }


Далее обрабатываем событие ListView OnCompare:

Код: Выделить всё
   void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1,
          TListItem *Item2, int Data, int &Compare)
    {
    if (rev==false)
    {
    if ((ColumnToSort == 0)||(ColumnToSort == 1)||(ColumnToSort == 2))
    {Compare = CompareText(Item1->Caption,Item2->Caption);}
    else if ((ColumnToSort == 3)||(ColumnToSort == 4)||(ColumnToSort == 5)||(ColumnToSort == 6)||(ColumnToSort == 7)||(ColumnToSort == 8)||(ColumnToSort == 9))
    {Compare = CompareValue(StrToFloat(Item1->SubItems->Strings[ColumnToSort-1]),StrToFloat(Item2->SubItems->Strings[ColumnToSort-1]));}
    else
    {Compare = CompareText(Item1->SubItems->Strings[ColumnToSort-1], Item2->SubItems->Strings[ColumnToSort-1]);}
    }
    else
    {
    if ((ColumnToSort == 0)||(ColumnToSort == 1)||(ColumnToSort == 2))
    {Compare = CompareText(Item2->Caption,Item1->Caption);}
    else if ((ColumnToSort == 3)||(ColumnToSort == 4)||(ColumnToSort == 5)||(ColumnToSort == 6)||(ColumnToSort == 7)||(ColumnToSort == 8)||(ColumnToSort == 9))
    {Compare = CompareValue(StrToFloat(Item2->SubItems->Strings[ColumnToSort-1]),StrToFloat(Item1->SubItems->Strings[ColumnToSort-1]));}
    else
    {Compare = CompareText(Item2->SubItems->Strings[ColumnToSort-1],Item1->SubItems->Strings[ColumnToSort-1]);}
    }
    }


В этом примере таблица ListView насчитывает 10 столбцов. Столбцы 0, 1, 2 сортируются как текст, а столбцы 3, 4, 5, 6, 7, 8, 9 - как числа, причем если переменная rev имеет значение false, то идет сортировка по возрастанию, если значение переменной равно true, то сортировка идет в обратном порядке, по убыванию.
Изображение
Аватара пользователя
sh4d0ff
Администратор
 
Сообщения: 127
Зарегистрирован: 19 фев 2012, 15:00
Благодарил (а): 2 раз.
Поблагодарили: 0 раз.

Re: работа с ListView C++ Builder

Сообщение sh4d0ff » 26 фев 2012, 10:59

Нестандартное выделение элементов списка

TListView - ViewStyle = vsReport

Так однажды столкнулся с задачей изменить цвет выделения элементов списка в ListView , долго искал в Интернете нужный код... ну и столкнулся с определенными трудностями.. Но обо всем по порядку.

Чтобы "перекрасить" таблицу, нам необходимо обрабатывать событие ListView OnDrawItem, а так же параметр OwnerDrow должен иметь значение true

Код: Выделить всё
    TRect v_Rect;
           TListView *ListView1   = dynamic_cast<TListView *>(Sender);
           TCanvas *Canvas = Sender->Canvas;
                if (Item->Selected==true) //если элемент выделен
                {
                Canvas->Brush->Color = clRed; //цвет выделения
                Canvas->Font->Color  = clBlack; //цвет шрифта
                }
                else //в противном случае
                {
                Canvas->Brush->Color = clBlack; //цвет элемента списка
                Canvas->Font->Color  = clRed; //цвет текста
                }
             v_Rect=Rect;
             v_Rect.Left   = v_Rect.Left+Sender->Column[0]->Width;

             Canvas->FillRect(v_Rect);

             v_Rect=Rect;
             int seek=0;
             Canvas->TextOut(Rect.Left+2,Rect.Top, Item->Caption);

            for(int i=0; i<Item->SubItems->Count;i++)
              {
                seek = seek + Sender->Column[i]->Width;
                v_Rect.Left=seek;
    //вывод текста
                Canvas->TextRect(v_Rect,v_Rect.Left+2,v_Rect.Top,Item->SubItems->Strings[i]);
              }


Этот код позволяет изменить выделение, но есть и две серьезные проблемы, во первых при прокрутке горизонтального скроллинга, данные начинают отображаться некорректно, решения данной проблемы я не нашел (а с вертикальной полосой прокрутки подобной проблемы нет). Вторая проблема - чекбоксы, они просто не прорисовываются.
Решение этой проблемы найдено, более того используя представленный ниже код, можно использовать чеки нестандартного вида, такие какие вам по душе.
На форму необходимо повесить компонент TImageList и добавить туда два bitmap'а - чекбокс с галочкой и и пустой. Модифицируем представленный выше код:
Перед циклом

Код: Выделить всё
    for(int i=0; i<Item->SubItems->Count;i++)


Добавляем следующее:

Код: Выделить всё
    Graphics::TBitmap *pBitmap = new Graphics::TBitmap();
    pBitmap->Transparent = true;
    pBitmap->TransparentColor = clWhite;

    if (Item->Checked==true)
            {
            ImageList1->GetBitmap(1 ,pBitmap);
            }
            else
            {
            ImageList1->GetBitmap(0 ,pBitmap);
            }

    ListView1->Canvas->Draw(3, Rect.top, pBitmap);

    TRect rect = Rect;
    rect.left = Rect.left + pBitmap->Width + 4;
    rect.right = ListView1->Columns->Items[0]->Width - Rect.left;


Теперь проблема решена.
Изображение
Аватара пользователя
sh4d0ff
Администратор
 
Сообщения: 127
Зарегистрирован: 19 фев 2012, 15:00
Благодарил (а): 2 раз.
Поблагодарили: 0 раз.

Re: работа с ListView C++ Builder

Сообщение sh4d0ff » 26 фев 2012, 11:10

Автоматический расчёт ширины столбца (по заголовку)

TListView - ViewStyle = vsReport

Тоже полезный кусок кода. Тут есть одна загвоздка, казалось бы поставил

Код: Выделить всё
ListView1->Column[i]->Width=ListView1->Canvas->TextWidth(ListView1->Column[i]->Caption)+20;


и все... ан нет.. дело в том, что расчёт ширины столбца происходит при создании формы, чтобы обойти эту проблему, после вышеуказанного воткнем следующий код:

Код: Выделить всё
WINDOWPOS p;

p.hwnd = Form1->ListView1->Handle;
p.hwndInsertAfter = HWND_NOTOPMOST;

p.x=0;
p.y=0;
p.cx=0;
p.cy=0;
p.flags=SWP_SHOWWINDOW;
Изображение
Аватара пользователя
sh4d0ff
Администратор
 
Сообщения: 127
Зарегистрирован: 19 фев 2012, 15:00
Благодарил (а): 2 раз.
Поблагодарили: 0 раз.

Re: работа с ListView C++ Builder

Сообщение bmiho » 28 авг 2013, 08:32

По поводу нестандартного выделения элементов списка при ViewStyle=vsReport хочу добавить следующее:
1) Не обязательно для реализации этого использовать событие OnDrawItem и ставить OwnerDraw=true. Достаточно использовать OnCustomDrawItem.
2) Чтобы избавиться от проблемы с горизонтальной прокруткой необходимо самостоятельно обрезать текст, выводимый на канву TListView. И правильно указать смещение слева (с учетом положения ScrollBar).
Вот мой вариант кода:
Код: Выделить всё
void __fastcall TfrmFLink::MainListViewCustomDrawItem(TCustomListView *Sender,
      TListItem *Item, TCustomDrawState State, bool &DefaultDraw)
{
  if(Item->Selected)
  {
   Sender->Canvas->Brush->Color = clGreen; //Цвет выделения
     Sender->Canvas->Font->Color = clRed; //Цвет шрифта у выделенной строчки
  }
  else
   return;  //Если элемент не выделен пусть все идет по умолчанию.

  int mrgWidth=2, mrgHeight=1;

//Получим прямоугольник выделения и зальем его цветом выделения
  TRect VCLRect=Item->DisplayRect(drBounds);
  VCLRect.Left+=mrgWidth;
  VCLRect.Bottom-=mrgHeight;
  Sender->Canvas->FillRect(VCLRect);

//Получим прямоугольник, в который нужно вписать текст первого столбца
  RECT WINRect=VCLRect;
  WINRect.left=VCLRect.Left+mrgWidth;
  WINRect.right=VCLRect.Left+(Sender->Column[0]->Width)-2*mrgWidth;

//На сколько мне известно TCanvas не имеет метода для вывода текста и его обрезания с добавлением
//многоточия (ellipsis). Так что у нас 2 варианта: самим написать такую функцию на основе скажем
//TextRect, либо использовать функцию WinApi DrawText. 1-й вариант плох тем, что нам будет очень
//сложно повторить алгоритм обрезания используемый в TListView по умолчанию и при выделении текста
//мы будем замечать иногда "скачок" окончания.
//(например: не выделенный текст обрезан так: some tex... при выделении получится: some te... и при
//развыделении текст вернется в исходный вид)
//Кроме того 1-й вариант требует написания дополнительного кода.
//Второй вариант хорош всем: в TListView для обрезания строк текста используется именно DrawText и
//мы всегда попадем при обрезке, и своего писать ничего не нужно, так что выбираем 2-й вариант:
  DrawText( Sender->Canvas->Handle, Item->Caption.c_str() , -1, &WINRect, DT_WORD_ELLIPSIS );

//Получим прямоугольники, в которые вписывается текст последующих столбцов.
//(обращаем внимание, что "seek" из кода автора статьи должен инициализироваться не нулем,
//а значением VCLRect.Left - в нем уже учтено смещение ScrollBar)
//Отрисуем текст:
 for(int i=0; i<Item->SubItems->Count; i++)
  {
   VCLRect.Left+=(Sender->Column[i]->Width);
   WINRect.left=VCLRect.Left+2*mrgWidth;
   WINRect.right=VCLRect.Left+(Sender->Column[i+1]->Width)-4*mrgWidth;

   DrawText( Sender->Canvas->Handle, Item->SubItems->Strings[i].c_str() , -1, &WINRect, DT_WORD_ELLIPSIS );
  }

//А вот здесь мы запретим системе продолжить отрисовку выделенного Itema, чтобы не зетереть то,
//что мы уже нарисовали.
  DefaultDraw=false;
}


PS: CheckBoxы для выделенных элементов придется отрисовывать ручками, как в статье, т.к. мы запретили системную прорисовку этих элементов, указав DefaultDraw=false
Аватара пользователя
bmiho
 
Сообщения: 1
Зарегистрирован: 28 авг 2013, 07:46
Благодарил (а): 0 раз.
Поблагодарили: 1 раз.


Вернуться в Статьи

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

cron