单窗口多视口实现思路
众多工程应用软件中,一个模型中查看的数据会多种多样,比如:三维模型视图,受力图表等。然后,在查看过程中,用户可能还需要同时对各种数据进行对比查看等。在以前的软件中,多视口的实现通过多选项卡(多窗口)模式来实现的,如下图,这在软件操作过程中给用户带来了极大的不变,对于多模型来说,更是痛苦至极。
目前多视口的界面大体如下:
其中,左上为工程的整体三维视图,右上为计算域三维网格,左下为切片、矢量图等后处理结果,右下为部分边界网格。
这仅是列举的一小部分,这对于用户来说,查看不同的数据,对比分析也更加直观。而却各个视口独立于其他视口,只与当前的工程有关。用户可以在不同的视口中做不同的操作,不影响其他视口。
实现思路:
多视口中,每个视口都“贴”在一个父窗口上。当视口被分割成两个视口时,首先创建一个父窗口,用来“装”原视口和新创建出来的视口,然后再将创建的父窗口“贴”于原视口的位置。流程如下表示:
实现流程:
通过上述流程,不难发现,这与数据结构中的二叉树的数据结构类似,因此,采用动态数组来描述多视口数据。父窗口采用Qt中的QSplitter类,视口为相应的窗口类。
1. 定义数据元素类型:
struct ViewCell
{
Direction direction; // 方向
double splitFraction; // 分割比例
QWidget* view;
ViewCell() : direction(NONE), splitFraction(0.5), view(0) {}
};
其中,direction和splitFraction当为父窗口时有效。
2. 定义数据结构
QVector<ViewCell> m_Cells;
3. 分割算法:
由于二叉树的特性,每分割一次,数组的变为 (2*location)+2+1个,原视口变为 2*location+1,其中,location为当前窗口的序号(序号按照二叉树的前序序号排列)。然后将location位置的元素类型更改为父窗口内容,将2*location+1位置的元素类型更改为原视口内容,伪代码如下:
int split(int location, Direction direction, double fraction)
{
ViewCell cell = m_Cells[location];
cell.direction = direction;
cell.splitFraction = fraction;
m_Cells.resize(2 * location + 2 + 1);
int child_location = (2 * location + 1);
ViewCell& child = m_Cells[child_location];
child.view = cell.view;
cell.view = NULL;// 默认为空,在创建窗口函数中自动创建
m_Cells[location] = cell;
return child_location;
}
4. 创建窗口:
当二叉树的数据完成后,接下来就需要创建窗口,刷新等操作了。可以按照二叉树的前序遍历进行迭代了。伪代码如下:
QWidget* createWidget(int index, QWidget* parentWdg)
{
Direction direction = m_Cells[index].direction;
QWidget* widget = m_Cells[index].view;
switch (direction)
{
case NONE:
{
if (!widget)
{
widget = new QWidget(this);
}
frame->setParent(parentWdg);
m_Cells[index].view = widget;
return widget;
}
case VERTICAL:
case HORIZONTAL:
{
QSplitter* splitter = qobject_cast<QSplitter*>(widget);
if (!splitter)
{
splitter = new QSplitter(parentWdg);
}
m_Cells[index].view = splitter;
splitter->setParent(parentWdg);
splitter->setOrientation(direction);
// 采用前序遍历,迭代
splitter->insertWidget(0, createWidget(2 * index + 1, splitter));
splitter->insertWidget(1, createWidget(2 * index + 2, splitter));
return splitter;
}
}
return NULL;
}
到此,多视口的大体实现已经完成。