|
|
|
||||||||||||
|
|
Собственный, легко управляемый и профессионально разработанный сайт – необходимый элемент любого бизнеса. Находитесь ли Вы в командировке, в дороге, дома или на отдыхе имея компьютер, подключенный к сети Internet, Вы получаете доступ к управлению Вашим сайтом! |
|||
Unbound DBGrid на VisualC++Статьи → Программирование на С/С++Или о том, как не мешает иногда поразмыслить О, сколько нам открытий чудных, Предупреждение: данная статья предназначена для прочтения лицам, достигшим совершеннолетия, без психических отклонений. Вы читаете эту статью на свой страх и риск. За возможные отрицательные последствия автор ответственности не несёт. :) И так. Думаю, многие из вас сталкивались с этим элементом управления и ломали себе голову, пытаясь заставить его работать. Я в своё время обыскал весь интернет на эту тему, но безрезультатно, везде упоминается только делфи, вижуал бейсик и тому подобное. Я против этих языков ничего не имею, но решил всё-таки как-то перенести этот элемент в VisualC++ и кое-чего достиг. Разговор пойдёт пока только о режиме Unbound. Создаём пустой диалог, привязанный к нему класс. В редакторе ресурсов вставляем в диалог элемент управления ActiveX DBGrid, нажимаем правой кнопкой, выбираем “DBGrid Control Object -> Edit”, добавляем нужное количество колонок (в принципе, эти и другие настройки можно сделать и программно, но пока воспользуемся редактором ресурсов, так меньше мороки). :) Теперь, в “обычных” свойствах DBGrid, переименовываем колонки, где необходимо – добавляем режим “button” (в этих колонках будет появляться выпадающий список). Обязательно ставим галочки в строках “Allow add new”, “Allow delete”, выбираем режим «Unbound». Всё, первичные настройки закончены. Еще раз нажимаем правой кнопкой на DBGrid, выбираем ”Class Wizard”, переходим в закладку «Member Variables», нажимаем двойным щелчком на строке, содержащей ID этого элемента (я его назвал к примеру IDD_Grid1). Появляется сообщение, что для этого элемента Wizard должен сгенерировать соответствующий класс, нажимаем «да». Создаётся класс CmsDgridCtrl. Теперь, необходимо дать название переменной, которая будет привязана к «DBGrid Control» и обмениваться с ним информацией. Возможно, вы уже доходили до этого момента, но оказывалось, что этот класс не слишком приспособлен для работы с гридом, тем более в режиме «Unbound» :( Я решил ненмого отвлечься от VisualC и обратиться за помощью к самому гриду. Вместе с этим компонентом прилагается справочный файл: C:WindowsHelpDbgrid96.hlp. Открываю, веду поиск на тему “DBGrid unbound”. Нахожу, помимо прочего, примеры обработки четырех сообщений от самого грида: UnboundAddData – добавление записи UnboundDeleteRow – удаление записи UnboundWriteData – обновление отредактированной записи UnboundReadData – чтение данных и отображение при прокрутке или открытии грида. Примеры, естественно, для VBA. После небольших косметических изменений я получил скрипт VBS и сделал простенькую вэб-страничку: ВНИМАНИЕ! Если не появится грид – возможно, он прописан у вас в реестре под другим Clsid`ом, нужно проверить. <HTML> <BODY> <BR> <OBJECT CLASSID="Clsid:00028C00-0000-0000-0000-000000000046" ID=DBGrid1 height=200 width=700 > </OBJECT> <SCRIPT LANGUAGE=VBSCRIPT> Dim UserData() ‘гибкий массив для хранения данных Dim mTotalRows ‘счетчик строк Dim MAXCOLS ‘количество столбцов Sub window_OnLoad() DBGrid1.AllowAddNew=True DBGrid1.AllowDelete=True DBGrid1.AllowUpdate=True DBGrid1.DataMode=1 ‘bound=0, unbound=1 mTotalRows=0 MAXCOLS=2 End Sub Sub DBGrid1_UnboundAddData(ByVal RowBuf, NewRowBookmark) Dim Col mTotalRows = mTotalRows + 1 ReDim Preserve UserData(MAXCOLS - 1, mTotalRows - 1) ‘ изменяем размеры массива ‘ с сохранением данных (счет от нуля) NewRowBookmark = mTotalRows - 1 For Col = 0 To UBound(UserData, 1) If Not IsNull(RowBuf.Value(0, Col)) Then UserData(Col, mTotalRows - 1) = RowBuf.Value(0, Col) Else UserData(Col, mTotalRows - 1) = DBGrid1.Columns(Col).DefaultValue End If Next End Sub Sub DBGrid1_UnboundReadData(ByVal RowBuf, StartLocation, ByVal ReadPriorRows) Dim CurRow, Row, Col, RowsFetched, Incr If ReadPriorRows Then Incr = -1 Else Incr = 1 End If If IsNull(StartLocation) Then If ReadPriorRows Then CurRow = RowBuf.RowCount - 1 Else CurRow = 0 End If Else CurRow = CLng(StartLocation) + Incr End If For Row = 0 To RowBuf.RowCount - 1 If CurRow < 0 Or CurRow >= mTotalRows Then Exit For For Col = 0 To UBound(UserData, 1) RowBuf.Value(Row, Col) = UserData(Col, CurRow) Next RowBuf.Bookmark(Row) = CStr(CurRow) CurRow = CurRow + Incr RowsFetched = RowsFetched + 1 Next RowBuf.RowCount = RowsFetched End Sub Sub DBGrid1_UnboundWriteData(ByVal RowBuf, WriteLocation) Dim Col For Col = 0 To MAXCOLS - 1 If Not IsNull(RowBuf.Value(0, Col)) Then UserData(Col, WriteLocation) = RowBuf.Value(0, Col) End If Next End Sub Sub DBGrid1_UnboundDeleteRow(Bookmark) Dim Col, Row For Row = Bookmark + 1 To mTotalRows - 1 For Col = 0 To MAXCOLS - 1 UserData(Col, Row - 1) = UserData(Col, Row) Next Next mTotalRows = mTotalRows - 1 End Sub </SCRIPT> </BODY> </HTML> Комментарии к коду почти не пишу - и так более-менее понятно, что к чему :) Получилась вполне работоспособная страничка, правда, без выпадающих списков, но об этом – позже. После пятиминутного созерцания кода меня озарило: «В Unbound режиме грид генерирует Unbound-сообщения, и если их правильно обработать, сделать двумерный массив для хранения данных – всё заработает!» С этой гениальной мыслью я вернулся к VisualC. В класс-визарде я нашел такие же сообщения для элемента IDD_Grid1, заготовил функции-обработчики для всех четырёх событий. Вот они: void CAboutDlg::OnUnboundReadDataDbgrid1(LPDISPATCH RowBuf, VARIANT FAR* StartLocation, BOOL ReadPriorRows) { } void CAboutDlg::OnUnboundWriteDataDbgrid1(LPDISPATCH RowBuf, VARIANT FAR* WriteLocation) { } void CAboutDlg::OnUnboundAddDataDbgrid1(LPDISPATCH RowBuf, VARIANT FAR* NewRowBookmark) { } void CAboutDlg::OnUnboundDeleteRowDbgrid1(VARIANT FAR* Bookmark) { } …Так-тааак :( Что это за LPDISPATCH такой? В справочнике MSDN читаю: при помощи этой гадости можно управлять ActiveX элементами. Но как? Искал-искал, много чего нашел, но ничего не понял. Бред :( И еще. RowBuf- это явно указатель на RowBuffer (это, как я понял, суб-объект грида, хранящий данные о текущей строке) В VBS всё просто: Если объект составной – пишем что-то типа Object.SubObject.Value=5. Но в классе, сгенерированном для грида - ни одного упоминания об RowBuffer, Columns, Column и т д, а ведь эти объекты входят в грид… :( И как выдернуть данные из этого ровбуффера?.. Перепробовал я много вариантов – без толку. В общем я забросил эту идею на некоторое время, но не забыл о ней. И вот однажды, роясь в MSDN по каким-то своим делам, я краем глаза заметил что-то об импортировании классов из исполняемых файлов. Оказывается, в класс-визарде есть такая штука: Нажимаем «Add Class» - появляется выпадающий список: “New” и “From a type library”, выбираем второй. Предлагается выбрать файл *.tlb, *.olb, *.dll … Мдя…:( Но вдруг, почувствовав очередное озарение, выбираю опцию «*.*» и ищу этот грид (C:Windowssystem Dbgrid32.ocx) Йесс!!! Я - лучший!!! Класс-визард, как ни в чем не бывало, проглотил этот файл и выдал список классов, которые я могу спокойно подключить к своей суппер-программе. Внушительный, кстати списочек-с :) И ровбуффер присутствует. Естественно, выделяю все классы (про запас типа), жму ОК. И так, что мы имеем: автоматичесски сформированный родной класс грида в файлах msdgridctrl.h & msdgridctrl.cpp и кучу классов, умеющих работать с гридом, но ни чем к нему не подключенных (файлы dbgrid32.h & dbgrid32.cpp) Не мудрствуя лукаво, пишу в файле msdgridctrl.h #include “dbgrid32.h”, а в разделе public: добавляю: RowBuffer rb; Всё спокойно компилируется - очень хорошо. :) Дальше – проще. Мне в голову пришла еще одна идея: В программе может быть несколько диалогов с гридами, и делать обработку каждого грида в классе “родного” диалога – не красиво, лучше все обработки событий полностью написать в классе CmsDgridCtrl, и вызывать их из любого диалога. Иными словами: ……………….. ……………….. void MyDialog::OnUnboundWriteDataDbgrid1(LPDISPATCH RowBuf, VARIANT FAR* WriteLocation) //Это перехват сообщения грида именно в этом диалоге { c_Grid.UnboundWrite(RowBuf,WriteLocation);
} …………… …………… Так. Теперь приступим к массиву. Я решил использовать ColeSafeArray. Он достаточно удобный, многомерный и позволяет работать с данными VARIANT. В msdgridctrl.h пишем: public: COleSafeArray UserData; Теперь, в файле msdgridctrl.cpp, сразу после #includ’ов DWORD numElements[] = {1, 65535}; //20 столбцов, много-много строк, // в общем с запасом подготавливаем параметры для UserData Теперь, нужно вручную объявить и добавить функцию инициализации массива, да и всего грида. void CMsDgridCtrl::init(int NumColumns) { numElements[0]=NumColumns; UserData.Create(VT_VARIANT, 2, numElements); mTotalRows=0; } Правильно. Эту функцию мы и будем вызывать из диалогов, перехватывая, например, WM_INITDIALOG А теперь – сам код: void CMsDgridCtrl::UnboundAdd(LPDISPATCH RowBuf, VARIANT *NewRowBookmark) { int a,Col; long index[2]; VARIANT value; rb.AttachDispatch(RowBuf,FALSE); for(Col=0;Col<numElements[0];Col++) { index[0]=Col; index[1]=mTotalRows; value=rb.GetValue(0,Col); UserData.PutElement(index, &value); } mTotalRows++;//*** rb.DetachDispatch(); NewRowBookmark->vt=VT_I2; NewRowBookmark->iVal=mTotalRows-1; } void CMsDgridCtrl::UnboundRead(LPDISPATCH RowBuf, VARIANT *StartLocation, BOOL ReadPriorRows) { int a,Col,RowsFetched; short Incr; long index[2],Row;//,ubound; VARIANT value; VARIANT CurRow; CurRow.vt=VT_I2; rb.AttachDispatch(RowBuf,TRUE); RowsFetched=0; if(ReadPriorRows) { Incr=-1; }else { Incr=1; } a=0; if ((StartLocation->vt)==1) { if(ReadPriorRows) { CurRow.iVal=(short)rb.GetRowCount()-1; a=0; }else { CurRow.iVal=0; a=0; } } else { CurRow.iVal=(StartLocation->iVal)+Incr; a=0; } a=(rb.GetRowCount())-1; a=a; for( Row = 0;(Row<=a);Row++) { if( (CurRow.iVal<0)||(CurRow.iVal>=mTotalRows) ) { //break; goto err; } for (Col=0;Col<numElements[0];Col++) { index[0]=Col; index[1]=CurRow.iVal; UserData.GetElement(index,&value); rb.SetValue(Row,Col,value); } rb.SetBookmark(Row,CurRow); CurRow.iVal+=Incr; RowsFetched++; } err: rb.SetRowCount(RowsFetched); rb.DetachDispatch(); } void CMsDgridCtrl::UnboundWrite(LPDISPATCH RowBuf, VARIANT *WriteLocation) { int Col; long index[2]; VARIANT value; // AfxMessageBox("UnboundWrite",MB_OK); WriteLocation->vt=VT_I2; rb.AttachDispatch(RowBuf,TRUE); for(Col=0;Col<numElements[0];Col++) { index[0]=Col; index[1]=WriteLocation->iVal; value=rb.GetValue(0,Col); if(value.vt!=VT_NULL) { UserData.PutElement(index, &value); } } rb.DetachDispatch(); } void CMsDgridCtrl::UnboundDeleteRow(VARIANT *Bookmark) { int Col, Row; long index[2]; VARIANT value; for(Row=(Bookmark->iVal)+1;Row<=mTotalRows-1;Row++) { for(Col=0;Col<numElements[0];Col++) { index[0]=Col; index[1]=Row; UserData.GetElement(index, &value); index[0]=Col; index[1]=Row-1; UserData.PutElement(index, &value); } } mTotalRows = mTotalRows - 1; } В общем один-в-один, как наVBS :) Чуть не забыл: каждому init’у по личному destroy() :) void CMsDgridCtrl::destroy() { mTotalRows=0; UserData.Detach(); } при компилировании будут предупреждения о преобразовании типов переменных, да и сам код не ахти, я не волшебник, я учусь :) Так… статейка большая, а написано по сути мало, грид будет просто заполняться введёнными данными, осталось еще организовать удобный опрос значений строк, заполнение массива данными (к примеру из базы данных:)), опрос и изменение значений ячеек, выпадающие списки, картинки, раскраска… много чего еще… Автор: MIB 16.02.2006 |
VSESMI.ru — новости в СМИ.
Tur-Hotel.ru — отзывы об отелях
|
|
Copyright © 2002—2010 ООО "Хостмэйк" Телефон в Москве: +7 (495) 223-46-50 Телефон в Санкт-Петербурге: +7 (812) 448-38-90 Тел./Факс: +7 (8636) 237-836 E-mail: 2006 |