Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Windows Forms »

Обсудить на форуме Обсудить на форуме

Написание простого грида для WinForms на основе ListView (virtual mode)
Поставяемый в составе Framework SDK DataGrid контрол имеет мощную функциональность и почти всем хорош. Но есть у него определенные недостатки, присущие всем гридам такого типа...
Поставляемый в составе Framework SDK DataGrid контроль имеет мощную функциональность и почти всем хорош. Но есть у него определенные недостатки, присущие всем гридам такого типа:
  • DataGrid как и многие его аналоги прежде всего заточен для In-place редактирования-заведения записей, что не всегда хорошо (я лично просто ни когда не использую эту возможность редактируя записи в диалогах). Для того, чтобы запретить это редактирование надо сильно постараться.
  • Нет стандартной возможности выводить значки в ячейках (можно написать свой ClumnStyle, но опять же это не просто)
  • Самое главное. Представим себе открытие формы, на которой лежит грид, отображающий содержимое таблицы в 100 000 записей. Для открытия формы необходимо закачать все 100 000 записей в память, что занимает достаточно серьезное время (точно более 10 секунд). Обновить содержимое грида - опять таки жать перезакачки.

Последнее соображение - самое главное. Ждать 10-20 секунд каждый раз, когда надо обновить форму это слишком. Причем существует альтернативный путь, который реализован в стандартном LuistView control - так называемый virtual mode. В данном режиме контроль сразу задается общее количество записей, но текст каждой записи контроль запрашивает по мере необходимости, когда нужно отобразить соответствующую запись. В данном случае нет необходимости сразу закачивать все записи в память, можно открыть курсор и качать их по мере запросов ListView.

К сожалению, ListView класс из Framework SDK не позволяет использовать данный режим - он просто не обвязан соответствующими функциями. Ниже, я покажу, как можно доработать класс ListView для того, чтоб использовать данный режим.

Итак, создадим класс, наследованный от ListView:

	public class MyList : ListView {
	}

Прежде всего, нужно включить в данном классе режим virtual mode, это необходимо сделать задав соответствующий стиль окна при СОЗДАНИИ (нельзя включать-выключать этот стиль когда окно уже создано). Это делается переписыванием свойства CreateParams:

protected override CreateParams CreateParams {
            get{
                CreateParams p = base.CreateParams;
                p.Style |= 0x1000;
                return p;
            }
        }

Далее, в конструкторе класса надо задать стандартные стили ListView необходимые нам для работы. Я использую FullRowSeect, SingleSelect , Report режимы. Впрочем, это дело вкуса.

Далее, нам понадобится возможность задавать количество элементов в списке. Добавим метод:

const int LVN_FIRST =unchecked ((int)0xFFFFFF9C);
const int LVM_SETITEMCOUNT = LVM_FIRST + 47;

[DllImport("user32.dll")]
static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, Int32 lParam);

public void SetCount(int Count) {
            SendMessage(this.Handle , (Int32)LVM_SETITEMCOUNT, 1000, 0);
}

С этого момента следует заметить, что мы не используем коллекцию Items класса ListView. Сам ListView с этого момента не обращается к данной коллекции. Хранение данных следует вести самому

Итак, мы задали количество элементов. Теперь ListView будет запрашивать данные о конкретном элементе путем посылки сообщения родительскому окну. НО ! Родительское окно чаще всего не имеет представления, о том, где брать данные и т.д. Гораздо удобнее всю работу сосредоточить в самом классе, не закладываясь на то, что его родитель что то знает о сообщениях и элементах. Сделать это можно , воспользовавшись message reflection механизмом посылки WM_NOTIFY сообщений. Вкратце, перед тем как послать это сообщение родителю контроль посылает его сам себе, смещая номер сообщения на некое число.

Итак, нам необходимо перехватить WM_NOTIFY + N сообщение. Для этого переписываем виртуальную ф-ию WndProc:

// Здесь описания номеров сообщения
const int WM_USER = 0x0400;
const int WM_NOTIFY = 0x004E;
const int WM_REFLECT = WM_USER + 0x1C00;
const UInt32 LVN_GETDISPINFO = 0xFFFFFF4F;
const int WM_DESTROY = 0x02;
// Здесь описания необходимых структур, приходящих в LPARAM сообщений
[StructLayoutAttribute(LayoutKind.Sequential)]
        struct NMHDR {
            public UInt32 hwndFrom; 
            public UInt32 idFrom; 
            public UInt32 code; 
}
[StructLayoutAttribute(LayoutKind.Sequential)]
        struct NMLVDISPINFO {
            public UInt32 hwndFrom; 
            public UInt32 idFrom; 
            public UInt32 code; 
            public UInt32 mask;
            public Int32  iItem;
            public Int32  iSubItem;
            public UInt32  state;
            public UInt32  stateMask;
           [MarshalAs(UnmanagedType.LPWStr)] public String pszText;
           public Int32 cchTextMax;
            public Int32  iImage;
            public Int32  lParam;
}

protected override void WndProc(ref Message m){
            switch ( m.Msg ) {
                case (WM_NOTIFY + WM_REFLECT): 
                    NMHDR hdr = (NMHDR)m.GetLParam (typeof(NMHDR));
                    switch ( hdr.code ) {
                        case LVN_GETDISPINFO:
                            NMLVDISPINFO inf = (NMLVDISPINFO)m.GetLParam(typeof(NMLVDISPINFO));
                            inf.pszText = m_Items[inf.iItem];
                            inf.iImage = 0;
                            Marshal.StructureToPtr(inf, m.LParam , false);
                            break;
                        //case 
                        default:
                            break;
                    }
                    break;
                case WM_DESTROY:
                    break;;
                default:
                    base.WndProc (ref m);
                    break;
            }
        }

Итак ! Что мы видим в коде. Прежде всего, перехватываем сообщение WM_DESTROY и НЕ зовем базовый класс. Это сделано вот почему: если у вас есть выделенная строка, то по закрытию ListView пытается освободить некоторые внутренние структуры (список SelectedItems), которые ни кто не заполнял (потому как с Items ни кто не работает). Возникает исключение.

Далее, мы перехватываем сообщение WEM_NOTIFY + WM_REFLECT, приводим LPARAM к структуре NMHDR и проверяем, что за сообщение пришло:

NMHDR hdr = (NMHDR)m.GetLParam (typeof(NMHDR));
switch ( hdr.code )

Если это LVN_GETDISPINFO то мы приводим LPARAM к структуре NMLVDISPINFO и заполняем inf.pszText поле. (тут должен стоять код, извлекающий откуда то данные. Лично я тут проверяю, закачаны ли уже записи по inf.iItem включительно, если нет, делаю fetch нужное количество раз и подставляю запись) После чего структуру мы должны запаковать обратно посредством Marshal.StructureToPtr(inf, m.LParam , false); вызова.

ВНИМАНИЕ ! У меня сложилось впечатление, что заполняя поле inf.pszText некоей строкой туда кладется указатель на память , которая содержит данную строку. Поэтому необходимо следить, чтобы данная строка не очищалась сборщиком мусора до конца работы.

Примечание ! Иногда (я не уловил в каких случаях) операция NMLVDISPINFO inf = (NMLVDISPINFO)m.GetLParam(typeof(NMLVDISPINFO)); приводит к исключению. Я решил эту проблему просто:

try {
NMLVDISPINFO inf = (NMLVDISPINFO)m.GetLParam(typeof(NMLVDISPINFO));
catch (Exception) {
return;
}

На функциональность не влияет, возникает очень редко. В чем дело - пока не понял, но данный ход спасает от этой проблемы.

Теперь мы получили почти полноценный nonedit datagrid . Осталось добавить в него код, позволяющий переносить окошки, включать-выключать сортировки по нажатию на заголовок, сохранять размер и порядок окошек. Но это не столь сложно и каждый может самостоятельно проделать это.


Может пригодится:


Автор: Dins
Прочитано: 10200
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру