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