【问题标题】:Refreshing wxGrid with dynamic contents使用动态内容刷新 wxGrid
【发布时间】:2012-02-10 01:43:39
【问题描述】:

这似乎不像我希望的那么简单:

使用 wxWidgets(2.8 稳定系列),我有一个带有自定义“数据适配器”的 wxGrid(未子类化)作为 wxGridTableBase 派生类。

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

我找不到的“简单”事情是当底层数据模型发生变化时刷新网格的方法。仅仅调用 wxWindow::Update (pGrid->Update()) 显然不足以让网格真正调用底层的 wxGridTableBase 实现?

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

特别是,此网格充当列表,并且将通过相同或(可能)另一个进程异步添加和删除行 - 它是一个共享数据列表,可以由多个中的任何一个更新网络系统。网格/列表本身实际上是只读的;其他控件用于添加和删除项目,每一行都有一个布尔类型的属性,也可以切换。

视图中好像没有添加新行,删除行会导致wx绘图代码中出现断断续续的SEGV。

由于动态/异步更新机制,我希望避免不得不不断删除和重新添加网格到窗口,因为我确信这会导致各种闪烁和肮脏……所以,如果我绝对必须的话,我会重新尝试这样的蛮力,但我强烈希望避免它。

不幸的是,尽管被标记为“稳定版本”,但 wxGrid 文档似乎主要由 Yet to be written 标签组成。

更新: 我开始怀疑这是一个容器布局问题。在绘制网格时,网格的底部(最后一行)实际上可以与 wxStaticBox 框架重叠在其 wxFrame 窗口部分周围,以及框架的状态行的一部分。添加和删​​除行似乎不会强制重新布局容器;我正在尝试尝试致电Layout 等。理想情况下,这应该是一个滚动区域,但 wxGrid 应该 仍然在其包含的 Sizer 中“受限”。

布局实际上由一个静态框组成,其中包含一个垂直框,其第一个元素是水平按钮框,然后是网格,如下所示:

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

很遗憾,公司政策禁止我发布屏幕截图 :-(

如果重要的话,这(目前)是 Fedora 16 (x86_64) 上的 wxGTK-2.8.12,尽管我在使用 EPEL (Fedora) 软件包的 CentOS5/RHEL5 上看到了相同的行为。

【问题讨论】:

  • (重复我在下面提到的有人可以找到它的地方:)恐怕我从来没有找到更好的解决方案,我们转向 wxWidgets 2.9 beta 系列,然后我离开了那份工作,所以......我在下面有效地发挥作用,但不是很好。 :-/

标签: c++ wxwidgets


【解决方案1】:

wxGrid::ForceRefresh()

导致立即重新绘制网格。使用它而不是通常的 wxWindow::Refresh。

【讨论】:

    【解决方案2】:

    不幸的是,调用pGrid->Update() 似乎是不够的。对该函数的调用实际上将调用wxWindow::Update,它会递归地重新绘制窗口的无效区域及其所有子窗口,它可能无法正常工作。

    相反,您要调用的是wxGrid::ForceRefresh(),如文档here 中所述。文档说

    导致立即重新绘制网格。

    用这个代替通常的wxWindow::Refresh()

    有趣的是,如果您查看 wxWidgets 提供的示例项目中的网格示例,它们只使用了wxGrid::Refresh

    我使用过 wxWidgets (C++) 和 wxPython,对于我的 wxGrid,我使用 ForceRefresh。可以在here 找到一个使用它的示例。虽然它是 wxPython,但我似乎没有找到使用 C++ 版本的在线示例,但是,在使用了这两个库后,我可以告诉你它们的使用方式相同。

    【讨论】:

      【解决方案3】:

      经过大量实验,强制刷新的“正确”方法看起来像这样:

      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);
      }
      

      在我的 (5Hz) 更新例程中通过抓取 grid 并调用 its ->AppendRows(1) 方法调用这些方法,该方法又调用 wxTableBase 派生类的 ::AppendRows 成员。

      不幸的是,由于我是从异步的、动态更新的记录中提取数据的,wxGrid 缓存系统仍然在与我“对抗”行属性(如果行发生变化,以至于它的 GetAttr 值应该发生变化,它不会动态刷新,因为上面只测试应该存在的行数与实际存在的行数)。不过,这是一个相对较小的错误,我希望通过其他方式克服它。

      “关键”部分似乎是通过ProcessTableMessage 将删除/追加行消息合成到 wxGridTable 系统……没有它,wxGrid 缓存似乎无法注意到表大小的变化。

      顺便说一句,通过在 ::GetValue(const int row, const int column) 方法中设置守卫来检查有效值,可以缓解由于丢失行而导致的崩溃:

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

      然而,在添加了上面疯狂的消息注入逻辑之后,这些“×”值似乎永远不会显示。

      【讨论】:

      • 很好的答案。您不需要在 BeginBatch ... EndBatch 块中调用 ForceRefresh(),因为它什么也不做。事实上,ForceRefresh 所做的是: { BeginBatch();结束批次(); }
      • 我忘了这是在这里;恐怕我不再在那里工作,所以我无法真正测试它;上面的 hack 确实有效,但由于稳定 wxWidgets 的这个和其他问题,我们最终遇到了“几乎稳定的 beta”版本。不过,谢谢你的解释!
      • 在回答您之前的问题时,如果您最终使用的是解决方案,那么接受您自己的答案是完全可以的。我们鼓励人们在提出与其他人建议的解决方案不同的解决方案时回答他们自己的问题,因此接受该解决方案没有问题。这是我自己做的。
      猜你喜欢
      • 2012-08-16
      • 1970-01-01
      • 2018-09-24
      • 1970-01-01
      • 2013-01-12
      • 2012-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多