一、项目概述
1.功能介绍
•支持文本创建,打开,保存,关闭的功能
•UI样式美化
•添加打开快捷键,添加保存快捷
•底部显示行列号及文本字符编码
•Ctrl加鼠标滚轮支持字体放大缩小
2.界面预览
二、常用控件
在此主要罗列一些主要使用的类,具体请查阅QT帮助手册
•按键 QPushButton
•文本编辑窗口 QTextEdit
•文件操作类 QFile
•文本流QTextStream
•文件选择对话框 QFileDialog
•创建下拉列表QComboBox
•容器类QList
•显示消息框QMessageBox
•快捷键QShortcut
三、重要知识点
1.信号与槽
提出疑问,界面上已经有按键了,怎么操作才能让用户按下按键后有操作上的反应呢?
在 Qt 中,信号和槽机制是一种非常强大的事件通信机制。
概要
•信号 (Signals):是由对象在特定事件发生时发出的消息。例如, QPushButton 有一个
clicked() 信号,当用户点击按钮时发出。
•槽 (Slots):是用来响应信号的方法。一个槽可以是任何函数,当其关联的信号被发出时,该槽函数将被调用。
•连接信号和槽:使用 QObject::connect() 方法将信号连接到槽。当信号发出时,关联的槽函数
会自动执行。
以下是Qt信号和槽的几种常见连接方式:
自定义信号与槽
•定义信号:在Qt中,信号是由signals 关键字声明的类成员函数。它们不需要实现只需声明。如:
class MyClass : public QObject {
Q_OBJECT
public:
MyClass();
signals:
void mySignal(int value);
};
在上面的例子中, MyClass 有一个名为mySignal 的信号,它带有一个整型参数。
•定义槽:槽可以是任何普通的成员函数,但通常在类定义中用slots 关键字标。槽可以有返回类型,也可以接受参数,但它们的参数类型需要与发出信号的参数类型匹配。例如:
class MyClass : public QObject {
Q_OBJECT
public slots:
void mySlot(int value);
};
在这个例子中,我们定义了一个名为mySlot 的槽,它接收一个整型参数。
•连接信号与槽:使用QObject::connect 函数将信号与槽连接起来。当信号被发射时,连接到这个信号的槽将被调用。
MyClass *myObject = new MyClass();
connect(myObject, SIGNAL(mySignal(int)), myObject, SLOT(mySlot(int)));
这行代码连接了myObject 的mySignal 信号到同一个对象的mySlot 槽。
•发射信号:使用emit 关键字发射信号。当信号被发射时,所有连接到这个信号的槽都会被调用。
emit mySignal(123);
这行代码将触发所有连接到mySignal 的槽。
自定义信号和槽是Qt编程中非常强大的特性,它们使得组件之间的通信变得灵活而松耦合。通过信和槽,可以方便地实现各种复杂的事件驱动逻//辑。
2.事件
(1)事件处理过程
当窗口事件产生之后,事件会经过: 事件派发 -> 事件过滤->事件分发->事件处理几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作,比如信号与槽就是一种。
事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。每一个Qt应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的exec() 函数,这样Qt框架内部的事件检测就开始了( 程序将进入事件循环来监听应用程序的事件)。
事件在Qt中产生之后,的分发过程是这样的:
1. 当事件产生之后,Qt使用用应用程序对象调用notify() 函数将事件发送到指定的窗口:
[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
2. 事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤 :
// 需要先给窗口安装过滤器, 该事件才会触发
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
3. 当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
[override virtual protected] bool QWidget::event(QEvent *event);
4. 事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函
数),比如:鼠标事件:
// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
(2)事件过滤器
在Qt的事件处理过程中,引入事件过滤器(Event Filter)可以让你在事件达到目标对象之前进行拦截和处理。这是一种强大的机制,允许你在不同对象间共享事件处理逻辑或在父对象中集中处理特定事件。下面是加入事件过滤器的步骤:
1. 定义事件过滤器: 事件过滤器通常是一个重写了QObject::eventFilter() 方法的对象。这个方法
会在事件传递给目标对象之前被调用。
2. 安装事件过滤器: 使用QObject::installEventFilter() 方法安装事件过滤器。这个方法告诉Qt
在将事件发送给特定对象之前先通过过滤器对象。例如,如果你想在父窗口中过滤子窗口的事件,
你需要在父窗口的对象上调用installEventFilter() ,并将子窗口作为参数传递。
3. 事件过滤器逻辑: 在eventFilter() 方法内部,你可以编写自定义逻辑来决定如何处理或忽略事
件。如果此方法返回true ,则表示事件已被处理,不应该继续传递;如果返回false ,则事件将
正常传递给目标对象。
4. 事件分发: 当事件发生时,Qt首先将事件发送到安装了事件过滤器的对象。在这一步,
eventFilter() 方法被调用。
5. 决定是否传递事件: 根据eventFilter() 方法的返回值,Qt决定是否继续向目标对象传递事件。如
果过滤器返回true ,事件处理到此结束;如果返回false ,事件继续传递到原始目标对象。
6. 目标对象处理事件: 如果事件过滤器允许事件继续传递,目标对象将像没有事件过滤器存在时那样处理事件。
适用情况:
•当你想在不修改子类代码的情况下改变事件的行为。
•当多个对象需要共享相同的事件处理逻辑。
•当你需要在更高的层级上监控或修改应用程序的事件流。
四、项目源码
https://gitee.com/GeekerGao/notepad
部分代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QFileDialog>
#include <QGuiApplication>
#include <QKeyEvent>
#include <QMessageBox>
#include <QShortcut>
#include <QTextEdit>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->textEdit->installEventFilter(this);
//虽然上面一行代码进行widget和ui的窗口关联,但是如果发生窗口大小变化时,里面的布局不会随之改变
//通过下面这行代码进行显示说明,让窗口变化时,布局及其子控件随之调整
this->setLayout(ui->verticalLayout);
//快捷键(打开、保存、放大、缩小)设置
QShortcut *shortcutOpen = new QShortcut(QKeySequence(tr("Ctrl+O", "File|Open")),this);
QShortcut *shortcutSave = new QShortcut(QKeySequence(tr("Ctrl+S", "File|Save")),this);
QShortcut *shortcutZoomIn = new QShortcut(QKeySequence(tr("Ctrl+Shift+=", "File|Save")),this);
QShortcut *shortcutZoomOut = new QShortcut(QKeySequence(tr("Ctrl+Shift+-", "File|Save")),this);
//绑定信号与槽
connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(oncurrentIndexChanged(int)));
connect(ui->textEdit,SIGNAL(cursorPositionChanged()),this,SLOT(oncursorPositionChanged()));
connect(shortcutOpen,&QShortcut::activated,[=](){
on_btnOpen_clicked();
});
connect(shortcutSave,&QShortcut::activated,[=](){
on_btnSave_clicked();
});
connect(shortcutZoomIn,&QShortcut::activated,[=](){
zoomIn();
});
connect(shortcutZoomOut,&QShortcut::activated,[=](){
zoomOut();
});
}
Widget::~Widget()
{
delete ui;
}
//快捷键放大字体
void Widget::zoomIn()
{
//获得TestEdit的当前字体信息
QFont font = ui->textEdit->font();
//获得当前字体大小
int fontSize = font.pointSize();
if(fontSize == -1) return;
//改变大小,并设置字体大小
int newFontSize = fontSize + 1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}
//快捷键缩小字体
void Widget::zoomOut()
{
//获得TestEdit的当前字体信息
QFont font = ui->textEdit->font();
//获得当前字体大小
int fontSize = font.pointSize();
if(fontSize == -1) return;
//改变大小,并设置字体大小
int newFontSize = fontSize - 1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}
//利用事件过滤器实现ctrl+滚轮实现字体放大缩小
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched);//使用Q_UNUSED宏来显式标记未使用的参数watched
if(event->type() == QEvent::Wheel){
if(QGuiApplication::keyboardModifiers() == Qt::ControlModifier){
QWheelEvent *wheelEvent = dynamic_cast<QWheelEvent*>(event);
if(wheelEvent && wheelEvent->angleDelta().y() > 0){
zoomIn();
}else if(wheelEvent && wheelEvent->angleDelta().y() < 0){
zoomOut();
}
return true;
}
return false;
}
return false; //添加这行代码,确保所有路径都有返回值
}
//打开
void Widget::on_btnOpen_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open Image"),
"D:/Study/",
tr("Text(*.txt)"));
ui->textEdit->clear();
file.setFileName(fileName);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)){
qDebug() << "file open error!";
}
this->setWindowTitle(fileName + "-高英杰记事本");
QTextStream in(&file);
QString str = ui->comboBox->currentText(); //把QString转化成char *
const char* c_str = str.toStdString().c_str();
in.setCodec(c_str);
while(!in.atEnd()){
QString context = in.readLine();
ui->textEdit->append(context);
}
}
//保存
void Widget::on_btnSave_clicked()
{
//判断文件没有打开,执行下面代码
if(!file.isOpen()){
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
"D:/Study/untitled.txt",
tr("Text (*.txt *.doc)"));
file.setFileName(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error!";
}
this->setWindowTitle(fileName + "-高英杰记事本");
}
//文件写入
QTextStream out(&file);
out.setCodec(ui->comboBox->currentText().toStdString().c_str());
QString context = ui->textEdit->toPlainText();
out << context;
}
//关闭
void Widget::on_btnClose_clicked()
{
QMessageBox msgBox;
int ret = QMessageBox::warning(this, tr("高英杰记事本 注意:"),
tr("当前文件未保存,请问是否保存?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel,
QMessageBox::Save);
switch (ret) {
case QMessageBox::Save:
on_btnSave_clicked();
break;
case QMessageBox::Discard:
ui->textEdit->clear();
if(file.isOpen()){
file.close();
this->setWindowTitle("高英杰记事本");
}
break;
case QMessageBox::Cancel:
break;
default:
break;
}
}
//在下拉框索引改变时,从一个已经打开的文件中读取内容,并将其显示在用户界面中的一个文本编辑器中。
void Widget::oncurrentIndexChanged(int)
{
ui->textEdit->clear();
if(file.isOpen()){
QTextStream in(&file);
//确保文件以正确的编码方式读取
in.setCodec(ui->comboBox->currentText().toStdString().c_str());
//将文件指针移动到文件的开头
file.seek(0);
//逐行读取文件中的内容,直到到达文件末尾
while(!in.atEnd()){
QString context = in.readLine();
ui->textEdit->append(context);
}
}
}
//显示光标位置行列序号
void Widget::oncursorPositionChanged()
{
QTextCursor cursor = ui->textEdit->textCursor();
//qDebug() << cursor.blockNumber() + 1 << cursor.columnNumber() + 1;
QString blockNum = QString::number(cursor.blockNumber() + 1);
QString columnNum = QString::number(cursor.columnNumber() + 1);
const QString labelMes = "第" + blockNum + "行" + "第" + columnNum + "列";
ui->labelPosition->setText(labelMes);
//设置当前行高亮
QList<QTextEdit::ExtraSelection> extraSelections;
QTextEdit::ExtraSelection ext;
//1.知道当前行
ext.cursor = ui->textEdit->textCursor();
//2.颜色
QBrush qBrush(Qt::yellow);
ext.format.setBackground(qBrush);
//配置段属性:整行显示,没有这句话不行
ext.format.setProperty(QTextFormat::FullWidthSelection,true);
//3.设置
//把ext加入到ext容器中
extraSelections.append(ext);
ui->textEdit->setExtraSelections(extraSelections);
}