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;
case 1:
aMode = Aspect_TOR_LEFT_BORDER;
break;
case 2:
aMode = Aspect_TOR_RIGHT_BORDER;
break;
case 4:
aMode = Aspect_TOR_TOP_BORDER;
break;
case 5:
aMode = Aspect_TOR_LEFT_AND_TOP_BORDER;
break;
case 6:
aMode = Aspect_TOR_TOP_AND_RIGHT_BORDER;
break;
case 8:
aMode = Aspect_TOR_BOTTOM_BORDER;
break;
case 9:
aMode = Aspect_TOR_BOTTOM_AND_LEFT_BORDER;
break;
case 10:
aMode = Aspect_TOR_RIGHT_AND_BOTTOM_BORDER;
break;
default:
break;
} // end switch
*( ( Standard_Integer* )&m_XLeft ) = qRound(m_dpiScale*m_Widget->rect().left());
*( ( Standard_Integer* )&m_XRight ) = qRound(m_dpiScale*m_Widget->rect().right());
*( ( Standard_Integer* )&m_YTop ) = qRound(m_dpiScale*m_Widget->rect().top());
*( ( Standard_Integer* )&m_YBottom) = qRound(m_dpiScale*m_Widget->rect().bottom());
}
return aMode;
}
//! Apply the mapping change to the window <me>.
//! and returns TRUE if the window is mapped at screen.
Standard_Boolean OCCT_Window::DoMapping() const{
return Standard_True;
};
//! Returns True if the window <me> is opened
//! and False if the window is closed.
Standard_Boolean OCCT_Window::IsMapped() const{
return !(m_Widget->isMinimized() || m_Widget->isHidden());
}
//! Returns The Window RATIO equal to the physical
//! WIDTH/HEIGHT dimensions
Standard_Real OCCT_Window::Ratio() const{
QRect aRect = m_Widget->rect();
return Standard_Real( aRect.right() - aRect.left() ) / Standard_Real( aRect.bottom() - aRect.top() );
}
void OCCT_Window::Position (Standard_Integer& X1, Standard_Integer& Y1, Standard_Integer& X2, Standard_Integer& Y2) const {
X1 = qRound(m_dpiScale*m_Widget->rect().left());
X2 = qRound(m_dpiScale*m_Widget->rect().right());
Y1 = qRound(m_dpiScale*m_Widget->rect().top());
Y2 = qRound(m_dpiScale*m_Widget->rect().bottom());
}
//! Returns The Window SIZE in PIXEL
void OCCT_Window::Size (Standard_Integer& Width, Standard_Integer& Height) const{
QRect aRect = m_Widget->rect();
Width = m_dpiScale*aRect.width();
Height = m_dpiScale*aRect.height();
}
//! Returns native Window handle (HWND on Windows, Window with Xlib, and so on)
Aspect_Drawable OCCT_Window::NativeHandle() const{
return (Aspect_Drawable)m_Widget->winId();
}
//! Returns parent of native Window handle (HWND on Windows, Window with Xlib, and so on)
Aspect_Drawable OCCT_Window::NativeParentHandle() const{
QWidget* parentWidget = m_Widget->parentWidget();
if (parentWidget) {
return (Aspect_Drawable)parentWidget->winId();
}else{
return 0;
}
}
//! Returns native Window FB config (GLXFBConfig on Xlib)
Aspect_FBConfig OCCT_Window::NativeFBConfig() const{
return nullptr;
}
因此,OCCWidget
构造函数相应部分修改为:
OCCWidget::OCCWidget(QWidget *parent)
: QWidget{parent}
{
...
// 获取窗口句柄并创建OCCT_Window
OCCT_Window* wind = new OCCT_Window(this);
// 设置视图窗口
m_view->SetWindow(wind);
if (!wind->IsMapped()) wind->Map();
}
程序执行结果为:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2023-01-16 c# ?的用法