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: dbgrid.cpp 43769 2006-12-03 18:20:28Z VZ $ 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 = %lu"), 650 row, m_row, (unsigned long)m_keys.GetCount()); 651 652 if (row == m_row) 653 return; 654 Writeback(); 655 656 //We add to row as Count is unsigned! 657 if ((unsigned)(row+1) > m_keys.GetCount()) 658 { 659 wxLogDebug(wxT("\trow key unknown")); 660 // Extend Array, iterate through data filling with keys 661 m_data->SetRowMode(wxDbTable::WX_ROW_MODE_QUERY); 662 int trow; 663 for (trow = m_keys.GetCount(); trow <= row; trow++) 664 { 665 wxLogDebug(wxT("Fetching row %i.."), trow); 666 bool ret = m_data->GetNext(); 667 668 wxLogDebug(wxT(" ...success=(%i)"),ret); 669 GenericKey k = m_data->GetKey(); 670 m_keys.Add(k); 671 } 672 m_row = row; 673 } 674 else 675 { 676 wxLogDebug(wxT("\trow key known centering data")); 677 GenericKey k = m_keys.Item(row); 678 m_data->SetRowMode(wxDbTable::WX_ROW_MODE_INDIVIDUAL); 679 m_data->ClearMemberVars(); 680 m_data->SetKey(k); 681 if (!m_data->QueryOnKeyFields()) 682 { 683 wxDbLogExtendedErrorMsg(_T("ODBC error during Query()\n\n"), m_data->GetDb(),__TFILE__,__LINE__); 684 } 685 686 m_data->GetNext(); 687 688 m_row = row; 689 } 690 m_rowmodified = false; 691} 692 693bool wxDbGridTableBase::Writeback() const 694{ 695 if (!m_rowmodified) 696 { 697 return true; 698 } 699 700 bool result=true; 701 wxLogDebug(wxT("\trow key unknown")); 702 703// FIXME: this code requires dbtable support for record status 704#if 0 705 switch (m_data->get_ModifiedStatus()) 706 { 707 case wxDbTable::UpdatePending: 708 result = m_data->Update(); 709 break; 710 case wxDbTable::InsertPending: 711 result = (m_data->Insert() == SQL_SUCCESS); 712 break; 713 default: 714 //Nothing 715 break; 716 } 717#else 718 wxLogDebug(wxT("WARNING : Row writeback not implemented ")); 719#endif 720 return result; 721} 722 723#include "wx/arrimpl.cpp" 724 725WX_DEFINE_EXPORTED_OBJARRAY(keyarray) 726 727#endif // wxUSE_GRID && wxUSE_ODBC 728