Статья
Версия для печати
Обсудить на форуме
   Класс для отображения табличных данных CGridEdit1153


Автор: Алексей1153


   В этой статье (под редакцией аж № 4) представлен класс CGridEdit1153 (далее - просто компонент), написанный в среде VC++6 с использованием библиотеки MFC , предназначенный для вывода на экран простых табличных данных.
   Класс изначально специально создавался для проекта, где необходимо выводить на экран МНОГО строк (несколько миллионов строк - суровая реальность) и затачивался под быстрое добавление новых данных и перерисовку без мерцания (в том проекте эта таблица - быстро заполняющийся лог сообщений от сотен охранных приборов, пользователь-оператор мониторит таблицу и принимает решения, и, соответственно, моргание утомляло бы глаза, а тормоза - вообще недопустимы).

   Статья и класс были переписаны с учётом найденных ошибок, спасибо тестерам Lotor, Hobotanius (может ещё кого то забыл ).   Также переделана система обработки сообщений, добавлено динамическое изменение размера таблицы, произведена оптимизация по потреблению ОЗУ.

   В классе CGridEdit1153 поддерживается:
   1) задание шрифта в таблице и в заголовке
   2) выравнивание текста в заголовке
   3) задание цвета фона для каждой ячейки
   4) задание цвета текста для каждой ячейки
   5) прямое редактирование текста ячейки
   6) динамическое изменение количества строк и столбцов (с сохранением данных в оставшихся  ячейках)

   Для экономии ОЗУ используется палитра цветов - об этом будет рассказано далее.

   Из "минусов" (имхо - не совсем нужная роскошь):
   1) не поддерживается объединение ячеек
   2) не поддерживается различная высота строк
   3) нет действий над группой ячеек при помощи курсора мыши

   Таблица не универсальна, конечно, но все минусы решаемы, потому что код открыт. А эта статья является неким мануалом по вставке в проект и использованию компонента, что поможет разобраться в исходниках.

   Исходный код компонента представлен в 4 файлах (их можно взять из файлов проекта, который находится по ссылке в конце статьи):
GridEdit1153.h заголовочный файл класса компонента.
GridEdit1153.cpp файл реализации класса компонента.
EnterEditDialog.h заголовочный файл диалога CEnterEditDialog
EnterEditDialog.cpp файл реализации диалога CEnterEditDialog
   Вспомогательный класс CEnterEditDialog предназначен для прямого редактирования текста ячеек. Он является классом, производным от класса CDialog. Для него нужен диалог-ресурс, который сделать очень просто при помощи редактора ресурсов:
   1) вставляем в ресурсы новый диалог, ID == IDD_ENTER_EDIT_DIALOG.
   2) удаляем всё с поверхности диалога (хотя, можно и не удалять, само всё удалится в классе).
   3) у диалога в свойствах -> окно Border -> устанавливаем None.

Создание тестового проекта

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

   Итак, создаём тестовый проект GridTest (MFC , Dialog-based). Копируем в папку с проектом 4 файла, описанных выше. Добавляем файлы также в дерево файлов проекта. Добавляем ресурс для диалога, как это описано выше. После этого рекомендуется удалить из папки проекта файл "имя_проекта.clw" (можно даже не закрывая студию). Затем в студии нажать Ctrl+W для того, чтобы обновилось дерево классов.
   Описываемый класс является производным от MFC-класса CStatic. Поэтому, чтобы поместить компонент на диалоговое окно в режиме редактирования ресурсов, кладём сначала на диалог CStatic из стандартной  палитры элементов управления. Зададим идентификатор IDC_GRID. Устанавливаем размер и положение будущего грида. (Потом, конечно, можно будет поменять программно при помощи CWnd::MoveWindow() ) Можно также по желанию поставить в свойствах статика галочку Sunken - будет модная каёмочка вокруг грида ).

   Добавляем для статика связанный с ним член-переменную: удерживая Ctrl дважды щелкаем по статику, пишем имя переменной m_IDC_GRID , в окне Category -> выбираем Control,  в окне Variable Type -> выбираем наш класс CGridEdit1153. Также не забываем добавить в заголовочный файл диалога перед описание класса диалога строчку:
Код:
#include "GridEdit1153.h"
   Компилируем проект (нет ли ошибок ?) и сохраняем.

   Перед началом работы с компонентом, необходимо вызвать метод Create(). Если этого не сделать, компонент отобразит себя в виде белого прямоугольника с красным квадратиком в левом верхнем углу.

   Теперь надо создать (инициализировать то есть) грид. Если бы главное окно было потомком от CView , то создание компонента нужно было делать в методе OnInitialUpdate(). Ну а так как у нас диалог, то делаем создание в методе диалога OnInitDialog() (обработчик сообщения WM_INITDIALOG). В примере представлено заполнение большого количества настроек сразу (на то и пример), но всё это можно делать в любой последующий момент времени, а не только в этом обработчике. Самое главное - тут должна быть вызвана Create().

Код:
BOOL CGridTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();

//инициализация грида/////////////
CGridEdit1153* pGR=&m_IDC_GRID;

//создаём грид1153
if(pGR->Create(this))
{
//задание настроек и параметров

//создаём грид1153
if(pGR->Create(this))
{
//задание шрифта 12 , Times New Roman
LOGFONT lf={
-(12*LOGPIXELSY)/72,
0,0,0,FW_NORMAL,0,0,0,RUSSIAN_CHARSET,
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
DRAFT_QUALITY,FIXED_PITCH,
"Times New Roman"
};

//задаём шрифт таблицы
pGR->SetTableLogFont(&lf);

//задаём шрифт заголовков
pGR->SetHeaderLogFont(&lf);

//высота ячеек
pGR->SetRowHeight(20);

//сдвиг при вращении колеса мыши - 3 строки
pGR->SetWheelMoveDistance(3);

//настройка обработчиков
pGR->SetMessageCallBack(CGridEdit1153::mes_OnLButtonDblClk, grid_mes_OnLButtonDblClk);
pGR->SetMessageCallBack(CGridEdit1153::mes_OnMouseWheel, grid_mes_OnMouseWheel);

//включить обработку сообщений
pGR->EnableNotifyParent(true);

//установка количества строк и столбцов
if(!pGR->SetRowsCols(1000,10,true,true))
{
return 0;
}

//тексты заголовков
CString txt;
for(DWORD i=0;i<pGR->GetCols();i++)
{
txt.Format("%d",i);
pGR->SetColText(i,txt);
}

//все колонки делаем шириной 30
pGR->SetColWidth_ToAll(30);
}

//....
//....

Обработка сообщений от таблицы

   Обработка сообщения от грида реализована через функции обратного вызова (callback). Имеется следующий набор сообщений (его вы , конечно же, можете расширить):
mes_OnLButtonDown Нажатие левой кнопкой мыши на клетку
mes_OnRButtonDown Нажатие правой кнопкой мыши на клетку
mes_OnMButtonDown Нажатие средней кнопкой мыши на клетку
mes_OnLButtonDblClk Двойной щелчок левой кнопкой мыши по клетке
mes_OnRButtonDblClk Двойной щелчок правой кнопкой мыши по клетке
mes_OnRandLButtonDown Одновременное нажатие левой и правой кнопкой мыши на клетку
mes_OnUpSqPressed Нажатие кнопки грида "наверх" (верхний правый угол грида)
mes_OnCellChange Сменилось положение курсора-рамки
mes_OnVScroll_slidermove Передвинут ползунок горизонтальной полосы прокрутки
mes_OnVScroll_arrowup Нажата стрелка "вверх" горизонтальной полосы прокрутки
mes_OnVScroll_arrowdown Нажата стрелка "вниз" горизонтальной полосы прокрутки
mes_OnVScroll_placeup Нажата область выше ползунка на горизонтальной полосе прокрутки
mes_OnVScroll_placedown Нажата область ниже ползунка на горизонтальной полосе прокрутки
mes_OnVirtualKeyUp Нажата кнопка клавиатуры
mes_OnVirtualKeyDown Отпущена кнопка клавиатуры
mes_OnTRACKING Идет изменение ширины столбца мышью (за разделитель в заголовке)
mes_OnHScroll_MOVED Перемещён ползунок горизонтальной полосы прокрутки
mes_OnMouseWheel Было повёрнуто колесо мыши

   Если функция-обработчик не задана для сообщения, то выполняется встроенный обработчик. Функция-обработчик задаётся методом CGridEdit1153::SetMessageCallBack. Прототип :
Код:
bool SetMessageCallBack(
ee_callbacksMessages messID,
type_GridCB pFunc=0,
BOOL B_DontRunIfCallbackIsNull=0,
bool bDisableNotify=true
);

   Описание параметров:
ee_callbacksMessages messID ID сообщения
type_GridCB pFunc имя статической функции типа CGridEdit1153::type_GridCB
BOOL B_DontRunIfCallbackIsNull если задать флаг равным 1, то при pFunc==0 не будет выполняться встроенный обработчик
bool bDisableNotify отключает отправку сообщений родительскому окну

   В обработчик передаётся указатель на структуру типа const CGridEdit1153::sCBinfo , содержащую всю необходимую информацию о сообщении
ee_callbacksMessages m_eeID ID сообщения
CGridEdit1153* m_pGrid указатель на грид, от которого сообщение пришло
CWnd* m_pParent указатель на родительское окно грида
DWORD m_dwdLogicNumberOfRows мгновенное логическое количество строк в таблице
UINT m_nChar символ для сообщений от клавиатуры
CVBarInfo* m_pVBI указатель на инфо о вертикальной полосе прокрутки

m_pVBI указывает на структуру CVBarInfo, содержащую:
Код:
	//границы для вертикальной полосы прокрутки
//(тип переменных - const int)
minpos //минимальная позиция ползунка
maxpos //максимальная позиция ползунка

//при перетаскивании ползунка вертикальной полосы прокрутки
//(тип переменных - const int)
posfrom //позиция, откуда начато перемещение
poswhere //позиция, куда ползунок переместили

//для нажатия стрелок и пространства снизу или сверху от ползунка
//вертикальной полосы прокрутки
//(тип переменных - const int)
tomovevalue //на сколько требуется подвинуть ползунок (со знаком)

   Что значит "мгновенное логическое количество строк в таблице" ? К примеру, создана таблица на 100 строк. Но заполнены ещё только первые 60. В данном случае , 60 - это и есть логическое количество строк, только они будут участвовать в прокрутке. Остальные 40 строк в конце - они не будут видны на экране. Значение 0 в dwdLogicNumberOfRows равносильно указанию максимального количества строк (100).
   Если обработчик возвращает 0, то встроенный обработчик не выполняется. Если же вернёт 1, то после этого выполниться ещё и встроенный обработчик.

   В обработчике сообщения mes_OnLButtonDblClk у нас сделано непосредственное редактирование содержимого ячейки.
Код:
//обработчик сообщения CGridEdit1153::mes_OnLButtonDblClk
BOOL CGridTestDlg::grid_mes_OnLButtonDblClk(const CGridEdit1153::sCBinfo* pInfo)
{
pInfo->m_pGrid->EditCell(
pInfo->m_pGrid->GetCursorRow_zb(),
pInfo->m_pGrid->GetCursorCol_zb()
);

//обработка по умолчанию не требуется
return 0;
}

"Символ для сообщений от клавиатуры" имеет тип const UINT , это всего лишь значение, полученное в гриде
Код:
CGridEdit1153::OnKeyUp(UINT nChar, ...))

Вертикальный скроллинг колесом мыши

   При прокрутке поворотом колеса стоит обратить внимание на следующий момент: сообщения WM_MOUSEWHEEL посылаются гриду только тогда, когда фокус находится на гриде (например, до этого щёлкнули по нему мышью). Если требуется делать прокрутку независимо от фокуса, но, например, тогда, когда курсор мыши находится над окном таблицы, то в родительском окне в обработчике сообщения WM_MOUSEWHEEL нужно применить метод CGridEdit1153::InParent_OnMouseWheel_hook :
Код:
BOOL CGridTestDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
//для более приятного поведения колёсика мыши
//(для случая, когда грид не в фокусе, а курсор мыши находится над гридом
//этот крюк также произведёт прокрутку)
if(m_IDC_GRID.InParent_OnMouseWheel_hook(nFlags,zDelta,pt))return 1;

return CDialog::OnMouseWheel(nFlags, zDelta, pt);
}

Палитра цветов

   Палитра цветов содержит набор цветов, используемых для отображения текста и фона ячеек.
   Цель применения палитры - экономия ОЗУ. Таким образом, цвет ячейки задан всего одним байтом (или двумя в случае 65535 цветов) а не 4 байтами (как при использовании COLORREF вместо индекса). Когда, к примеру, в таблице миллион строк и 20 колонок, то экономия ОЗУ весьма ощутима (на один только цвет - 40 мегабайт  или 160, а ? ).

Поговорим о том, как выбрать размер палитры цветов.
   В файле "GridEdit1153.h" имеются строки:
Код:
	//выбор размера палитры 
typedef BYTE  CColorIndex;enum {e_CColorIndex_colorsnum=255};    //2...(0xff)
//typedef WORD  CColorIndex;enum {e_CColorIndex_colorsnum=65535};  //2...(0xffff)
//typedef DWORD CColorIndex;enum {e_CColorIndex_colorsnum=16777216}; //2...(0x01000000)
...
enum
{
e_tx_default_0=0,//индекс цвета по умолчанию для текста ячейки
e_bk_default_1=1,//индекс цвета по умолчанию для фона ячейки
...

e_tx_default_color = RGB(0,0,0),
e_bk_default_color = RGB(254,255,254),

};

   В оригинальном варианте открыта строка, где индекс цвета (а каждая ячейка имеет два таких индекса - для фона и для текста) CColorIndex - размером 1 байт. Такой индекс позволяет использовать для цвета текста и фона ячеек максимум 255 цветов одновременно. Для исключительного числа задач этого количества цветов достаточно. Если закрыть первую строчку и открыть вторую (где typedef WORD..), то размер палитры увеличится до 65535 (но и ОЗУ цвет займёт в 2 раза больше, чем при typedef BYTE). Ну, и вряд ли когда то понадобится третья строка , с DWORD, где доступны все 16777216 RGB-цветов. Но вообще говоря, с DWORD тут экономия пропадает, и рациональнее переписать класс CGridElemColor (класс цвета) , а от типа CColorIndex отказаться совсем.
   Палитра реализована в виде класса CElemzPalette. В конструкторе класса сразу создаются 2 цвета - которые используются по умолчанию для цвета фона и цвета текста ячеек.

Код:
		CElemzPalette()
{
m_dwdPaletteLen=0;
::memset(m_a_palette,0,sizeof(m_a_palette));

//заполняем два цвета (которые по умолчанию)
m_a_palette[e_tx_default_0]=e_tx_default_color;
m_dwdPaletteLen++;

m_a_palette[e_bk_default_1]=e_bk_default_color;
m_dwdPaletteLen++;
}


Описание некоторых "настроечных" элементов класса CGridEdit1153


Константы размеров и цветов
Код:
def_RowsMax=10000000,   //максимальное количество строк		//1...2147483647
def_ColsMax=2000,       //максимальное количество колонок //1...2147483647

def_MinColWidth=12,     //минимальная ширина колонки
init_ColWidth=20,       //ширина колонки по умолчанию

def_MinRowHeight=2,     //минимальная высота строки
def_MaxRowHeight=100,   //максимальная высота строки
init_RowHeight=22,      //высота строки по умолчанию

def_nHeaderHeightMin=0, //минимальная высота заголовка
def_nHeaderHeightMax=30,//максимальная высота заголовка

def_nHBarHeightMin=0,   //минимальная высота горизонтальной полосы прокрутки
def_nHBarHeightMax=30,  //максимальная высота горизонтальной полосы прокрутки

def_nVBarWidthMin=0,    //минимальная ширина вертикальной полосы прокрутки
def_nVBarWidthMax=30,   //максимальная ширина вертикальной полосы прокрутки

def_TextEscapeFromLeft=2,//отступ текста от левого края клетки (пикселы)
def_TextEscapeFromRight=2,//отступ текста от правого края клетки (пикселы)

def_TrackLineWid=1,     //толщина трек-курсора (если виден) (пикселы)
init_TrackColor=RGB(0,200,0),//цвет трек-курсора по умолчанию

def_CursorLineWid=2,    //толщина курсора-рамки (пикселы)
init_CursorColor=RGB(50,50,255),//цвет курсора-рамки по умолчанию

init_LeftInfoSpaceDef=20, //ширина информационной области слева от первой колонки
init_LeftInfoSpaceMax=100, //ширина информационной области слева от первой колонки
init_LeftInfoSpaceColor=RGB(220,255,220),////цвет информационной области по умолчанию

init_BkColor=RGB(253,255,253),  //фон клеток по умолчанию
init_TxColor=RGB(0,0,0),        //цвет текста клеток по умолчанию
init_LineColor=RGB(170,190,170),//цвет линий сетки по умолчанию

def_distWhenWheel_min=1,   //величина сдвига таблицы при повороте колеса (строки)
def_distWhenWheel_max=100, //... при повороте колеса  - максимум

def_distWhenH_Arr=2,       //... при нажатии на стрелки гориз. полосы (пикселы)
def_distWhenH_Space=10,    //... при нажатии сбоку от ползунка гориз. полосы (пикселы)

def_distWhenV_Arr=1,       //... при нажатии на стрелки верт. полосы (строки)
def_distWhenV_Space=5,  //... при нажатии сверху/снизу от ползунка гориз. полосы (строки)

   Ширина информационной области слева от первой колонки хранится в переменной m_LeftInfoSpace, начальное значение которой равно init_LeftInfoSpaceDef. Ширину можно задавать в пределах 0...init_LeftInfoSpaceMax методом SetLeftInfoWidth().



Метод OnPaint(). Устранение мерцания при частой перерисовке

   Вся графика класса разделена на 3 области:
Область №1

   Элементы, рисуемые с двойным буфером (без мерцания при частой перерисовке). Область отрисовывается в методе PaintGrid().
   1)фон ячейки по умолчанию заливает всё пространство, ограниченное заголовком, полосами прокрутки и левым краем окна таблицы. Позже частично перекрывается следующими далее элементами:
   2)информационная область слева
   3)неиспользуемая область справа от ячеек таблицы
   4)неиспользуемая область снизу от ячеек таблицы

Область №2

   Дочерние окна
   1)заголовок
   2)вертикальная полоса прокрутки
   3)горизонтальная полоса прокрутки
   
Область №3

   Элементы, рисуемые без двойного буфера
   1)"квадратик" с треугольником, используемый для прокрутки на начало таблицы  (расположен над вертикальной полосой прокрутки)
   2)правый нижний "квадратик" (под вертикальной полосой прокрутки)
   3)левый верхний "квадратик" (слева от заголовка)
   
Описание некоторых методов класса CGridEdit1153

   Теперь пройдёмся немного по коду файлов компонента.
   Файл EnterEditDialog.cpp, файл реализации вспомогательного диалога для прямого редактирования ячеек. Всё необходимое диалогу передаётся в конструктор. Диалог открывается методом DoModal() , и пока не будет закрыт клавишей Enter или Esc , таблица недоступна для пользователя. Впрочем , как и главное окно проекта :) .  
   Файл GridEdit1153.h , заголовочный файл таблицы. Содержим все константы компонента, содержит описание вспомогательных  структур. В принципе, тут тоже всё подробно закомментировано.
   Файл GridEdit1153.cpp, файл реализации таблицы. Опишу моменты, на которые стоит обратить особое внимание.

   В конструкторе , помимо инициализации переменных, задаётся будущий шрифт по умолчанию  - Arial, 15.

 
Код:
bool SetRowsCols(
  DWORD rows,
  DWORD cols,
  bool bRedimIfAlreadyCreated=true,
  bool bSaveContentIfRedim=true
  );
   Здесь создаётся динамический массив с ячейками. Если указать bRedimIfAlreadyCreated==true, то функция в любом случае выполнит пересоздание массива с новыми параметрами. Если указать false, то функция завершится, ничего не создавая нового и вернёт false. Флаг bSaveContentIfRedim позволяет указать, нужно ли сохранить содержимое таблицы (тексты, цвета, ширина колонок) , естественно, которое останется после изменения размера.


Код:
void ClearTable(
  DWORD begrow_zb=0,
  DWORD endrow_zb=0xffffffff
  );
   Функция просто очищает ячейки от текста, не удаляя массив ячеек.

 
Код:
BOOL Create(
  CWnd* pParent,
  int nControlID_whenDinamicCreationOnly=0xffff
  );
   Тут создаём и инициализируем заголовок m_Header и полосы прокрутки m_HBar и m_VBar.

 
Код:
void SendNotifyMessToParent(
  ee_callbacksMessages eeID,
  UINT nChar=0xffffffff
  );
   Отправка сообщения родительскому окну. Здесь вызывается callback-функция, если назначена сообщению, либо выполняется встроенный обработчик, если он не выключен флагом B_DontRunIfCallbackIsNull метода SetMessageCallBack (смотри выше по тексту)


    Обработчики событий по умолчанию выполняют следующее:
   1) mes_OnUpSqPressed - прокрутить таблицу в самый верх
   2) mes_OnVScroll_slidermove - прокрутка в соответствии с положением ползунка вертикальной полосы прокрутки (логическое количество строк == все строки).
   3) mes_OnMouseWheel - прокрутка вверх/вниз на 1 строку
   4) mes_OnVScroll_arrowup,mes_OnVScroll_arrowdown - прокрутка вверх/вниз на 1 строку.
   5) mes_OnVScroll_placeup, mes_OnVScroll_placedown - прокрутка вверх/вниз на 5 строк.
   6) mes_OnVirtualKeyDown - передвижение курсора по таблице при помощи стрелок клавиатуры.

Код:
bool SetUserCanChangeProperty(
  bool b
  );
   Разрешает/запрещает пользователю вызвать меню настроек грида (щелчок правой кнопкой по нижнему правому квадратику грида). В обработчике сообщения WM_RBUTTONUP таблицы.

Код:
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
   В OnRButtonUp() создаётся контекстное меню. Сейчас там один пункт, да и то холостой. Если вам потребуется, создавайте своё меню и наполняйте его смыслом.

Код:
bool GetCellRowColRectOfPoint(
  CPoint point,
  DWORD* row,
  DWORD* col
  );
   Определяет положение клетки по координатам точки на гриде (координаты точки - относительные для грида, то есть левый верхний угол таблицы - 0,0).

Код:
bool GetCellRect(
  RECT& r,
  DWORD row,
  DWORD col,
  bool bAbsolutCoord=true
  );
   Получить прямоугольник клетки (абсолютные/относит координаты , в зависимости от флага bAbsolutCoord).

Код:
void EditCell(
  DWORD row,
  DWORD col
  );
   Открыть окошко для прямого редактирования текста ячейки.

Код:
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
   Здесь реагируем на полосы прокрутки.

Код:
virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); 
   OnNotify() Обрабатывает сообщения от элемента заголовка.
   Двойной щелчок по разделителю заголовка - выравниваем ширину колонки по ширине текста максимальной длины в видимой части колонки.         
   Двойной щелчок по заголовку колонки - уменьшаем ширину колонки до минимума.





   FAQ


   Q: С какого числа начинаются индексы колонок и столбцов ?
   A: С нуля.

   Q: Что означает постфикс "_not_saved" у некоторых методов?
   A: Означает, что для ускорения работы в этих методах не производится проверка на существование колонок и столбцов с индексами (и их диапазонами), которые (индексы) передаются в метод. Поэтому, перед вызовом методов необходимо убедиться в существовании индексов методом GetCellsAreBeing() . Например, это можно сделать один раз перед "долгим" циклом.

   Q: Каково максимальное количество строк и столбцов ?
   A: Это задаётся в константах CGridEdit1153::def_RowsMax и CGridEdit1153::def_ColsMax . Эти константы можно менять от 1 до INT_MAX (ну, в разумных пределах :) ).

   Q: В статье описано, как создать экземпляр грида при помощи визарда студии, а можно ли это сделать полностью в динамике - через new ?
   A: Можно.
Код:
	CGridEdit1153* m_pGrid;
...
...
m_pGrid = new CGridEdit1153;
m_pGrid->Create(указатель_на_родителя, идентификатор_грида_на_диалоге);
m_pGrid->MoveWindow(...);//размещаем, где нужно
m_pGrid->ModifyStyle(0 ,SS_SUNKEN | SS_NOTIFY, 0);
//SS_NOTIFY - обязательно. Хотя это свойство всё равно установиться в Create автоматически, но для порядку ставьте.
//SS_SUNKEN - если охота лёгкую рамочку вокруг грида

...
...

   Q: Никак не могу рассчитать точное значение позиции ползунка, соответствующее нужному сдвигу таблицы.
   A:  На самом деле, положение горизонтального ползунка - это вторичная информация, которая лишь показывает примерное положение верхней видимой строки таблицы по отношению к строкам всей таблицы. Когда двигают ползунок - сообщение mes_OnVScroll_slidermove "выдаёт" в процентах "откуда" и "куда" сдвинули (смотри методы структуры CGridEditUP1153::CVBarInfo m_VBI). Если же таблицу сдвинули по горизонтали не ползунком, то для коррекции полосы нужно вызвать CGridEditUP1153::CorrectVBarSliderPos().

   Q: По умолчанию курсор можно передвигать стрелками. Мне это не нужно, как остановить передвижение стрелками, не определяя обработчик, возвращающий 0 ?
   A:  Можно установить для сообщения флаг, который при отсутствии переопределённого обработчика не будет вызывать встроенный обработчик:
Код:
 
//отключение встроенного обработчика
pGrid->SetMessageCallBack(CGridEdit1153::mes_OnVirtualKeyDown, 0, 1,false);

   Q: Что за странное названия класса?
   A: Символы "Grid" говорят сами за себя - это таблица. Остальные символы ничего не говорят, но зато название вряд ли где повторится :) А вот так захотелось.

   Q: Почему 4-я редакция ?
   A: Совершенству нет предела, нет его и тут. Но серьёзно переделывать вроде больше не собираюсь. Если только глюко-косметические поправки.


   Весь тестовый проект (вместе с файлами класса, они лежат в папке проекта) можно найти здесь.

   Обсуждение и замечания можно произвести в уже существующей  теме.

Версия для печати
Обсудить на форуме