0.前言
欢迎来到 wxSmith 教程页面!wxSmith 与 Code::Blocks、wxWidgets 和 C++ 编译器相结合,为您提供一种所见即所得的方式来创建具有图形用户界面 (GUI) 的应用程序。该组合形成了一个用于快速应用程序开发 (RAD) 的工具,可在 Linux、Mac OS X 和 Windows 上运行。当您工作时,您会在屏幕上看到您正在设计的表单;他们看着你,就像他们看着你的程序的用户一样。
这些教程假设您是Code::Blocks 或 wxWidgets 的初学者,事实上,它们也是学习它们的好教程;假设您对 C++ 有基本的了解,您能够看懂教程中的C++实例代码。
这套教程的其他文章可以从以下文章中找到:
在CodeBolcks下wxSmith的C++编程教程——wxSmith教程目录(序言) - lexyao - 博客园
编写这篇文章参照了Code::Blocks 用户文档中wxSmith教程的以下文章:
Using wxGrid - Code::Blocks
在这篇文章中你将看到以下内容:
- 关于使用 wxGrid
- 使用wxSmith设置wxGrid
- 运行时向wxGrid添加数据
- 运行时改变wxGrid
- 结束语
1.关于使用 wxGrid
网格是显示数据的有用方法,尤其是二维的矩阵。如果矩阵很大,则整个网格对于屏幕来说太大,因此我们希望以适度的比例显示它,但允许用户调整它的大小或用鼠标在其中滚动。wxGrid是非常强大的网格组件,提供了丰富的操作和强大的能力。
Code::Blocks 用户文档中wxSmith教程(原英文教程)讲述了wxGrid的使用方法,但教程中的示例与实际编程中的使用方法有很大的差别,不利于初学者参照使用;原文没有划分章节,对于喜欢列出123的我来说有些不习惯,所以,我决定:
- 按着我编写教程的方法重新组织文章的内容,按着wxGrid操作的对象分类描述。
- 将原文中构建并运行应用程序就能看得见的内容改成用户可以控制的菜单操作。
- 在菜单中加入更多的操作wxGrid的方法,以便于让读者更容易了解wxGrid。
就像许多与计算相关的事情一样,一旦您知道如何创建,使用 wxSmith 创建这样的网格就很容易,但弄清楚如何创建这样的网格会消耗很多时间。以下是这种试错过程的提炼结果。最后,我们将有一个可滚动的网格,在列和行上包含名称,并在 20x30 矩阵的某些元素中显示数字。
wxSmith 为我们编写的代码最有价值的用途可能是作为如何使用 wxGrid 的示例。当斯马特和霍克开始在第 346 页描述它时,他们似乎已经没有精力编造清晰、完整的例子了。wxSmith 可以用很好的例子来拯救他们。但是,wxSmith 本身编写的代码不太可能直接进入您的最终程序。例如,您可能希望网格的大小由程序中的变量确定。wxSmith 编写的代码具有由两个常量(行数和列数)给出的网格大小,但您可以从该代码中轻松看到如何使网格的大小取决于代码中的变量。
2.使用wxSmith设置wxGrid
那么让我们开始吧。创建一个新的 wxWidgets 应用程序,项目名称为tuX,主界面基于wxFrame。原英文教程中将wxGrid 表格放入一个基于wxDialog的窗口中,可以在主界面中点击按钮打开这个窗口;而在这里,我想把wxGrid 放在主窗口的框架中,这样在测试时看起来更简洁、直观一些。下面开始我们创建项目的步骤:
- 在Code::Blocks中使用向导创建一个基于wxFrame的wxWidgets 项目,项目名称确定为tuX。
- 构建并运行,必要时补充需要的库文件,确保没有编译错误
- 点击添加的wxFrame,属性浏览器中设置他的Title属性为“Test wxGrid”
- 向wxFrame中添加一个wxBoxSizer,变量名BoxSizer1
- 向BoxSizer1中添加一个wxGrid,变量名Grid1。
- 注:按着前面教程中的做法,应该先向BoxSizer1中添加一个Panel1,再向Panel1中添加一个BoxSizer2,然后将Grid1添加到BoxSizer2中。
对于需要添加更多组件的界面中这样做是有必要的,但对于仅有一个wxGrid组件的界面来说是不是一定要这样做呢?
我做了测试,将Grid1直接添加到BoxSizer1中,与添加到BoxSizer2中的做法没有发现差别。
- 注:按着前面教程中的做法,应该先向BoxSizer1中添加一个Panel1,再向Panel1中添加一个BoxSizer2,然后将Grid1添加到BoxSizer2中。
- 在属性浏览器中设置Grid1的布局属性:
- Border width属性设置为0(去掉边界黑框)
- 使用Panel1+BoxSizer2的方案时这一条是设置Panel1的属性
- 选中“扩展”(Expand)属性
- 任何方案都要这么做,很多组件都需要这么选
- 取消选中默认尺寸框,并将最小宽度(Min Width)设置为 400,最小高度(Min Height)设置为 300。(这些数字以像素为单位。)
- 这个设置将会作为窗口的最小尺寸。运行时可以用鼠标拖动将窗口变得更大,但不会比这个数值更小。在构造函数tuXFrame::tuXFrame中会找到对应的语句Grid1->SetMinSize(wxSize(400,300));
- Border width属性设置为0(去掉边界黑框)
- 在属性浏览器中设置Grid1的特有属性
- 列数设置为 40,行数设置为 40(我们将在稍后的代码中将其更改为 20 x 30)
- 展开“样式”(Style)会看到下面的许多选项,选中 wxFullRepaintOnResize 的复选框
- 你也可以选择其他选项测试其效果
至此,在资源树中会看到这样的画面:
构建并运行tuX项目,会看到一个漂亮的表格画面:
你可以在表格中尝试添加数据、选择行或列、选择区域,感觉跟在Excel或wps表格中的操作没有什么差别,当然,这里只有基础的功能,还没有添加更多的操作。
原英文版的教程作者把以下代码添加到窗口的构造函数中,这种做法是不可取的,也没有实用价值,但作为一种演示能够让读者知道表格组件的功能是可以的,只要不会误导他人就无可厚非。在本教程的后续部分,我将会通过菜单操作的方式分类实现这些代码的功能,而且还会有所增加。
// Part 2. // Now comes the hand-coded section (with a few lines borrowed from wxSmith)int nrows, ncols;nrows =20; ncols = 30;Grid1->CreateGrid(nrows,ncols); // wxSmith had Grid1->CreateGrid(40.40);Grid1->EnableEditing(true); // from wxSmithGrid1->EnableGridLines(true); // from wxSmith // Label some columnsGrid1->SetColLabelValue(0, _("alpha")); //from wxSmith Grid1->SetColLabelValue(1, _("beta")); //from wxSmith Grid1->SetColLabelValue(2, _("gamma")); //from wxSmith Grid1->SetColLabelValue(3, _("delta "));Grid1->SetColLabelValue(4, _("epsilon"));// Label some rowsGrid1->SetRowLabelValue(0, _("mu")); // from wxSmithGrid1->SetRowLabelValue(1, _("nu")); // from wxSmithGrid1->SetRowLabelValue(2, _("xi")); // from wxSmithGrid1->SetRowLabelValue(3, _("omicron"));Grid1->SetRowLabelValue(4, _("pi"));// Column or row labels can be set from C-strings whose // values are determined at runtime by first converting // the C-string to a wxString, as illustrated here:char colone[10]; //colone is the variable C-string.strcpy(colone,"omega");// Give it some content at run time.wxString ColumnName; // Define a wxString which may be used repeatedly. // Convert contents of the C-string to a wxString:ColumnName = wxString::FromUTF8(colone);Grid1->SetColLabelValue(0,ColumnName); // Set the label for column 0.// Set some cell valuesGrid1->SetCellValue(0, 0, _("2")); //from wxSmithGrid1->SetCellValue(0, 1, _("7")); //from wxSmithGrid1->SetCellValue(0, 2, _("6")); //from wxSmith Grid1->SetCellValue(1, 0, _("9"));Grid1->SetCellValue(1, 1, _("5"));Grid1->SetCellValue(1, 2, _("1"));Grid1->SetCellValue(2, 0, _("4"));Grid1->SetCellValue(2, 1, _("3"));Grid1->SetCellValue(2, 2, _("8"));// Illustrate what SetColFormatFloat() does. From Smart & Hock, page 348 corrected.Grid1->SetColFormatFloat(5,6,2); // Format numeric strings in column 5.Grid1->SetCellValue(0,5,_("3.14159")); // Put a number with 5 decimals there.// Put the same string in Column 4 for comparison.Grid1->SetCellValue(0,4,_("3.14159"));// Here is how to show floats defined at run time in the wxGrid:float e = 2.71828;wxString cart;cart = wxString::Format(_T("%f"), e);Grid1->SetCellValue(1,4,cart);Grid1->SetCellValue(1,5,cart);// Display a matrix of floats defined at run time.short i,j;float xx;for (i = 10; i <=12; i++){for (j = 10; j <= 12; j++){xx = i*j;xx = xx/2.;cart = wxString::Format(_T("%8.2f"),xx);Grid1->SetCellValue(i,j,cart);}}
3.运行时向wxGrid添加数据
在这里我们将会添加几个菜单项,分类实现原教程作者part 2的内容。
双击编辑区tuXframe.wxs标签下的菜单图标打开菜单编辑器,添加顶级菜单项Part2,从属性浏览器中看到变量名为Menu3,取消Is member。
3.1 设置行列标签
从grid.h中可以找到设置行、列标签的成员函数的定义:
void SetRowLabelValue( int row, const wxString& );void SetColLabelValue( int col, const wxString& );
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem3Selected(wxCommandEvent& event) {// Label some columnsGrid1->SetColLabelValue(0, _("alpha"));Grid1->SetColLabelValue(1, _("beta"));Grid1->SetColLabelValue(2, _("gamma"));Grid1->SetColLabelValue(3, _("delta "));Grid1->SetColLabelValue(4, _("epsilon"));// Label some rowsGrid1->SetRowLabelValue(0, _("mu"));Grid1->SetRowLabelValue(1, _("nu"));Grid1->SetRowLabelValue(2, _("xi"));Grid1->SetRowLabelValue(3, _("omicron"));Grid1->SetRowLabelValue(4, _("pi")); }
构建并运行tuX项目,点击菜单Part2>>设置行列标签,将会看到以下画面:
对照代码中的行/列值和截图中的的画面可以看出:行和列的编号都是从0开始的,行/列标签与正文的单元格是分开编号的,这一点与其他的表格组件不同。
3.2 添加单元格数据(坐标)
从grid.h中可以找到设置行、列标签的成员函数的定义:
void SetCellValue( int row, int col, const wxString& s );void SetCellValue( const wxGridCellCoords& coords, const wxString& s ){ SetCellValue( coords.GetRow(), coords.GetCol(), s ); }
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem4Selected(wxCommandEvent& event) { Grid1->SetCellValue(0, 0, _("(0, 0)")); Grid1->SetCellValue(0, 1, _("(0, 1)"));Grid1->SetCellValue(0, 2, _("(0, 2)")); Grid1->SetCellValue(1, 0, _("(1, 0)"));Grid1->SetCellValue(1, 1, _("(1, 1)"));Grid1->SetCellValue(1, 2, _("(1, 2)"));Grid1->SetCellValue(2, 0, _("(2, 0)"));Grid1->SetCellValue(2, 1, _("(2, 1)"));Grid1->SetCellValue(2, 2, _("(2, 2)")); }
构建并运行tuX项目,点击菜单Part2>>测试单元格坐标,将会看到以下画面:
从画面中可以很容易理解单元格坐标的编号规则,单元格中的数值对应坐标的含义为(row, col),即(行,列)。
3.3 添加单元格数据(数字)
在菜单编辑器中给Part2添加一个子菜单项,Id为idPartInt,Label为“向单元格添加数字”,变量名为MenuItem5。
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem5Selected(wxCommandEvent& event) {Grid1->SetCellValue(0, 0, _("2"));Grid1->SetCellValue(0, 1, _("7"));Grid1->SetCellValue(0, 2, _("6"));Grid1->SetCellValue(1, 0, _("9"));Grid1->SetCellValue(1, 1, _("5"));Grid1->SetCellValue(1, 2, _("1"));Grid1->SetCellValue(2, 0, _("4"));Grid1->SetCellValue(2, 1, _("3"));Grid1->SetCellValue(2, 2, _("8")); }
构建并运行tuX项目,点击菜单Part2>>向单元格添加数字,将会看到以下画面:
3.4 添加单元格数据(实数)
在菜单编辑器中给Part2添加一个子菜单项,Id为idPartFloat,Label为“向单元格添加实数”,变量名为MenuItem6。
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem6Selected(wxCommandEvent& event) {//格式化数字字符串Grid1->SetColFormatFloat(5, 6, 2); // 格式化第五列中的数字字符串。Grid1->SetCellValue(0, 5, _("3.14159")); // 在那里放一个有五位小数的数字。Grid1->SetCellValue(0, 4, _("3.14159"));//在第4列放入相同的字符串进行比较。//以下是如何在 wxGrid 中显示运行时定义的浮点数:float e = 2.71828;wxString cart;cart = wxString::Format(_T("%f"), e);Grid1->SetCellValue(1, 4, cart);Grid1->SetCellValue(1, 5, cart);//显示在运行时定义的浮点矩阵。short i, j;float xx;for(i = 2; i <= 5; i++){for(j = 8; j <= 10; j++){xx = i * j;xx = xx / 2.;cart = wxString::Format(_T("%8.2f"), xx);Grid1->SetCellValue(i, j, cart);}} }
上述代码中用到了SetColFormatFloat设置列的数字格式,在grid.h中找到它的定义如下:
// set the format for the data in the column: default is stringvoid SetColFormatBool(int col);void SetColFormatNumber(int col);void SetColFormatFloat(int col, int width = -1, int precision = -1);void SetColFormatDate(int col, const wxString& format = wxString());void SetColFormatCustom(int col, const wxString& typeName);
在grid.h中同时还看到了其他几个格式化函数。
构建并运行tuX项目,点击菜单Part2>>向单元格添加实数,将会看到以下画面:
4.运行时改变wxGrid
在这里我们将会添加几个菜单项,用来测试对表格的操作。
双击编辑区tuXframe.wxs标签下的菜单图标打开菜单编辑器,添加顶级菜单项Grid,从属性浏览器中看到变量名为Menu4,取消Is member。
4.1 清空表格中的数据
在菜单编辑器中给Grid添加一个子菜单项,Id为idGridCLear,Label为“CLearGrid”,变量名为MenuItem7。
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem7Selected(wxCommandEvent& event) {Grid1->ClearGrid(); }
构建并运行tuX项目,向表格中添加数据,然后点击菜单Grid>>CLearGrid,表格中的数据都会清空,但不会影响行列标签。
4.2 设置行数和列数
tuXFrame::tuXFrame构造函数中有以下语句:
Grid1->CreateGrid(40,40);
这条语句将表格设置为40行、40列。这个语句只能使用一次,也就是说程序运行以后我们没有机会去修改这个数值,除非是销毁了表格对象后重新创建新的表格,这样做显然是不合适的。wxGrid提供了成员函数可以删除或添加行或列,这样可以改变现有表格的行列数,而且这样的修改不应影响保留下来的单元格的数据。
从grid.h中可以找到删除、插入、添加行或列的成员函数的定义:
// ------ display update functionsbool InsertRows(int pos = 0, int numRows = 1, bool updateLabels = true){return DoModifyLines(&wxGridTableBase::InsertRows,pos, numRows, updateLabels);}bool InsertCols(int pos = 0, int numCols = 1, bool updateLabels = true){return DoModifyLines(&wxGridTableBase::InsertCols,pos, numCols, updateLabels);}bool AppendRows(int numRows = 1, bool updateLabels = true){return DoAppendLines(&wxGridTableBase::AppendRows, numRows, updateLabels);}bool AppendCols(int numCols = 1, bool updateLabels = true){return DoAppendLines(&wxGridTableBase::AppendCols, numCols, updateLabels);}bool DeleteRows(int pos = 0, int numRows = 1, bool updateLabels = true){return DoModifyLines(&wxGridTableBase::DeleteRows,pos, numRows, updateLabels);}bool DeleteCols(int pos = 0, int numCols = 1, bool updateLabels = true){return DoModifyLines(&wxGridTableBase::DeleteCols,pos, numCols, updateLabels);}
通过删除、插入、添加行的操作可以将表格的行数调整为指定的行数,同样,通过删除、插入、添加列的操作可以将表格的列数调整为指定的列数。
从函数的定义可以看出,一次可以删除、插入、添加一行(列)或多行(列),可以指定自动更新标签,也可以不更新。
要执行删除或插入的操作还需要知道表格现有的行数、当前光标的位置。这些成员函数的定义是:
// ------ grid dimensionsint GetNumberRows() const { return m_numRows; }int GetNumberCols() const { return m_numCols; }// ------ grid location functionsint GetGridCursorRow() const { return m_currentCellCoords.GetRow(); }int GetGridCursorCol() const { return m_currentCellCoords.GetCol(); }
在菜单编辑器中给Grid添加三个子菜单项,Id分别为idGridAppend、idGridInsert、idGridDelete,Label分别为“添加行列”、“插入行列”、“删除行列”、,变量名分别为MenuItem8、MenuItem9、MenuItem10。
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem8Selected(wxCommandEvent& event) {//在表格末尾添加两列,更新标签Grid1->AppendCols(2, true);//在表格末尾添加两行,更新不标签Grid1->AppendRows(2, false); }void tuXFrame::OnMenuItem9Selected(wxCommandEvent& event) { int cCols = Grid1->GetGridCursorCol();int cRows = Grid1->GetGridCursorRow();//在表格当前光标处插入两列,更新标签Grid1->InsertCols(cCols, 2, true);//在表格当前光标处插入两行,更新标签Grid1->InsertRows(cRows, 2, false); }void tuXFrame::OnMenuItem10Selected(wxCommandEvent& event) {int cCols = Grid1->GetGridCursorCol();int cRows = Grid1->GetGridCursorRow();//删除表格光标处的列,更新标签Grid1->DeleteCols(cCols, 1, true);//删除表格光标处的行,更新标签Grid1->DeleteRows(cRows, 1, false); }
构建并运行tuX项目,测试从菜单中新添加操作的效果。在这里展示插入行列的结果,其他的你可以自己去测试。
为了便于测试,需要在表格中添加标记,以便对比操作前后的效果。我们点击菜单Part2下的“设置行列标签”和“测试单元格坐标”,得到如下画面:
点击菜单Grid>>插入行列,得到以下画面:
跟预想的是不是一样?我猜想的好像不是这样,本来我以为不更新标签会把新插入的标签设置成空白,但事实不是这样。画面很直观,对比两张截图就看明白了,我再做解释就是多余的了。哈哈哈!
现在回到前面提到的原英文版教程作者提到的一个问题:把表格设置成20行30列。为了便于查看效果,我们添加一个把表格设置成2行3列的操作。
在菜单编辑器中给Grid添加一个子菜单项,Id为idGridSetRowsAndCols,Label分别为“把表格设置成2行3列”,变量名分别为MenuItem11。
在属性浏览器的事件页给新添加的菜单项的EVT_MENU事件添加事件响应程序,添加以下代码:
void tuXFrame::OnMenuItem11Selected(wxCommandEvent& event) {int cCols = Grid1->GetNumberCols();int cRows = Grid1->GetNumberRows();//调整表格为3列if(cCols != 3){if(cCols>3){cCols = cCols - 3;Grid1->DeleteCols(0, cCols, true);}else{cCols = 3 - cCols;Grid1->InsertCols(0, cCols, true);}}//调整表格为2行if(cRows != 2){if(cRows > 2){cRows = cRows - 2;Grid1->DeleteRows(0, cRows, false);}else{cRows = 2 - cRows;Grid1->InsertRows(0, cRows, false);}} }
构建并运行tuX项目,测试从菜单中新添加操作的效果。测试得到如下画面:
之所以设置删除、插入行列从0开始,是为了检验插入、删除对标签的影响。结果是什么呢?
5.结束语
这一篇教程讲述了wxGrid的部分操作,相对于强大的表格组件来说,这只是冰山一角,只能算是抛砖引玉。你如果有兴趣可以尝试测试更多的操作——任何经验都是从一次次的尝试中得到的。“积土成山,积水成渊”。通过不断的学习和尝试,积累更多的知识和经验,不久的将来从一个初学者变成专家也是可能的。
关于wxSmith这个话题的教程全部完成了,希望能给你提供帮助。
最后说一件不开心的事:在我的教程还没有全部编写完成的时候,偶然的机会发现网上已经有好多人转发了我的教程。
能够有人转发我的教程应该是觉得我的教程有意义,这应该是值得高兴的事,为什么不开心呢?这是因为我发现转发的人抹除了与我有关的任何链接和文字,也抹除了我添加的原英文教程的链接。
这是转发吗?这应该算是剽窃吧?把教程发到网上就是为了共享,转发推广都不是不可以,但保留一丝对原作者的尊重应该是最起码的道德底线吧?
言尽于此,是非对错还是留给读者们去评说吧。