Open CASCADE+Qt:实现简单的窗口程序
简介
这篇小的博文来说明如何搭建基于 OCCT+Qt 的简单的窗口程序(没有其它多余配置,简单实用)。环境配置这里就不讲了,可以参考官网文档以及网上其它教程,这里默认你已经具有了 OCCT 以及 Qt 的开发环境。本文中介绍一种常用方式来实现基于 OCCT+Qt 的简单的窗口程序,另一种实现方式下期介绍。
方式一
首先创建一个继承自QWidget的类:OCCWidget,配置其构造函数(都是固定配置,没有什么好讲):
OCCWidget::OCCWidget(QWidget *parent)
: QWidget{parent}
{
// 直接绘制在屏幕上
this->setAttribute(Qt::WA_PaintOnScreen);
// 创建连接显示设备
Handle(Aspect_DisplayConnection) m_Aspect_DisplayConnect = new Aspect_DisplayConnection();
// 创建3D接口定义图形驱动
Handle(OpenGl_GraphicDriver) driver = new OpenGl_GraphicDriver(m_Aspect_DisplayConnect);
// 创建3D查看器对象,并指定图形驱动
m_viewer = new V3d_Viewer(driver);
// 创建交互上下文对象,关联到3D查看器
m_context = new AIS_InteractiveContext(m_viewer);
// 创建视图,并关联到3D查看器
m_view = m_viewer->CreateView();
// 获取窗口句柄并创建WNT_Window
WId window_handle = (WId) winId();
Handle(WNT_Window) wind = new WNT_Window((Aspect_Handle) window_handle);
// 设置视图窗口
m_view->SetWindow(wind);
if (!wind->IsMapped()) wind->Map();
}
此外,还需要重写父类中的三个虚函数:paintEngine、paintEvent、resizeEvent:
QPaintEngine* OCCWidget::paintEngine() const
{
return nullptr;
}
void OCCWidget::paintEvent( QPaintEvent* /*theEvent*/ )
{
m_view->Redraw();
}
void OCCWidget::resizeEvent( QResizeEvent* /*theEvent*/ )
{
if( !m_view.IsNull() )
{
m_view->MustBeResized();
}
}
配置好OCCWidget类后,在MainWindow中实例化类OCCWidget:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建一个名为m_occWidget的OCCWidget实例
m_occWidget = new OCCWidget(this);
// 将OCCWidget设置为主窗口的中央部件
this->setCentralWidget(m_occWidget);
}
做好上述配置后,在主函数中实例化MainWindow,并显示基于 OCCT+Qt 的窗口程序:
int main(int argc, char *argv[]){
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
mainWindow.resize(800,600);
return app.exec();
}
运行程序,便可以显示基本的窗口,默认窗口背景为灰色:
避坑处
避坑的地方有两个,其一是在main函数中:
mainWindow.show();
mainWindow.resize(800,600);
上述代码不能交换顺序,需要首先显示出窗口,然后再调用resize函数。该函数会调用我们上文中重写的虚函数使得 OCCT 窗口铺满 Qt 中,否则窗口会出现如下状况:
可以看出来,OCCT 窗口在左下角,没有铺满 Qt 窗口。
第二个需要注意的地方是,MainWindow构造函数中,绑定了 ui:
ui->setupUi(this);
我们打开setupUi函数:
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName("MainWindow");
MainWindow->resize(640, 450);
centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName("centralwidget");
MainWindow->setCentralWidget(centralwidget);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
发现其中有调用resize函数,并且传入了窗口的宽和高分别为 640 和 450,与我们在主函数中调用的 resize 函数传入的宽和高分别为 800 和 600 并不同,由于前者在构造函数中首先被调用,因此,最终显示主函数中 resize 设置的窗口大小。
这里我是故意而为之,如果两者传入参数相同,窗口同样会出现如下状况:
猜测原因是因为,当两者设置相同参数后,Qt机制中默认第二次调用 resize,并不会去调用上文中OCCWidget类中重写的三个虚函数,导致 OCCT 窗口并没有铺满 Qt 窗口。
方式二
方式一中,OCCWidget构造函数中通过获取当前Qt窗口句柄,并创建WNT_Window,从而将OCCT窗口绑定在当前Qt窗口中。
OCCWidget::OCCWidget(QWidget *parent)
: QWidget{parent}
{
...
// 获取窗口句柄并创建WNT_Window
WId window_handle = (WId) winId();
Handle(WNT_Window) wind = new WNT_Window((Aspect_Handle) window_handle);
// 设置视图窗口
m_view->SetWindow(wind);
if (!wind->IsMapped()) wind->Map();
}
尽管OCCT源文件中有该类对应的源文件以及头文件,但我们在最新版本的OCCT手册中却找不到该类。查看OCCT-7.0.0版本手册,可以看到,WNT_Window继承自Aspect_Window:

从7.1.0版本开始,WNT_Window貌似被取消了:

虽然在源文件中,仍然可以找到该类对应的源文件,但可能处于某种原因,OCCT官方并不推荐原来的方式。
那么我们其实可以从Aspect_Window继承一个新类,Aspect_Window是一个抽象类,参考7.8.0版本的帮助文档,要实现以下其纯虚函数:
virtual void Map () const =0
virtual void Unmap () const =0
virtual Aspect_TypeOfResize DoResize () const =0
virtual Standard_Boolean DoMapping () const =0
virtual Standard_Boolean IsMapped () const =0
virtual Quantity_Ratio Ratio () const =0
virtual void Position (Standard_Integer &X1, Standard_Integer &Y1, Standard_Integer &X2, Standard_Integer &Y2) const =0
virtual void Size (Standard_Integer &Width, Standard_Integer &Height) const =0
virtual Aspect_Drawable NativeHandle () const =0
virtual Aspect_Drawable NativeParentHandle () const =0
virtual Aspect_FBConfig NativeFBConfig () const =0
虚函数的实现参考了WNT_Window源文件以及开源程序:
OCCT_Window::OCCT_Window(QWidget* Widget, const Quantity_NameOfColor theBackColor)
: Aspect_Window(),
m_Widget(Widget),
m_dpiScale(Widget->devicePixelRatioF())
{
SetBackground (theBackColor);
m_XLeft = qRound(m_dpiScale*m_Widget->rect().left());
m_YTop = qRound(m_dpiScale*m_Widget->rect().top());
m_XRight = qRound(m_dpiScale*m_Widget->rect().right());
m_YBottom = qRound(m_dpiScale*m_Widget->rect().bottom());
}
//! Opens the window <me>.
void OCCT_Window::Map() const{
m_Widget->show();
m_Widget->update();
}
//! Closes the window <me>.
void OCCT_Window:: Unmap() const{
m_Widget->hide();
m_Widget->update();
}
//! Apply the resizing to the window <me>.
Aspect_TypeOfResize OCCT_Window::DoResize(){
int aMask = 0;
Aspect_TypeOfResize aMode = Aspect_TOR_UNKNOWN;
if ( !m_Widget->isMinimized() )
{
if ( Abs ( m_dpiScale*m_Widget->rect().left() - m_XLeft ) > 2 ) aMask |= 1;
if ( Abs ( m_dpiScale*m_Widget->rect().right() - m_XRight ) > 2 ) aMask |= 2;
if ( Abs ( m_dpiScale*m_Widget->rect().top() - m_YTop ) > 2 ) aMask |= 4;
if ( Abs ( m_dpiScale*m_Widget->rect().bottom() - m_YBottom ) > 2 ) aMask |= 8;
switch ( aMask )
{
case 0:
aMode = Aspect_TOR_NO_BORDER;
break;