Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refreshing wxGrid with dynamic contents

Tags:

c++

wxwidgets

This doesn't seem to be as simple as I'd hoped:

Using wxWidgets (2.8 stable series), I have a wxGrid (not subclassed) with a custom “data adapter” as a wxGridTableBase-derived class.

wxGrid* grid = new wxGrid (this, ID_TABLE);
grid->SetTable (new TableAdapter (foo, bar, baz));
grid->EnableEditing (false);
sizer->Add(grid, wxSizerFlags (1).Expand());

The “simple” thing that I can't find is a way to refresh the grid when the underlying data model changes. Simply calling wxWindow::Update (pGrid->Update()) is apparently insufficient to actually get the grid to call the underlying wxGridTableBase implementation?

wxGrid* const grid = (wxGrid* const) FindWindow (ID_TABLE);
if (NULL != grid) {
     grid->Update ();
     grid->AutoSizeColumns ();
}

In particular, this grid is acting as a list, and will have rows added and removed from it asynchronously, by either the same or (potentially) another process — it's a shared data list that can be updated by any one of several networked systems. The grid/list itself is effectively read-only; other controls are used to add and remove items, and each row has one boolean-type attribute that can be toggled as well.

It seems that new rows aren't added to the view, and deleting rows will cause intermittent SEGV's in the wx drawing code.

Due to the dynamic/asynchronous updating mechanism, I'm hoping to avoid having to delete and re-add the grid to the window constantly, as I'm sure that will cause all sorts of flicker and nastiness… so, I'll fall back on trying something brute-force like that, if I absolutely must, but I'd strongly prefer to avoid it.

Unfortunately, despite being flagged as the “stable version,” the wxGrid documentation appears to mostly consist of Yet to be written tags.

Updated: I'm becoming suspicious that this is a layout-of-container problem. In drawing the grid, the bottom of the grid (last row) can actually overlap both the wxStaticBox frame around its section of the wxFrame window, as well as part of the status line of the frame. Adding and removing rows doesn't seem to force a re-layout of the container; I'm experimenting with trying to call Layout and the like. Ideally, this should be a scrolling region, but the wxGrid should still be “constrained” within its containing Sizer.

The layout consists, effectively, of a static box, containing a vertical box, the first element of which is a horizontal box of buttons, then the grid, as so:

    --[ Static Box ]------------------------
   |                                        |
   | [Button] [Button] [Button]             |
   |                                        |
   |  -----------------------------------   |
   | |      |   A      |   B   |    C    |  |
   | |-----------------------------------|  |
   | |    1 |   1a     |   1b  |    1c   |  |
   |  -----------------------------------   |
   |                                        |
    ----------------------------------------

Unfortunately, company policy prohibits me from posting screenshots :-(

If it matters, this is (presently) wxGTK-2.8.12 on Fedora 16 (x86_64), although I'm seeing identical behaviour on CentOS5/RHEL5 using the EPEL (Fedora) packages.

like image 706
BRPocock Avatar asked Jan 13 '12 21:01

BRPocock


3 Answers

After much experimentation, it looks like the “correct” way to force a refresh goes something like this:

bool
CDynamicWxGridTable::AppendRows(const size_t IGNORED _)
{
   wxGrid *grid = GetView();

   if (pGrid != NULL)
   {
      const int iNumRecords = GetNumberRows();
      const int iGridRows = grid->GetNumberRows();
      const int iNeedRows = iNumRecords - iGridRows;

      if (iNeedRows)
      {
         grid->BeginBatch();
         grid->ClearSelection();

         if (grid->IsCellEditControlEnabled())
         {
            grid->DisableCellEditControl();
         }

         {
            wxGridTableMessage pop(this,
                 wxGRIDTABLE_NOTIFY_ROWS_DELETED,
                 0, iGridRows);
            grid->ProcessTableMessage(pop);
         }
         {
            wxGridTableMessage push(this,
                 wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
                 iNumRecords);
            grid->ProcessTableMessage(push);
         }
         grid->AutoSize();
         grid->ForceRefresh();
         grid->EndBatch();
      }
   }

   return true;
}

bool
CDynamicWxGridTable::DeleteRows(const size_t IGNORED pos,
      const size_t IGNORED rows)
{
   return AppendRows(0);
}

These are called during my (5Hz) update routine by grabbing grid and calling its ->AppendRows(1) method, which in turn calls the wxTableBase-derived class's ::AppendRows member.

Unfortunately, since I'm drawing from asynchronous, dynamically-updated records, the wxGrid caching system is still “fighting” me as far as row attributes (if a row changes, such that its GetAttr value should change, it doesn't refresh dynamically, since the above only tests the number of rows that there should be versus the number of rows that actually exist). However, this is a relatively minor bug, and I'm hoping to overcome it through other means.

The “critical” part seems to be synthesizing the delete/append rows messages to the wxGridTable system through ProcessTableMessage… without which, the wxGrid cache seems to fail to notice the changes in the table size.

Incidentally, the crashes due to losing rows were alleviated by putting guards in the ::GetValue(const int row, const int column) method to check for valid values:

if (row < 0 || row > GetNumberRows()) { return L"×"; }
if (col < 0 || col > LAST_COLUMN) { return L"×"; }

These “×” values don't ever seem to display, after adding the crazy message-injection logic above, however.

like image 95
BRPocock Avatar answered Nov 17 '22 09:11

BRPocock


wxGrid::ForceRefresh()

Causes immediate repainting of the grid. Use this instead of the usual wxWindow::Refresh.

like image 28
ravenspoint Avatar answered Nov 17 '22 11:11

ravenspoint


Unfortunately it appears that a call to pGrid->Update() will not suffice. The call to that function will actually call wxWindow::Update, which does repaint the invalided area of the window, along with all of its child recursively, it may not be working properly.

Instead, what you want to call is wxGrid::ForceRefresh() as detailed in the documentation here. The documentation says

Causes immediate repainting of the grid.

Use this instead of the usual wxWindow::Refresh().

What is interesting though, is that if you look at the grid example on the samples project supplied with wxWidgets, they use just wxGrid::Refresh.

I have used wxWidgets (C++) and wxPython, and for my wxGrid I use ForceRefresh. An example of it being used can be found here. While it is wxPython, I did not seem to find an online example of it using the C++ version, however, having used both libraries I can tell you that they are both used in the same manner.

like image 1
josephthomas Avatar answered Nov 17 '22 10:11

josephthomas