工具及基础

本软件使用qt5.9.9。IDE推荐qt自带的qtcreater,它的帮助是真的好用,可以节省大量的查阅资料时间。

左边是代码,在insert上按F1即可出现右边帮助

qt基础需要了解qt元对象系统qt信号槽系统qt内存管理

信号槽

信号槽是qt特有的信号传递方式。在子类和父类之间如何进行信号传递呢?

  1. 父类和子类相互引用
    父类与子类相互引用解决方法
  2. 定义接口
    定义子类需要使用的接口并且由父类实现,并且把父类的指针以接口的形式传递给子类。这样子类就可以使用父类的部分函数。这样避免了相互引用但是容易引起代码结构混乱,本代码中new_capture/Widgets/Ipoint_position_change就是使用这种思想
  3. 使用信号槽

信号槽可以理解为第二种方法,只是qt在后台帮我们生成了许多代码。它首先需要定义一个信号,然后再适当的时机抛出这个信号,然后等待其他代码接收。

发出信号

#ifndef LIST_ITEM_H
#define LIST_ITEM_H
#include<QWidget>
#include<QLineEdit>
#include<QListWidgetItem>

class List_item : public QWidget
{
Q_OBJECT//想要定义信号必须申明O_OBJECT宏,它相当于注册到了qt的管理系统中,这样qt才可以找到它。也可以认为是添加了这个宏才加入链接。
public:
enum button_type{UP, DOWN, CLOSE, APPEND};
List_item();
List_item(QWidget* parent, QString text, bool useable);
void set_name(QString name);
QString get_name();
signals://定义信号,信号可以使public,也可以是private,因为它一定是给其他人使用的
void text_change(QString text);//参数随便,返回值一定是void
void button_click(button_type index);
private:
QLineEdit* name;
};

#endif // LIST_ITEM_H

//截取List_item.cpp中的部分内容
up_button->setIcon(QIcon(":/image/up.png"));
up_button->setIconSize(QSize(20, 20));
QToolButton* down_button = new QToolButton(this);
down_button->setIcon(QIcon(":/image/down.png"));
down_button->setIconSize(QSize(20, 20));
connect(up_button, &QPushButton::clicked, this, [=](){
emit button_click(UP);//发出信号,参数是UP
});
connect(down_button, &QPushButton::clicked, this, [=](){
emit button_click(DOWN);
});

接收信号

接收信号需要使用slots槽,本代码中多使用lambda表达式,因此主要介绍lambda表达式的实现方式。

//connect是连接信号和槽的函数,list_widget是信号的发出者,它的类型是上面的List_item
//text_change就是上面声明的信号
//this是信号的接受者
//最后一个参数是接收到信号后执行的函数,这里使用c++ lambda表达式。
//可以认为使用这个函数就是生命了一个接口,父类实现了这个接口并且把它传给子类,子类会调用这个接口(发射信号时)
connect(list_widget, &List_item::text_change, this, [=](QString text){
emit text_change(this->indexFromItem(item).row(), text);
});

当然上面只是类比,信号槽系统优点还有它的信号和槽是多对多的关系,可以有一个信号匹配多个槽。

层次管理

窗口化编程一般是建立窗口,然后窗口中添加各种小部件,例如文本框(QLineEdit)、按钮(QPushButton)、滚动区域(QScrollArea)等。并且他们之间的排布可以用QLayout进行管理。大部件如窗口可以添加小部件如QScrollArea,小部件中还可以添加小部件如QLabel(标签)。这些都可以在网上找到具体用法。

qt中的layout主要有QHBoxLayout、QVBoxLayout、QGridLayout。一般来说可以先加载一个QVBoxLayout(垂直排列),然后再添加若干个QHBoxLayout(水平排列)。

QLayout构建时如果添加了父类那么相当于调用了parent->setLayout(layout),这时再调用setLayout(layout)就会出现一些警告,并且有可能导致无法添加组件进入layout的问题。

Hook

Hook其实是另一个项目,为了好看也把它拉到主项目中。

Hook是windows消息钩子,它包括鼠标钩子和键盘钩子,可以使用它勾取一些键盘和鼠标的信息。比如说哪个按键按下和单击、双击等。它是一个动态库并且导入到主程序中

MainWindow

main和MainWindow负责一些重要的初始化。

main负责配置加载,快捷键加载,消息过滤器注册,反射(Reflect)注册等。

MainWindow负责更新检测,内存清理检测、键盘钩子注入、开机启动等。

内存清理检测

QTimer* timer = new QTimer(this);
//window_manager_thread是实际执行内存清理的函数,它位于Window_manager中
connect(timer, SIGNAL(timeout()), this, SLOT(window_manager_thread()));
int time = Config::get_config(Config::clear_interval);
timer->start(time * 1000);

Base

Record_element

执行撤销和恢复功能的基类

virtual bool undo(int index=0)=0;//撤销,index和返回值都是为了橡皮擦的擦除功能而建立的,返回值表示当前index是否有效,它的管理类为Recorder,在Paint/Widgets中
virtual bool redo(int index=0)=0;

Serializable

序列化和反序列化的基类。

函数

实现json序列化需要实现这两个函数,他们会在Helper/Serialize中调用
virtual void serialized(QJsonObject* json) = 0;
virtual void deserialized(QJsonObject* json)=0;

Tab_base

设置中widget的基类,每一个tab_widget就是设置中的一行。具体实现在Panel(Setting_panel)/Widgets/…_tab.

  • virtual QString get_name()=0;: 获得tab的名字
  • virtual int get_begin_index()=0; 获得tab的序号
  • virtual int get_default_index()=0; 获得tab的序号加上tab的当前下拉列表中的序号(只在Combo_tab中使用,也就是下拉列表)
  • virtual void reset()=0; 重置,如果没有保存就退出需要重置为初始值

Window_base

Window_base是各个Window的基类,Window_manager通过这个类定义了管理Window需要的动作。Window包括MainWindow、Capture_window、Paint_window、Setting

  • virtual void on_window_select(): 当window选择时调用
  • virtual void on_window_cancal(): 当window隐藏时调用
  • virtual void on_window_close(): 当window销毁时调用
  • virtual void load_key_event(QString name): 载入快捷键,name是window的名字,例如MainWindow

Helper

debug

  • static void show_error_message(QString message); 显示错误信息的弹出框
  • static void debug_print_warning(QString message); 在控制台显示错误信息

EnumReflect

EnumReflect是构建Enum的帮助类,它可以构建Enum时生成Enum的字符串。

  • QString eto_string(enum_type type): 通过enum找到对应字符串
  • enum_type efrom_string(std::string str): 从字符串找到对应的enum
  • MAKE_ENUM: 这是一个宏,通过它可以生成上面两个函数。具体使用例子可以看Manager/config
  • DEFINE_STRING(class_name): 如果这个enum在类中,由于MAKE_ENUM中使用了static,需要在类外面初始化,因此需要在类外使用DEFINE_STRING宏

mstring

MString是为了翻译的一个帮助类,它内部有一个哈希表维护着字符串ID到字符串的映射。

MString的字符串书写规则为"{ID}content",如果匹配到了ID,则会使用ID对应的字符串,如果没有匹配到则使用content。

  • QString search(QString str): 搜索字符串,可以使用上面的形式,也可以不用加{},这样会直接返回原字符串
  • void load_from_file(QString path): path是对应翻译文件的目录名,它的使用在Window_base和Config中

plist

是QList的扩展,如果它容纳的是指针的话可以使用对应函数快速删除指针并且将她移除列表。

  • remove_one(int index):
  • remove_range(int begin, int end)
  • remove_all():

Reflect

Reflect是反射类,即根据字符串获得函数或类实例。它借助了qt的元对象系统,使用它的条件有:

  1. 继承QWidget为基础的类并且添加Q_OBJECT
  2. 构造函数前添加Q_INVOKABLE
  3. 调用registerClass

完成上面的步骤之后就可以使用newInstance获得对象实例了。例如:

void Window_manager::create_window(QString name)
{
if(window_list.find(name) == window_list.end())
{
if(name == "MainWindow")
{
window_list[name] = create_data(static_cast<Window_base*>(Reflect::newInstance(name)));
}
else
{
if(window_list.find("MainWindow") == window_list.end())
{
window_list["MainWindow"] = create_data
(static_cast<Window_base*>(Reflect::newInstance("MainWindow")));
}
window_list[name] = create_data(static_cast<Window_base*>(
Reflect::newInstance(name, Q_ARG(QWidget*, window_list["MainWindow"].window))));//一个参数
}
}
}

Serialize

序列化类,可以增添,获取,写入等。

  • static void serialize(QString path, Serializable* point): 写json文件到path中,会调用point的serailized函数
  • static bool deserialize(QString path, Serializable* point):返回值表示读文件成功与否
  • static void append(QString path, Serializable* point): 修改内容到文件中
  • static bool deserialize_data(QByteArray data, Serializable* point): 从data中获取信息

Manager

Config

Upgrate(Update)