(Ubuntu)Qt下的ROS图形化GUI编程---在模板上加功能+注释+源码

(Ubuntu)Qt下的ROS编程—在模板上加功能+注释+源码

     在上篇介绍了如何创建Qt下ROS图形化GUI工程,创建好之后他会默认给我们一个模板,就在上篇博客中最后有截图到。
     接下来就在工程模板的基础上添加一个订阅者Subscriber,并且画好Ui,实现图形化的Topic发布与订阅。

工程目录如下,我会按照工程目录给出源码和注释。
main_window.cpp:

/**
 * @file /src/main_window.cpp
 *
 * @brief Implementation for the qt gui.
 *
 * @date February 2011
 **/
/*****************************************************************************
** Includes
*****************************************************************************/

#include 
#include 
#include 
#include "../include/testgui/main_window.hpp"

/*****************************************************************************
** Namespaces
*****************************************************************************/

namespace testgui {

using namespace Qt;

/*****************************************************************************
** Implementation [MainWindow]
*****************************************************************************/

MainWindow::MainWindow(int argc, char** argv, QWidget *parent)
	: QMainWindow(parent)
	, qnode(argc,argv)
{
	ui.setupUi(this); // Calling this incidentally connects all ui's triggers to on_...() callbacks in this class.
    //QAPP是应用程序的全局变量
    QObject::connect(ui.actionAbout_Qt, SIGNAL(triggered(bool)),qApp, SLOT(aboutQt())); // qApp is a global variable for the application

    ReadSettings();//执行配置,用于设置初始状态配置
    setWindowIcon(QIcon(":/images/icon.png"));//加载软件图标
    //选项卡控件,在这里把选项设置在第一个上,此信号和槽机制用于向GUI发出关机信号(对RosLaunch有用)
	ui.tab_manager->setCurrentIndex(0); // ensure the first tab is showing - qt-designer should have this already hardwired, but often loses it (settings?).
    QObject::connect(&qnode, SIGNAL(rosShutdown()), this, SLOT(close()));

	/*********************
    ** Logging   数据文本框1发布者
	**********************/
    ui.view_logging->setModel(qnode.loggingModel());//通过调用loggingModel这个函数,返回的是已经在qnode对象用QStringListModel包装好的数据正好是参数需要,然后可以直接设置到文本框中显示
    //自定义信号和槽,用于重新调整滚动条
    QObject::connect(&qnode, SIGNAL(loggingUpdated()), this, SLOT(updateLoggingView()));

    /*********************
    **  add  数据文本框2订阅者
    **********************/
    //通过调用这个loggingModel_sub函数,返回的是已经在qnode对象用QString包装好的数据,然后可以直接设置到文本框中显示
    ui.view_logging_sub->setModel(qnode.loggingModel_sub()); //add
    //自定义信号和槽,用于重新调整滚动条
    QObject::connect(&qnode,SIGNAL(loggingUpdated_sub()),this,SLOT(updateLoggingView_sub())); //add


    /*********************
    ** Auto Start  启动时记住设置控件init
    **********************/
    if ( ui.checkbox_remember_settings->isChecked() ) {
        on_button_connect_clicked(true);
    }
}

MainWindow::~MainWindow() {}

/*****************************************************************************
** Implementation [Slots]
*****************************************************************************/
//弹出提示框
void MainWindow::showNoMasterMessage() {
	QMessageBox msgBox;
	msgBox.setText("Couldn't find the ros master.");
	msgBox.exec();
    close();
}

/*
 * These triggers whenever the button is clicked, regardless of whether it
 * is already checked or not.
 */
//点击连接按钮
void MainWindow::on_button_connect_clicked(bool check ) {
    if ( ui.checkbox_use_environment->isChecked() ) {//if选中单选框(使用环境变量)
        if ( !qnode.init() ) {//直接构造空参失败,就提示一个框
			showNoMasterMessage();
        } else {//否则就开始读取并且把按钮设置不可选
			ui.button_connect->setEnabled(false);
		}
    } else {//如果没有选中单选框(不使用环境变量),就获取文本框填的URL地址和ip,如果失败就弹窗框
		if ( ! qnode.init(ui.line_edit_master->text().toStdString(),
				   ui.line_edit_host->text().toStdString()) ) {
			showNoMasterMessage();
        } else {//如果ok就把东西设置成不可操控
			ui.button_connect->setEnabled(false);
			ui.line_edit_master->setReadOnly(true);
			ui.line_edit_host->setReadOnly(true);
			ui.line_edit_topic->setReadOnly(true);
		}
	}
}

//"是否使用环境变量"单选框状态
void MainWindow::on_checkbox_use_environment_stateChanged(int state) {
	bool enabled;
	if ( state == 0 ) {
		enabled = true;
	} else {
		enabled = false;
	}
	ui.line_edit_master->setEnabled(enabled);
	ui.line_edit_host->setEnabled(enabled);
	//ui.line_edit_topic->setEnabled(enabled);
}

/*****************************************************************************
** Implemenation [Slots][manually connected]  两个文本数据跟新的槽函数
*****************************************************************************/
/**
 * This function is signalled by the underlying model. When the model changes,
 * this will drop the cursor down to the last line in the QListview to ensure
 * the user can always see the latest log message.
 *  *此函数由基础模型发出信号。当模型改变时,
    *这将把光标放到qlistView的最后一行,以确保
    *用户总是可以看到最新的日志消息。
 */
void MainWindow::updateLoggingView() {
        ui.view_logging->scrollToBottom();//用于调整滚动条到底部
}

void MainWindow :: updateLoggingView_sub(){// add
        ui.view_logging_sub->scrollToBottom();//用于调整滚动条到底部
}

/*****************************************************************************
** Implementation [Menu]菜单栏显示文字
*****************************************************************************/

void MainWindow::on_actionAbout_triggered() {
    QMessageBox::about(this, tr("About ..."),tr("

PACKAGE_NAME Test Program 0.10

Copyright Yujin Robot

This package needs an about description.

")); } /***************************************************************************** ** Implementation [Configuration] 执行配置,用于设置初始状态配置 *****************************************************************************/ void MainWindow::ReadSettings() { QSettings settings("Qt-Ros Package", "testgui"); restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("windowState").toByteArray()); QString master_url = settings.value("master_url",QString("http://192.168.1.2:11311/")).toString(); QString host_url = settings.value("host_url", QString("192.168.1.3")).toString(); //QString topic_name = settings.value("topic_name", QString("/chatter")).toString(); ui.line_edit_master->setText(master_url); ui.line_edit_host->setText(host_url); //ui.line_edit_topic->setText(topic_name); bool remember = settings.value("remember_settings", false).toBool(); ui.checkbox_remember_settings->setChecked(remember); bool checked = settings.value("use_environment_variables", false).toBool(); ui.checkbox_use_environment->setChecked(checked); if ( checked ) { ui.line_edit_master->setEnabled(false); ui.line_edit_host->setEnabled(false); //ui.line_edit_topic->setEnabled(false); } } void MainWindow::WriteSettings() { QSettings settings("Qt-Ros Package", "testgui"); settings.setValue("master_url",ui.line_edit_master->text()); settings.setValue("host_url",ui.line_edit_host->text()); //settings.setValue("topic_name",ui.line_edit_topic->text()); settings.setValue("use_environment_variables",QVariant(ui.checkbox_use_environment->isChecked())); settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); settings.setValue("remember_settings",QVariant(ui.checkbox_remember_settings->isChecked())); } void MainWindow::closeEvent(QCloseEvent *event) { WriteSettings(); QMainWindow::closeEvent(event); } } // namespace testgui

main_window.hpp:

/**
 * @file /include/testgui/main_window.hpp
 *
 * @brief Qt based gui for testgui.
 *
 * @date November 2010
 **/
#ifndef testgui_MAIN_WINDOW_H
#define testgui_MAIN_WINDOW_H

/*****************************************************************************
** Includes
*****************************************************************************/

#include 
#include "ui_main_window.h"
#include "qnode.hpp"

/*****************************************************************************
** Namespace
*****************************************************************************/

namespace testgui {

/*****************************************************************************
** Interface [MainWindow]
*****************************************************************************/
/**
 * @brief Qt central, all operations relating to the view part here.
 */
class MainWindow : public QMainWindow {
Q_OBJECT

public:
	MainWindow(int argc, char** argv, QWidget *parent = 0);
	~MainWindow();

	void ReadSettings(); // Load up qt program settings at startup
	void WriteSettings(); // Save qt program settings when closing

	void closeEvent(QCloseEvent *event); // Overloaded function
	void showNoMasterMessage();

public Q_SLOTS:
	/******************************************
	** Auto-connections (connectSlotsByName())
	*******************************************/
	void on_actionAbout_triggered();
	void on_button_connect_clicked(bool check );
	void on_checkbox_use_environment_stateChanged(int state);


    /******************************************
    ** Manual connections
    *******************************************/
    void updateLoggingView(); // no idea why this can't connect automatically

    void updateLoggingView_sub(); //add

private:
	Ui::MainWindowDesign ui;
	QNode qnode;
};

}  // namespace testgui

#endif // testgui_MAIN_WINDOW_H

qnode.cpp:

/**
 * @file /src/qnode.cpp
 *
 * @brief Ros communication central!
 *
 * @date February 2011
 **/

/*****************************************************************************
** Includes
*****************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include "../include/testgui/qnode.hpp"

/*****************************************************************************
** Namespaces
*****************************************************************************/

namespace testgui {

/*****************************************************************************
** Implementation
*****************************************************************************/

QNode::QNode(int argc, char** argv ) :
	init_argc(argc),
	init_argv(argv)
	{}

QNode::~QNode() {
    if(ros::isStarted()) {
      ros::shutdown(); // explicitly needed since we use ros::start();
      ros::waitForShutdown();
    }
	wait();
}

//这个构造函数是使用环境变量执行的构造函数
bool QNode::init() {
    ros::init(init_argc,init_argv,"testgui");//初始化ros节点
    if ( ! ros::master::check() ) {//检查master是否开启
		return false;
	}
//    因为我们的nodehandle超出了范围,所以显式需要。
	ros::start(); // explicitly needed since our nodehandle is going out of scope.
	ros::NodeHandle n;
    // Add your ros communications here.在此处添加您的ROS通信。
	chatter_publisher = n.advertise("chatter", 1000);
    chatter_subscriber = n.subscribe("chatter",1000,&QNode::Callback,this);//add
    start();//开启此线程,一边发布topic信息一边订阅topic消息
	return true;
}

//这个构造函数不使用环境变量
bool QNode::init(const std::string &master_url, const std::string &host_url) //Master的URL地址和IP地址
{
	std::map remappings;
    remappings["__master"] = master_url;//给定Master的URL地址和IP地址,
	remappings["__hostname"] = host_url;
    ros::init(remappings,"testgui");//
	if ( ! ros::master::check() ) {
		return false;
	}
    ros::start(); // 因为我们的nodehandle超出了范围,所以显式需要。
	ros::NodeHandle n;
	// Add your ros communications here.
    chatter_publisher = n.advertise("chatter", 1000);

    chatter_subscriber = n.subscribe("chatter",1000,&QNode::Callback,this); //add
    start();//开启此线程
	return true;
}

void QNode::run() {
    ros::Rate loop_rate(10);
	int count = 0;
	while ( ros::ok() ) {

		std_msgs::String msg;
		std::stringstream ss;
		ss << "hello world " << count;
		msg.data = ss.str();
        chatter_publisher.publish(msg);//

        log(Info,std::string("I sent: ")+msg.data);//调用日志信息函数,第一个参数时信息类型,并将发布的数据组织成字符串

        ros::spinOnce();
		loop_rate.sleep();
		++count;
	}
	std::cout << "Ros shutdown, proceeding to close the gui." << std::endl;
    //发送此信号,至此说明程序异常或者执行完毕
    Q_EMIT rosShutdown(); // used to signal the gui for a shutdown (useful to roslaunch) 用于向GUI发出关机信号(对RosLaunch有用)
}

//日志信息函数
void QNode::log( const LogLevel &level, const std::string &msg) {
	logging_model.insertRows(logging_model.rowCount(),1);
	std::stringstream logging_model_msg;
	switch ( level ) {
		case(Debug) : {
				ROS_DEBUG_STREAM(msg);
				logging_model_msg << "[DEBUG] [" << ros::Time::now() << "]: " << msg;
				break;
		}
		case(Info) : {
				ROS_INFO_STREAM(msg);
				logging_model_msg << "[INFO] [" << ros::Time::now() << "]: " << msg;
				break;
		}
		case(Warn) : {
				ROS_WARN_STREAM(msg);
				logging_model_msg << "[INFO] [" << ros::Time::now() << "]: " << msg;
				break;
		}
		case(Error) : {
				ROS_ERROR_STREAM(msg);
				logging_model_msg << "[ERROR] [" << ros::Time::now() << "]: " << msg;
				break;
		}
		case(Fatal) : {
				ROS_FATAL_STREAM(msg);
				logging_model_msg << "[FATAL] [" << ros::Time::now() << "]: " << msg;
				break;
		}
	}
	QVariant new_row(QString(logging_model_msg.str().c_str()));

    /*
    开始包装数据到QStringListModel类中,随后通过MainWIndow进行函数的调用,在函数中将返回这个包装好的数据
    */

	logging_model.setData(logging_model.index(logging_model.rowCount()-1),new_row);
    Q_EMIT loggingUpdated(); // used to readjust the scrollbar用于重新调整滚动条至底层
}

void QNode::log_sub(const LogLevel&level,const std::string&msg){// add
    logging_model_sub.insertRows(logging_model_sub.rowCount(),1);
    std::stringstream logging_model_msg;
    switch (level)
    {
        case(Debug):{
                ROS_DEBUG_STREAM(msg);
                logging_model_msg <<"[DEBUG] ["<< ros :: Time :: now()<<"]:"<< msg;
                break;
        }
        case(Info):{
                ROS_INFO_STREAM(msg);
                logging_model_msg <<"[INFO] ["<< ros :: Time :: now()<<"]:"<< msg;
                break;
        }
        case(Warn):{
                ROS_WARN_STREAM(msg);
                logging_model_msg <<"[INFO] ["<< ros :: Time :: now()<<"]:"<< msg;
                break;
        }
        case(Error):{
                ROS_ERROR_STREAM(msg);
                logging_model_msg <<"[ERROR] ["<< ros :: Time :: now()<<"]:"<< msg;
                break;
        }
        case(Fatal):{
                ROS_FATAL_STREAM(msg);
                logging_model_msg <<"[FATAL] ["<< ros :: Time :: now()<<"]:"<< msg;
                break;
        }
    }
    QVariant new_row(QString(logging_model_msg.str().c_str()));
    /*
    开始包装数据到QStringListModel类中,随后通过MainWIndow进行函数的调用,在函数中将返回这个包装好的数据
    */
    logging_model_sub.setData(logging_model_sub.index(logging_model_sub.rowCount()-1),new_row);
    Q_EMIT loggingUpdated_sub(); //用于重新调整滚动条
}

//订阅者的回调函数:如果有发布的数据,就订阅并调用log_sub日志函数
void QNode::Callback(const std_msgs::StringConstPtr &submsg)// add
{
    log_sub(Info,std::string(" Success sub : ")+ submsg-> data.c_str());
}


}  // namespace testgui

qnode.hpp:

/**
 * @file /include/testgui/qnode.hpp
 *
 * @brief Communications central!
 *
 * @date February 2011
 **/
/*****************************************************************************
** Ifdefs
*****************************************************************************/

#ifndef testgui_QNODE_HPP_
#define testgui_QNODE_HPP_

/*****************************************************************************
** Includes
*****************************************************************************/

// To workaround boost/qt4 problems that won't be bugfixed. Refer to
//    https://bugreports.qt.io/browse/QTBUG-22829
#ifndef Q_MOC_RUN
#include 
#endif
#include 
#include 
#include 
#include "std_msgs/String.h" //add

/*****************************************************************************
** Namespaces
*****************************************************************************/

namespace testgui {

/*****************************************************************************
** Class
*****************************************************************************/

class QNode : public QThread {
    Q_OBJECT
public:
	QNode(int argc, char** argv );
	virtual ~QNode();
	bool init();
	bool init(const std::string &master_url, const std::string &host_url);
	void run();

	/*********************
	** Logging
	**********************/
	enum LogLevel {
	         Debug,
	         Info,
	         Warn,
	         Error,
	         Fatal
         };
//
	QStringListModel* loggingModel() { return &logging_model; }
	void log( const LogLevel &level, const std::string &msg);

        QStringListModel * loggingModel_sub(){return &logging_model_sub; } //add
        void log_sub(const LogLevel&level,const std :: string&msg); //add
        void Callback(const std_msgs::StringConstPtr &submsg); //add
        //(const std_msgs::StringConstPtr &submsg)

//自定义信号
Q_SIGNALS:
	void loggingUpdated();
        void rosShutdown();
        void loggingUpdated_sub();  //add

private:
	int init_argc;
	char** init_argv;
	ros::Publisher chatter_publisher;
        ros::Subscriber chatter_subscriber; //add
        QStringListModel logging_model;
        QStringListModel logging_model_sub; //add
};

}  // namespace testgui

#endif /* testgui_QNODE_HPP_ */

main.cpp:

/**
 * @file /src/main.cpp
 *
 * @brief Qt based gui.
 *
 * @date November 2010
 **/
/*****************************************************************************
** Includes
*****************************************************************************/

#include 
#include 
#include "../include/testgui/main_window.hpp"

/*****************************************************************************
** Main
*****************************************************************************/

int main(int argc, char **argv) {

    /*********************
    ** Qt
    **********************/
    QApplication app(argc, argv);
    testgui::MainWindow w(argc,argv);//通过改变MainWindow的构造函数,将两个参数传递过来
    w.show();
    app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
    int result = app.exec();

	return result;
}

CMakeLists.txt:

##############################################################################
# CMake
##############################################################################

cmake_minimum_required(VERSION 2.8.0)
project(testgui)

##############################################################################
# Catkin
##############################################################################

# qt_build provides the qt cmake glue, roscpp the comms for a default talker
find_package(catkin REQUIRED COMPONENTS qt_build roscpp std_msgs)
include_directories(${catkin_INCLUDE_DIRS})
# Use this to define what the package will export (e.g. libs, headers).
# Since the default here is to produce only a binary, we don't worry about
# exporting anything. 
catkin_package()

##############################################################################
# Qt Environment
##############################################################################

# this comes from qt_build's qt-ros.cmake which is automatically 
# included via the dependency call in package.xml
rosbuild_prepare_qt4(QtCore QtGui) # Add the appropriate components to the component list here

##############################################################################
# Sections
##############################################################################

file(GLOB QT_FORMS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ui/*.ui)
file(GLOB QT_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} resources/*.qrc)
file(GLOB_RECURSE QT_MOC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} FOLLOW_SYMLINKS include/testgui/*.hpp)

QT4_ADD_RESOURCES(QT_RESOURCES_CPP ${QT_RESOURCES})
QT4_WRAP_UI(QT_FORMS_HPP ${QT_FORMS})
QT4_WRAP_CPP(QT_MOC_HPP ${QT_MOC})

##############################################################################
# Sources
##############################################################################

file(GLOB_RECURSE QT_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} FOLLOW_SYMLINKS src/*.cpp)

##############################################################################
# Binaries
##############################################################################

add_executable(testgui ${QT_SOURCES} ${QT_RESOURCES_CPP} ${QT_FORMS_HPP} ${QT_MOC_HPP})
target_link_libraries(testgui ${QT_LIBRARIES} ${catkin_LIBRARIES})
install(TARGETS testgui RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})


ui如下图所示:
(Ubuntu)Qt下的ROS图形化GUI编程---在模板上加功能+注释+源码_第1张图片过几天会放到github上,到时候直接拿工程编译就好了。

在我电脑上是可以运行,如果有啥不会的,或者创建失败的可以在下面留言。

有错误还请大家指出,可以互相学习,多多进步。

你可能感兴趣的:(机器人,Qt,SLAM,ROS,C++)