ui因为一直想实现QT+CLion+Revit的开发路径,所以从头开始学习QT与C++,在这里记录一下QT遇到的问题及后续的问题。如果开发周期不足估计会用一两周爆肝出来,随时会断更。
目前的思路是C++创建dll,在dll里面编写QT界面,之后用C++ Revit 的API接口实现整个步骤。
中间需要用到sqlite3的数据库组件,目前就想到这么多。现在已经实现c++读取sqlite3并完成数据接口。
由于项目进度,之前为了实现cpp+QT耽误了太多时间,所以国庆肝了三天完成整个程序,能够将整个数据库的结构模型创建在revit中,在思路已经清晰的情况下,写起来速度确实快了很多,用完cpp再用c#发现c#的继承还挺好用的,测试一下整个项目之后再继续修复这个项目。下面是所有楼板的成果,21层的框架结构预估时间在十分钟以内可以完成,我是分块测试没有集中测试,明天去公司测试一下。
inline string UTF8ToGB(const char* str)
{
string result;
WCHAR *strSrc;
LPSTR szRes;
//获得临时变量的大小
int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
strSrc = new WCHAR[i + 1];
MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);
//获得临时变量的大小
i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
szRes = new CHAR[i + 1];
WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);
result = szRes;
delete[]strSrc;
delete[]szRes;
return result;
}
const char* path = strdup(UTF8ToGB(R"(C:\Users\xu.lanhui\Desktop\二次开发\test_db_files\morelevel.db)").c_str());
bool getValueFromDatabase::OpenSQLite(const char* path) {
if(path == NULL)
return false;
int nlen = 0,codepage;
char cpath[130];
wchar_t wpath[130];
codepage = AreFileApisANSI()?CP_ACP:CP_OEMCP;
nlen = MultiByteToWideChar(codepage,0,path,-1,NULL,0);
MultiByteToWideChar(CP_ACP,0,path,-1,wpath,nlen);
nlen = WideCharToMultiByte(CP_UTF8,0,wpath,-1,0,0,0,0);
WideCharToMultiByte(CP_UTF8,0,wpath,-1, cpath,nlen,0,0);
int result = sqlite3_open_v2(cpath,&db,SQLITE_OPEN_READONLY,NULL);
if(result == SQLITE_OK)
return true;
else
return false;
}
cmake_minimum_required(VERSION 3.20)
project(ReadDatabase)
set(CMAKE_CXX_STANDARD 14)
#自动调用moc,uic,rcc处理qt的部分
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_PREFIX_PATH "E:\\Qt\\Qt5.14.2\\5.14.2\\mingw73_64")
find_package(Qt5 COMPONENTS
Core
Gui
Widgets
REQUIRED)
set(SOURCE_FILES library.cpp sql_src/sqlite3.c unit.h ui/pathwindow.cpp ui/pathwindow.h)
add_executable(ReadDatabase ${SOURCE_FILES})
target_link_libraries(ReadDatabase
Qt5::Core
Qt5::Gui
Qt5::Widgets
)
if (WIN32)
set(DEBUG_SUFFIX)
#if (CMAKE_BUILD_TYPE MATCHES "Debug")
#set(DEBUG_SUFFIX "d")
#endif ()
set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
if (NOT EXISTS "${QT_INSTALL_PATH}/bin")
set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..")
endif ()
endif ()
if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"$/plugins/platforms/" )
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll"
"$/plugins/platforms/" )
endif ()
foreach (QT_LIB Core Gui Widgets)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${QT_INSTALL_PATH}/bin/Qt5${QT_LIB}${DEBUG_SUFFIX}.dll"
"$" )
endforeach (QT_LIB)
endif ()
Must construct a QApplication before a QWidget
网上参考了很多链接,有说是debug与realse版本同时生成造成静态库冲突的问题,还有说是定义全局变量导致的问题,后来我发现我的问题是由于
int main() //修改前
int main(int argc , char* argv[]) // 修改后
在类中没有初始化QApplication造成的,归根结底还是自己菜。
3. 创建的ui.cpp,ui.h,ui.ui三件套在cpp中一直报错,但是运行时不报错,先build后运行也是没有问题但是依旧是错误提示不知道是IDE的问题还是确实存在这个事情。
接上面的问题,由于能够显示界面所以昨天降错误忽视,结果今天赋值的时候发现无法赋值,因为ui指针是无效指针,并且无法找到ui_pathwindow.h的文件。注意到QT文件生成时会有一句注释,昨天研究过但是因为懒没有继续深入,造成今天的错误。。。。
// You may need to build the project (run Qt uic code generator) to get "ui_pathWindow.h" resolved
注释告诉我们build项目之前应该先使用uic生成一下ui_pathwindow.h,下面是uic的使用步骤,参照博客:QT中 uic 工具的使用
qmake -project,qmake ReadDatabase.pro
在qmake项目之后会有.pro文件,可以直接去文件夹看名称pathwindow.ui -o ui_pathwindow.h
,头文件名称按照自己的UI文件命名即可如果在QT 5中使用连接器的老式写法,及connector( , SINGNAL() , ,SLOT())
会发现函数无效,比如listview无法调出clicked函数,这时我们需要采用新式写法,直接调取相应构件下的函数,如:QListview::clicked , 如果是自己写的函数,直接 pathWindow::show即可调用。具体的操作可以参照豆子的博客。
解决办法,参照上面难问题,进入ui文件位置输入命令uic pathwindow.ui -o ui_pathwindow.h
重新生成ui-pathwindow.h
文件,有时间的可以直接修改.h文件小的构件可以这样修改,如果改动较大还是重新跑一边比较好。
解决办法:使用内联函数,通过内联函数减少重复定义,解决此问题。
重新生成一遍.h文件以后可以正常显示,不清楚具体的原因,todo:
如果希望给listwidget添加checkbox可参照下面链接:http://www.qtcentre.org/threads/47119-checkbox-on-QListView
code:
for (int i = 0; i < count; ++i) {
QString string = static_cast<QString>(stringList.at(i));
QStandardItem *item = new QStandardItem(string);
item->setCheckable(true);
item->setCheckState(Qt::Checked);
itemModel->setItem(i,item);
}
listview继承自model类,listwidget继承自item,listview相对灵活,listwidget操作方便。具体解释参照链接:QListView和QListWidget的区别
int r_count = ui->listView->model()->rowCount();
for (int i = 0; i < r_count; ++i) {
//selected
QModelIndex b_index = ui->listView->model()->index(i,0);
QString level = b_index.data().toString();
bool checked = b_index.data(Qt::CheckStateRole).toBool();
if(checked)
{
float f_level = level.toFloat();
tFloors.push_back(f_level);
}
}
这里面Filter的分割使用空格,如下面示例
QString fileName = QFileDialog::getOpenFileName(this, tr("open the data file"), "D:/", tr("files(*.db *.ydb)"));
qDebug()<<fileName;
//ui style
//hide row number
ui->tableView->verticalHeader()->hide();
//create model
QStandardItemModel *tableModel = new QStandardItemModel(this);
//set column titles
QStringList columnTitles;
columnTitles<<"Location"<<"Floor"<<"Name";
tableModel->setHorizontalHeaderLabels(columnTitles);
for (int i = 0; i < columns.size(); ++i) {
stcolumn value = columns.at(i);
QString column_locate = QString(value.location.display().c_str());
QString column_name = QString(value.name.c_str());
tableModel->setItem(i,0,new QStandardItem(column_locate));
tableModel->setItem(i,1,new QStandardItem(QString::number(value.floor.evaluate)));
tableModel->setItem(i,2,new QStandardItem(column_name));
qDebug()<<column_locate;
qDebug()<<value.floor.evaluate;
qDebug()<<value.name.c_str();
}
ui->tableView->setModel(tableModel);
我定义了一个类,类中字符串使用const char*
记录值,但是当值存放在vector
容器中时,相应的字符串值变为空值,应该是vector作为一个指针存放数据,const char 此时指向失败,更换为string类型即可正常显示*
QT中文件路径分隔符为/
在Linux中使用这种格式,在Windows中需要将分隔符变为\
,此时需要函数进行转换。来源:https://blog.csdn.net/Littlehero_121/article/details/115078556
QDir::toNativeSeparators(fileName).toStdString().c_str()
经过假期与肠胃炎的争斗,现在继续编写程序-.-。
节前取一个模块的数据,发现一直报错SIGTRAP (Trace/breakpoint trap)
,或是Process finished with exit code -1073740940 (0xC0000374)
,打开搜索引擎开始复制粘贴发现数组错误,或是数组读取出界又或是析构函数发现错误,为此我改动了我的类结构,发现还是此错误,最终发现是因为工厂模式中指向出现错误,将楼板类指向了楼层类,因此直接读取vector容器报错,不过以后碰到数组出界的问题可以参照下面的博客解决。c++调试在容器释放内存时报Unknown Signal 或 Trace/breakpoint trap异常
程序的功能基本完成,简单点来说就是数据库的数据读取与整理,完成后因为要与Revit关联所以中建需要用到VC++,为了方便便将窗口程序打包成了dll,方便后期的使用,中间遇到了一些问题记录一下
- 为了测试方便,所以我将原来的动态链接文件修改为执行文件,只一个修改一下CMAKElist即可
```cmake
# add_executable(ReadDatabase ${SOURCE_FILES})
add_library(ReadDatabase SHARED ${SOURCE_FILES})
```
修改cmake文件后发现一直无法生成dll文件,发现将原有的.exe可执行文件删除后即可成功生成。
CMAKE文件,我们采用静态载入的办法,关于静态载入或是动态载入可以参照Windows:CLion下编写及动/ 静态调用DLL,重新创建项目编写cmake文件,将lib载入即可使用,此处注意一个事情,如果事情像我一样需要载入带有QT窗口的dll,需要将工程文件debug下的三个QT开头的dll一起copy过来,不然会一直报错无法找到文件,这种错误debug也不会有提示,错误名称为:Process finished with exit code -1073741515 (0xC0000135)
,此时我们需要将文件copy到debug文件下面,如下图。之后修改CMAKE文件与窗口文件相同,将QT环境配置好,就可以引用相应的内容,见下图
调用的话需要将dll文件中的部分函数公开,通过在头文件中定义使用extern "C" __declspec(dllexport) int hello2(int argc , char* argv[]);
并在cpp文件中实现,然后在引用文件中声明函数,即可使用函数接口实现功能
extern "C"{
void get_lib();
int hello2(int args,char* argv[]);
}
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
昨天定义了一个单例类,向作为导出类导出交互数据,创建完单例模式之后运行报错重复定义,位置是在我的ui_pathwindow.h
和unit.h
,根据网上的办法
pathwindow.h
/*
* singleton from
* https://zhuanlan.zhihu.com/p/37469260
*/
class getSingletonValue{
private:
static getSingletonValue* instance;
private:
getSingletonValue() = default;
~getSingletonValue() = default;
//prevent copying
getSingletonValue(const getSingletonValue&) =delete;
getSingletonValue& operator=(const getSingletonValue&) = delete;
private:
class _Deletor{
public:
~_Deletor();
static _Deletor deletor;
};
public:
static getSingletonValue* getInstance();
vector<st_slab> _Slabs;
vector<st_wall> _Walls;
vector<st_beam> _Beams;
vector<float> _Levels;
vector<st_column> _Columns;
};
pathwindow.cpp
//must initialization static variables out of class
getSingletonValue* getSingletonValue::instance = NULL;
getSingletonValue *getSingletonValue::getInstance() {
if(instance == NULL)
return new getSingletonValue();
return instance;
}
getSingletonValue::_Deletor::~_Deletor() {
if(getSingletonValue::instance != NULL)
delete getSingletonValue::instance;
}
使用宏定义dll导出接口报错,无法标记dllexport,需要将宏定义放在#include
的上方,让编译器首先编译宏命令即可解决。
extern_dll.h
#ifndef READDATABASE_EXTERN_DLL_H
#define READDATABASE_EXTERN_DLL_H
//1. macro definition
#ifdef DLL_EXPORTS
#define DLL_API_ __declspec(dllexport)
#else
#define DLL_API_ __declspec(dllimport)
#endif
#include "unit.h"
#include "library.h"
using namespace std;
class IDLL{
public:
virtual vector<st_column> GetColumn() = 0;
virtual vector<float> GetLevel() = 0;
virtual vector<st_beam> GetBeam() = 0;
virtual vector<st_slab> GetSlab() = 0;
virtual vector<st_wall> GetWall() = 0;
virtual void Release() = 0;
protected:
getSingletonValue* _instance = getSingletonValue::getInstance();
};
extern "C" DLL_API_ IDLL* __stdcall GetObj();
#endif //READDATABASE_EXTERN_DLL_H
extern_dll.cpp
#define DLL_EXPORTS
#include "extern_dll.h"
#include "library.h"
class ImiDLL: public IDLL{
vector<st_column> GetColumn() override;
vector<float> GetLevel() override;
vector<st_beam> GetBeam() override;
vector<st_slab> GetSlab() override;
vector<st_wall> GetWall() override;
void Release() override;
};
vector<st_column> ImiDLL::GetColumn() {
return _instance->_Columns;
}
vector<float> ImiDLL::GetLevel() {
return _instance->_Levels;
}
vector<st_beam> ImiDLL::GetBeam() {
return _instance->_Beams;
}
vector<st_slab> ImiDLL::GetSlab() {
return _instance->_Slabs;
}
vector<st_wall> ImiDLL::GetWall() {
return _instance->_Walls;
}
void ImiDLL::Release() {
delete this;
}
extern "C" DLL_API_ IDLL* __stdcall GetObj(){
return new ImiDLL;
}
今天继续修复之前的问题,考虑到之前实在CLion中创建的QT窗口,并且使用的时QMainWIndow类,所以这次将项目移植到QT Creater中并使用QWidgets类创建,整体都和之前一样就是修改导出函数和之前不一样。
还是和c++写的时候宏定义不同,如果QT直接创建Library会得到一个*_global文件,直接将文件宏定义复制把这个文件删除就可以了
#if defined(RDSTRUCALSOURCE_LIBRARY)
# define RDSTRUCALSOURCE_EXPORT Q_DECL_EXPORT
#else
# define RDSTRUCALSOURCE_EXPORT Q_DECL_IMPORT
#endif
#ifndef EXTERNAL_DLL_H
#define EXTERNAL_DLL_H
#include "unit.h"
#include "library.h"
#include
using namespace std;
class getSingletonValue{
private:
static getSingletonValue* instance;
private:
getSingletonValue() = default;
~getSingletonValue() = default;
//prevent copying
getSingletonValue(const getSingletonValue&) =delete;
getSingletonValue& operator=(const getSingletonValue&) = delete;
private:
class _Deletor{
public:
~_Deletor();
static _Deletor deletor;
};
public:
static getSingletonValue* getInstance();
vector _Slabs;
vector _Walls;
vector _Beams;
vector _Levels;
vector _Columns;
};
struct IDLL{
virtual vector GetColumn() = 0;
virtual vector GetLevel() = 0;
virtual vector GetBeam() = 0;
virtual vector GetSlab() = 0;
virtual vector GetWall() = 0;
virtual void Release() = 0;
};
class RDSTRUCALSOURCE_EXPORT GetObj{
public:
GetObj();
void MakeShow();
IDLL* GetValue();
private:
IDLL* _value;
};
#endif // EXTERNAL_DLL_H
由于是项目移植所以没有使用QT += sql
这种配置模式,通过点击项目右键添加库选择外部库,选择自己的lib文件夹即可配置,我在这里头文件直接写的绝对路径,这里还没有修改试错过
win32: LIBS += -L$$PWD/../lib/ -lRDstrucalSource
INCLUDEPATH += $$PWD/../
DEPENDPATH += $$PWD/../
和上面的链接库一样的操作步骤,将界面dll,.a,.h文件拷贝到一个文件夹中并添加外部连接,直接在项目中引用头文件即可使用。
#include "widget.h"
#include
#include "E:\QTprogram\lib\external_dll.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
GetObj* getObj = new GetObj();
getObj->MakeShow();
qDebug()<<"success";
return a.exec();
}
其实是堆栈的应用,我们可以将创建窗口指针,show(),程序就会等待界面运行完成后继续运行后续的程序。
void GetObj::MakeShow()
{
//show window
PATHWINDOW *pathwindow = new PATHWINDOW();
pathwindow->show();
}
今天下午没有什么事情,试了一下自己的想法目前一切正常,在下班时完成mingwc++项目间的相互调用,明天继续vc++的调用。放一张成果图。