]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/common/dbgrid.cpp
Better place for coordinate mirroring and removal
[wxWidgets.git] / src / common / dbgrid.cpp
... / ...
CommitLineData
1///////////////////////////////////////////////////////////////////////////////
2// Name: src/common/dbgrid.cpp
3// Purpose: Displays a wxDbTable in a wxGrid.
4// Author: Roger Gammans, Paul Gammans
5// Modified by:
6// Created:
7// RCS-ID: $Id$
8// Copyright: (c) 1999 The Computer Surgery (roger@computer-surgery.co.uk)
9// Licence: wxWindows licence
10///////////////////////////////////////////////////////////////////////////////
11// Branched From : dbgrid.cpp,v 1.18 2000/12/19 13:00:58
12///////////////////////////////////////////////////////////////////////////////
13
14#include "wx/wxprec.h"
15
16#ifdef __BORLANDC__
17 #pragma hdrstop
18#endif
19
20#if wxUSE_ODBC && wxUSE_GRID
21
22#ifndef WX_PRECOMP
23 #include "wx/textctrl.h"
24 #include "wx/dc.h"
25 #include "wx/app.h"
26#endif // WX_PRECOMP
27
28#include "wx/generic/gridctrl.h"
29#include "wx/dbgrid.h"
30
31// DLL options compatibility check:
32WX_CHECK_BUILD_OPTIONS("wxDbGrid")
33
34
35wxDbGridCellAttrProvider::wxDbGridCellAttrProvider()
36{
37 m_data=NULL;
38 m_ColInfo=NULL;
39}
40
41wxDbGridCellAttrProvider::wxDbGridCellAttrProvider(wxDbTable *tab, wxDbGridColInfoBase* ColInfo)
42{
43 m_data=tab;
44 m_ColInfo=ColInfo;
45}
46
47wxDbGridCellAttrProvider::~wxDbGridCellAttrProvider()
48{
49}
50
51wxGridCellAttr *wxDbGridCellAttrProvider::GetAttr(int row, int col,
52 wxGridCellAttr::wxAttrKind kind) const
53{
54 wxGridCellAttr *attr = wxGridCellAttrProvider::GetAttr(row,col,kind);
55
56 if (m_data && m_ColInfo && (m_data->GetNumberOfColumns() > m_ColInfo[col].DbCol))
57 {
58 //FIXME: this test could.
59 // ??::InsertPending == m_data->get_ModifiedStatus()
60 // and if InsertPending use colDef[].InsertAllowed
61 if (!(m_data->GetColDefs()[(m_ColInfo[col].DbCol)].Updateable))
62 {
63 switch(kind)
64 {
65 case (wxGridCellAttr::Any):
66 if (!attr)
67 {
68 attr = new wxGridCellAttr;
69 // Store so we don't keep creating / deleting this...
70 wxDbGridCellAttrProvider * self = wxConstCast(this, wxDbGridCellAttrProvider) ;
71 attr->IncRef();
72 self->SetColAttr(attr, col);
73 attr->SetReadOnly();
74 }
75 else
76 {
77 //We now must check what we were returned. and do the right thing (tm)
78 wxGridCellAttr::wxAttrKind attrkind = attr->GetKind();
79 if ((attrkind == (wxGridCellAttr::Default)) || (attrkind == (wxGridCellAttr::Cell)) ||
80 (attrkind == (wxGridCellAttr::Col)))
81 {
82 wxGridCellAttr *attrtomerge = attr;
83 attr = new wxGridCellAttr;
84 attr->SetKind(wxGridCellAttr::Merged);
85 attr->MergeWith(attrtomerge);
86 attr->SetReadOnly();
87 attrtomerge->DecRef();
88 }
89 attr->SetReadOnly();
90 }
91 break;
92 case (wxGridCellAttr::Col):
93 //As we must have a Coll, and were setting Coll attributes
94 // we can based on wxdbTable's so just set RO if attr valid
95 if (!attr)
96 {
97 attr = new wxGridCellAttr;
98 wxDbGridCellAttrProvider * self = wxConstCast(this, wxDbGridCellAttrProvider) ;
99 attr->IncRef();
100 self->SetColAttr(attr, col);
101 }
102 attr->SetReadOnly();
103 break;
104 default:
105 //Dont add RO for...
106 // wxGridCellAttr::Cell - Not required, will inherit on merge from row.
107 // wxGridCellAttr::Row - If wxDbtable ever supports row locking could add
108 // support to make RO on a row basis also.
109 // wxGridCellAttr::Default - Don't edit this ! or all cell with a attr will become readonly
110 // wxGridCellAttr::Merged - This should never be asked for.
111 break;
112 }
113 }
114
115 }
116 return attr;
117}
118
119void wxDbGridCellAttrProvider::AssignDbTable(wxDbTable *tab)
120{
121 m_data = tab;
122}
123
124wxDbGridTableBase::wxDbGridTableBase(wxDbTable *tab, wxDbGridColInfo* ColInfo,
125 int count, bool takeOwnership) :
126 m_keys(),
127 m_data(tab),
128 m_dbowner(takeOwnership),
129 m_rowmodified(false)
130{
131
132 if (count == wxUSE_QUERY)
133 {
134 m_rowtotal = m_data ? m_data->Count() : 0;
135 }
136 else
137 {
138 m_rowtotal = count;
139 }
140// m_keys.Size(m_rowtotal);
141 m_row = -1;
142 if (ColInfo)
143 {
144 m_nocols = ColInfo->Length();
145 m_ColInfo = new wxDbGridColInfoBase[m_nocols];
146 //Do Copy.
147 wxDbGridColInfo *ptr = ColInfo;
148 int i =0;
149 while (ptr && i < m_nocols)
150 {
151 m_ColInfo[i] = ptr->m_data;
152 ptr = ptr->m_next;
153 i++;
154 }
155#ifdef __WXDEBUG__
156 if (ptr)
157 {
158 wxLogDebug(wxT("NoCols over length after traversing %i items"),i);
159 }
160 if (i < m_nocols)
161 {
162 wxLogDebug(wxT("NoCols under length after traversing %i items"),i);
163 }
164#endif
165 }
166}
167
168wxDbGridTableBase::~wxDbGridTableBase()
169{
170 wxDbGridCellAttrProvider *provider;
171
172 //Can't check for update here as
173
174 //FIXME: should i remove m_ColInfo and m_data from m_attrProvider if a wxDbGridAttrProvider
175// if ((provider = dynamic_cast<wxDbGridCellAttrProvider *>(GetAttrProvider())))
176 // Using C casting for now until we can support dynamic_cast with wxWidgets
177 provider = (wxDbGridCellAttrProvider *)(GetAttrProvider());
178 if (provider)
179 {
180 provider->AssignDbTable(NULL);
181 }
182 delete [] m_ColInfo;
183
184 Writeback();
185 if (m_dbowner)
186 {
187 delete m_data;
188 }
189}
190
191bool wxDbGridTableBase::CanHaveAttributes()
192{
193 if (!GetAttrProvider())
194 {
195 // use the default attr provider by default
196 SetAttrProvider(new wxDbGridCellAttrProvider(m_data, m_ColInfo));
197 }
198 return true;
199}
200
201
202bool wxDbGridTableBase::AssignDbTable(wxDbTable *tab, int count, bool takeOwnership)
203{
204 wxDbGridCellAttrProvider *provider;
205
206 //Remove Information from grid about old data
207 if (GetView())
208 {
209 wxGrid *grid = GetView();
210 grid->BeginBatch();
211 grid->ClearSelection();
212 if (grid->IsCellEditControlEnabled())
213 {
214 grid->DisableCellEditControl();
215 }
216 wxGridTableMessage msg(this, wxGRIDTABLE_NOTIFY_ROWS_DELETED,0,m_rowtotal);
217 grid->ProcessTableMessage(msg);
218 }
219
220 //reset our internals...
221 Writeback();
222 if (m_dbowner)
223 {
224 delete m_data;
225 }
226 m_keys.Empty();
227 m_data = tab;
228 //FIXME: Remove dynamic_cast before sumision to wxwin
229// if ((provider = dynamic_cast<wxDbGridCellAttrProvider *> (GetAttrProvider())))
230 // Using C casting for now until we can support dynamic_cast with wxWidgets
231 provider = (wxDbGridCellAttrProvider *)(GetAttrProvider());
232 if (provider)
233 {
234 provider->AssignDbTable(m_data);
235 }
236
237 if (count == wxUSE_QUERY)
238 {
239 m_rowtotal = m_data ? m_data->Count() : 0;
240 }
241 else
242 {
243 m_rowtotal = count;
244 }
245 m_row = -1;
246
247 //Add Information to grid about new data
248 if (GetView())
249 {
250 wxGrid * grid = GetView();
251 wxGridTableMessage msg(this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rowtotal);
252 grid->ProcessTableMessage(msg);
253 grid->EndBatch();
254 }
255 m_dbowner = takeOwnership;
256 m_rowmodified = false;
257 return true;
258}
259
260wxString wxDbGridTableBase::GetTypeName(int WXUNUSED(row), int col)
261{
262 if (GetNumberCols() > col)
263 {
264 if (m_ColInfo[col].wxtypename == wxGRID_VALUE_DBAUTO)
265 {
266 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
267 {
268 wxFAIL_MSG (_T("You can not use wxGRID_VALUE_DBAUTO for virtual columns"));
269 }
270 switch(m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype)
271 {
272 case SQL_C_CHAR:
273#ifdef SQL_C_WCHAR
274 case SQL_C_WCHAR:
275#endif
276 return wxGRID_VALUE_STRING;
277 case SQL_C_SHORT:
278 case SQL_C_SSHORT:
279 return wxGRID_VALUE_NUMBER;
280 case SQL_C_USHORT:
281 return wxGRID_VALUE_NUMBER;
282 case SQL_C_LONG:
283 case SQL_C_SLONG:
284 return wxGRID_VALUE_NUMBER;
285 case SQL_C_ULONG:
286 return wxGRID_VALUE_NUMBER;
287 case SQL_C_FLOAT:
288 return wxGRID_VALUE_FLOAT;
289 case SQL_C_DOUBLE:
290 return wxGRID_VALUE_FLOAT;
291 case SQL_C_DATE:
292 return wxGRID_VALUE_DATETIME;
293 case SQL_C_TIME:
294 return wxGRID_VALUE_DATETIME;
295 case SQL_C_TIMESTAMP:
296 return wxGRID_VALUE_DATETIME;
297 default:
298 return wxGRID_VALUE_STRING;
299 }
300 }
301 else
302 {
303 return m_ColInfo[col].wxtypename;
304 }
305 }
306 wxFAIL_MSG (_T("unknown column"));
307 return wxString();
308}
309
310bool wxDbGridTableBase::CanGetValueAs(int row, int col, const wxString& typeName)
311{
312 wxLogDebug(wxT("CanGetValueAs() on %i,%i"),row,col);
313 //Is this needed? As it will be validated on GetValueAsXXXX
314 ValidateRow(row);
315
316 if (typeName == wxGRID_VALUE_STRING)
317 {
318 //FIXME ummm What about blob field etc.
319 return true;
320 }
321
322 if (m_data->IsColNull((UWORD)m_ColInfo[col].DbCol))
323 {
324 return false;
325 }
326
327 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
328 {
329 //If a virtual column then we can't find it's type. we have to
330 // return false to get using wxVariant.
331 return false;
332 }
333 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
334
335 if (typeName == wxGRID_VALUE_DATETIME)
336 {
337 if ((sqltype == SQL_C_DATE) ||
338 (sqltype == SQL_C_TIME) ||
339 (sqltype == SQL_C_TIMESTAMP))
340 {
341 return true;
342 }
343 return false;
344 }
345 if (typeName == wxGRID_VALUE_NUMBER)
346 {
347 if ((sqltype == SQL_C_SSHORT) ||
348 (sqltype == SQL_C_USHORT) ||
349 (sqltype == SQL_C_SLONG) ||
350 (sqltype == SQL_C_ULONG))
351 {
352 return true;
353 }
354 return false;
355 }
356 if (typeName == wxGRID_VALUE_FLOAT)
357 {
358 if ((sqltype == SQL_C_SSHORT) ||
359 (sqltype == SQL_C_USHORT) ||
360 (sqltype == SQL_C_SLONG) ||
361 (sqltype == SQL_C_ULONG) ||
362 (sqltype == SQL_C_FLOAT) ||
363 (sqltype == SQL_C_DOUBLE))
364 {
365 return true;
366 }
367 return false;
368 }
369 return false;
370}
371
372bool wxDbGridTableBase::CanSetValueAs(int WXUNUSED(row), int col, const wxString& typeName)
373{
374 if (typeName == wxGRID_VALUE_STRING)
375 {
376 //FIXME ummm What about blob field etc.
377 return true;
378 }
379
380 if (!(m_data->GetColDefs()[(m_ColInfo[col].DbCol)].Updateable))
381 {
382 return false;
383 }
384
385 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
386 {
387 //If a virtual column then we can't find it's type. we have to faulse to
388 //get using wxVairent.
389 return false;
390 }
391
392 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
393 if (typeName == wxGRID_VALUE_DATETIME)
394 {
395 if ((sqltype == SQL_C_DATE) ||
396 (sqltype == SQL_C_TIME) ||
397 (sqltype == SQL_C_TIMESTAMP))
398 {
399 return true;
400 }
401 return false;
402 }
403 if (typeName == wxGRID_VALUE_NUMBER)
404 {
405 if ((sqltype == SQL_C_SSHORT) ||
406 (sqltype == SQL_C_USHORT) ||
407 (sqltype == SQL_C_SLONG) ||
408 (sqltype == SQL_C_ULONG))
409 {
410 return true;
411 }
412 return false;
413 }
414 if (typeName == wxGRID_VALUE_FLOAT)
415 {
416 if ((sqltype == SQL_C_SSHORT) ||
417 (sqltype == SQL_C_USHORT) ||
418 (sqltype == SQL_C_SLONG) ||
419 (sqltype == SQL_C_ULONG) ||
420 (sqltype == SQL_C_FLOAT) ||
421 (sqltype == SQL_C_DOUBLE))
422 {
423 return true;
424 }
425 return false;
426 }
427 return false;
428}
429
430long wxDbGridTableBase::GetValueAsLong(int row, int col)
431{
432 ValidateRow(row);
433
434 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
435 {
436 wxFAIL_MSG (_T("You can not use GetValueAsLong for virtual columns"));
437 return 0;
438 }
439 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
440 if ((sqltype == SQL_C_SSHORT) ||
441 (sqltype == SQL_C_USHORT) ||
442 (sqltype == SQL_C_SLONG) ||
443 (sqltype == SQL_C_ULONG))
444 {
445 wxVariant val = m_data->GetColumn(m_ColInfo[col].DbCol);
446 return val.GetLong();
447 }
448 wxFAIL_MSG (_T("unknown column, "));
449 return 0;
450}
451
452double wxDbGridTableBase::GetValueAsDouble(int row, int col)
453{
454 wxLogDebug(wxT("GetValueAsDouble() on %i,%i"),row,col);
455 ValidateRow(row);
456
457 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
458 {
459 wxFAIL_MSG (_T("You can not use GetValueAsDouble for virtual columns"));
460 return 0.0;
461 }
462 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
463 if ((sqltype == SQL_C_SSHORT) ||
464 (sqltype == SQL_C_USHORT) ||
465 (sqltype == SQL_C_SLONG) ||
466 (sqltype == SQL_C_ULONG) ||
467 (sqltype == SQL_C_FLOAT) ||
468 (sqltype == SQL_C_DOUBLE))
469 {
470 wxVariant val = m_data->GetColumn(m_ColInfo[col].DbCol);
471 return val.GetDouble();
472 }
473 wxFAIL_MSG (_T("unknown column"));
474 return 0.0;
475}
476
477bool wxDbGridTableBase::GetValueAsBool(int row, int col)
478{
479 wxLogDebug(wxT("GetValueAsBool() on %i,%i"),row,col);
480 ValidateRow(row);
481
482 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
483 {
484 wxFAIL_MSG (_T("You can not use GetValueAsBool for virtual columns"));
485 return 0;
486 }
487 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
488 if ((sqltype == SQL_C_SSHORT) ||
489 (sqltype == SQL_C_USHORT) ||
490 (sqltype == SQL_C_SLONG) ||
491 (sqltype == SQL_C_ULONG))
492 {
493 wxVariant val = m_data->GetColumn(m_ColInfo[col].DbCol);
494 return val.GetBool();
495 }
496 wxFAIL_MSG (_T("unknown column, "));
497 return 0;
498}
499
500void* wxDbGridTableBase::GetValueAsCustom(int row, int col, const wxString& typeName)
501{
502 wxLogDebug(wxT("GetValueAsCustom() on %i,%i"),row,col);
503 ValidateRow(row);
504
505 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
506 {
507 wxFAIL_MSG (_T("You can not use GetValueAsCustom for virtual columns"));
508 return NULL;
509 }
510 if (m_data->IsColNull((UWORD)m_ColInfo[col].DbCol))
511 return NULL;
512
513 if (typeName == wxGRID_VALUE_DATETIME)
514 {
515 wxDbColDef *pColDefs = m_data->GetColDefs();
516 int sqltype = pColDefs[(m_ColInfo[col].DbCol)].SqlCtype;
517
518 if ((sqltype == SQL_C_DATE) ||
519 (sqltype == SQL_C_TIME) ||
520 (sqltype == SQL_C_TIMESTAMP))
521 {
522 wxVariant val = m_data->GetColumn(m_ColInfo[col].DbCol);
523 return new wxDateTime(val.GetDateTime());
524 }
525 }
526 wxFAIL_MSG (_T("unknown column data type "));
527 return NULL;
528}
529
530
531void wxDbGridTableBase::SetValueAsCustom(int row, int col, const wxString& typeName, void* value)
532{
533 wxLogDebug(wxT("SetValueAsCustom() on %i,%i"),row,col);
534 ValidateRow(row);
535
536 if (m_data->GetNumberOfColumns() <= m_ColInfo[col].DbCol)
537 {
538 wxFAIL_MSG (_T("You can not use SetValueAsCustom for virtual columns"));
539 return;
540 }
541
542 if (typeName == wxGRID_VALUE_DATETIME)
543 {
544 int sqltype = m_data->GetColDefs()[(m_ColInfo[col].DbCol)].SqlCtype;
545 if ((sqltype == SQL_C_DATE) ||
546 (sqltype == SQL_C_TIME) ||
547 (sqltype == SQL_C_TIMESTAMP))
548 {
549 //FIXME: you can't dynamic_cast from (void *)
550 //wxDateTime *date = wxDynamicCast(value, wxDateTime);
551 wxDateTime *date = (wxDateTime *)value;
552 if (!date)
553 {
554 wxFAIL_MSG (_T("Failed to convert data"));
555 return;
556 }
557 wxVariant val(date);
558 m_rowmodified = true;
559 m_data->SetColumn(m_ColInfo[col].DbCol,val);
560 }
561 }
562 wxFAIL_MSG (_T("unknown column data type"));
563 return ;
564}
565
566
567wxString wxDbGridTableBase::GetColLabelValue(int col)
568{
569 if (GetNumberCols() > col)
570 {
571 return m_ColInfo[col].Title;
572 }
573 wxFAIL_MSG (_T("unknown column"));
574 return wxString();
575}
576
577bool wxDbGridTableBase::IsEmptyCell(int row, int col)
578{
579 wxLogDebug(wxT("IsEmtpyCell on %i,%i"),row,col);
580
581 ValidateRow(row);
582 return m_data->IsColNull((UWORD)m_ColInfo[col].DbCol);
583}
584
585
586wxString wxDbGridTableBase::GetValue(int row, int col)
587{
588 wxLogDebug(wxT("GetValue() on %i,%i"),row,col);
589
590 ValidateRow(row);
591 wxVariant val = m_data->GetColumn(m_ColInfo[col].DbCol);
592 wxLogDebug(wxT("\tReturning \"%s\"\n"),val.GetString().c_str());
593
594 return val.GetString();
595}
596
597
598void wxDbGridTableBase::SetValue(int row, int col,const wxString& value)
599{
600 wxLogDebug(wxT("SetValue() on %i,%i"),row,col);
601
602 ValidateRow(row);
603 wxVariant val(value);
604
605 m_rowmodified = true;
606 m_data->SetColumn(m_ColInfo[col].DbCol,val);
607}
608
609
610void wxDbGridTableBase::SetValueAsLong(int row, int col, long value)
611{
612 wxLogDebug(wxT("SetValueAsLong() on %i,%i"),row,col);
613
614 ValidateRow(row);
615 wxVariant val(value);
616
617 m_rowmodified = true;
618 m_data->SetColumn(m_ColInfo[col].DbCol,val);
619}
620
621
622void wxDbGridTableBase::SetValueAsDouble(int row, int col, double value)
623{
624 wxLogDebug(wxT("SetValueAsDouble() on %i,%i"),row,col);
625
626 ValidateRow(row);
627 wxVariant val(value);
628
629 m_rowmodified = true;
630 m_data->SetColumn(m_ColInfo[col].DbCol,val);
631
632}
633
634
635void wxDbGridTableBase::SetValueAsBool(int row, int col, bool value)
636{
637 wxLogDebug(wxT("SetValueAsBool() on %i,%i"),row,col);
638
639 ValidateRow(row);
640 wxVariant val(value);
641
642 m_rowmodified = true;
643 m_data->SetColumn(m_ColInfo[col].DbCol,val);
644}
645
646
647void wxDbGridTableBase::ValidateRow(int row)
648{
649 wxLogDebug(wxT("ValidateRow(%i) currently on row (%i). Array count = %i"),row,m_row,m_keys.GetCount());
650
651 if (row == m_row)
652 return;
653 Writeback();
654
655 //We add to row as Count is unsigned!
656 if ((unsigned)(row+1) > m_keys.GetCount())
657 {
658 wxLogDebug(wxT("\trow key unknown"));
659 // Extend Array, iterate through data filling with keys
660 m_data->SetRowMode(wxDbTable::WX_ROW_MODE_QUERY);
661 int trow;
662 for (trow = m_keys.GetCount(); trow <= row; trow++)
663 {
664 wxLogDebug(wxT("Fetching row %i.."), trow);
665 bool ret = m_data->GetNext();
666
667 wxLogDebug(wxT(" ...success=(%i)"),ret);
668 GenericKey k = m_data->GetKey();
669 m_keys.Add(k);
670 }
671 m_row = row;
672 }
673 else
674 {
675 wxLogDebug(wxT("\trow key known centering data"));
676 GenericKey k = m_keys.Item(row);
677 m_data->SetRowMode(wxDbTable::WX_ROW_MODE_INDIVIDUAL);
678 m_data->ClearMemberVars();
679 m_data->SetKey(k);
680 if (!m_data->QueryOnKeyFields())
681 {
682 wxDbLogExtendedErrorMsg(_T("ODBC error during Query()\n\n"), m_data->GetDb(),__TFILE__,__LINE__);
683 }
684
685 m_data->GetNext();
686
687 m_row = row;
688 }
689 m_rowmodified = false;
690}
691
692bool wxDbGridTableBase::Writeback() const
693{
694 if (!m_rowmodified)
695 {
696 return true;
697 }
698
699 bool result=true;
700 wxLogDebug(wxT("\trow key unknown"));
701
702// FIXME: this code requires dbtable support for record status
703#if 0
704 switch (m_data->get_ModifiedStatus())
705 {
706 case wxDbTable::UpdatePending:
707 result = m_data->Update();
708 break;
709 case wxDbTable::InsertPending:
710 result = (m_data->Insert() == SQL_SUCCESS);
711 break;
712 default:
713 //Nothing
714 break;
715 }
716#else
717 wxLogDebug(wxT("WARNING : Row writeback not implemented "));
718#endif
719 return result;
720}
721
722#include "wx/arrimpl.cpp"
723
724WX_DEFINE_EXPORTED_OBJARRAY(keyarray)
725
726#endif // wxUSE_GRID && wxUSE_ODBC