Qt:简单的记事本小软件

这学期的C++大作业是结合Qt做一个医院的病人排号系统,所以最近在学习Qt。最好的学习方法就是实践,多动手做一些东西才能了解和熟悉Qt的那些库函数,周五晚上做了个记事本小软件,实现了大部分微软记事本的功能。写下留存记录。

来几张效果图吧:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


小软件的主要功能包括:

  • 新建文件,打开文件,保存文件,文件另存为
  • 撤销,重做
  • 复制,剪切,粘贴
  • 改变字体,改变字体颜色

第一部分:

一个一个慢慢说。首先是软件的基本界面,有了界面再添加功能。构成界面的主体就是菜单栏,工具栏和文本区域。

菜单栏:

QMenu *fileMenu;
QMenu *editMenu;
QMenu *helpMenu;

上面代码声明三个菜单项,然后再添加到菜单栏,Qt里一个menuBar()函数返回主窗口的菜单栏。也可以像下面这样用QMenuBar声明一个菜单栏:

QMenuBar *menuBar = new QMenuBar(0);

因为我是直接在MainWindow窗口直接写的代码,就用menuBar()函数了。将菜单项添加到菜单栏:

//实例化
fileMenu = new QMenu(this);//this指当前窗口
editMenu = new QMenu(this);
helpMenu = new QMenu(this);
//添加到菜单栏并命名
fileMenu = menuBar()->addMenu("File");
editMenu = menuBar()->addMenu("Edit");
helpMenu = menuBar()->addMenu("Help");

在每个菜单项下面有好几个功能,比如File菜单下有新建,打开,保存,另存为,打印,退出功能,在Qt里每一个功能作为一个动作,即QAction,这与java不同,java里每一个菜单项的子项是menuItem。声明子菜单项(以File菜单为例):

QAction *newAct; //新建
QAction *openAct; //打开
QAction *saveAct; //保存
QAction *saveAsAct; //另存为
QAction *printAct; //打印
QAction *quitAct; //退出

在Edit,Help菜单下的那些子项也像上面那样声明就行了,这里就不贴代码里。接下来就是实例化每一个动作,以newAct为例:

newAct = new QAction(QIcon("Images/newFile"),"New",this);
newAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
newAct->setStatusTip("New File");

上面三行代码中,第一句实例化一个动作,第一个参数设置它的Icon,即图标,第二个参数是名字,第三个参数是父对象,this指当前窗口。函数原型如下:

QAction(const QIcon & icon, const QString & text, QObject * parent)

第二句代码设置快捷键。第三行代码设置状态栏信息,状态栏在软件的下面,当鼠标停在该动作上时在状态栏左边显示的信息。
接下来就是把这些action添加到菜单项下面。addAction()函数负责完成这个功能,如下:

fileMenu->addAction(newAct);
fileMenu->addSeparator();
fileMenu->addAction(openAct);

addSeparator()函数是在两个动作之间加一条分割线。

依葫芦画瓢其他的一些菜单子项也是这么个步骤,不再详述。接下来是工具栏。

工具栏

当新建工程时主窗口是自带工具栏的,那便是mainToolBar,所以不必另外再定义新的工具栏,直接在它上面添加动作就行了。添加动作和添加分割线的函数与菜单栏一样,示例代码如下:

ui->mainToolBar->addAction(newAct);
ui->mainToolBar->addSeparator();

文本区域

文本区域我用的是QTextEdit类,该类提供一个用来显示和编辑文本的部件,这个类的强大之处是我们平常用的那些操作文本的快捷键它都用实现了,不用自己再写代码。如复制文本Ctrl+C,粘贴文本Ctrl+V,剪贴文本Ctrl+X。主要代码如下:

QTextEdit *textEdit;
textEdit = new QTextEdit();
textEdit->setFont(QFont("宋体", 15));//设置默认字体:字体,字体大小
setCentralWidget(textEdit);//设置文本区域为整个窗口的中央部件

当我们用notepad编辑器的时候,软件右下角会显示鼠标光标所在的行数和列数,这里也实现了这个功能。首先声明一个Label层用来显示信息,然后再把Label添加到状态栏。

QLabel *textInfo;
textInfo = new QLabel();
textInfo->setText("Ready");//设置默认显示内容
statusBar()->addPermanentWidget(textInfo);//添加到状态栏,在软件右下角

statusBar()函数获取主窗口的状态栏,addPermanentWidget()函数添加永久性部件,永久性意味着它不会被临时信息所覆盖,添加到状态栏右边。刚才在菜单栏设置每个动作的信息时,显示的信息是在状态栏左边。然后就是写一个函数获得鼠标光标的位置信息,并把信息显示在Label上。

void MainWindow::showTextInfo(){
    QTextCursor textCursor = textEdit->textCursor();//获得鼠标光标
    int lineNum = textCursor.blockNumber();//行数
    int colNum = textCursor.columnNumber();//列数
    textInfo->setText(tr("Li:%1,Col:%2").arg(lineNum+1).arg(colNum));//显示在Label上,注意:行数是从0开始的
}

最后,也是最重要的一步,谁来调用这个函数?我们怎么知道鼠标光标的位置改变了呢?这就要说到Qt最厉害的地方了,信号与槽。简单来说:槽就是响应函数,信号就是给响应函数发的消息,意思是这个消息一产生,相应的函数就要执行。就像电影《集结号》里,集结的号声一响你就可以撤退了,这里号声就是信号,撤退就是槽。一行代码如下:

 connect(textEdit,SIGNAL(cursorPositionChanged()),this,SLOT(showTextInfo()));

函数原型:

QObject::connect(const QObject * sender, const char * signal, const QObject * receiver, 
    const char * method, Qt::ConnectionType type = Qt::AutoConnection)

参数说明:

  • const QObject * sender :发出信号的对象
  • const char * signal : 信号
  • const QObject * receiver : 接收信号的对象
  • const char * method : 槽
  • Qt::ConnectionType type = Qt::AutoConnection : 连接方式,已经设定为自动连接了,多数情况下不用考虑这个参数

在我们那行代码里,发出信号的对象是文本区域,即textEdit,信号是鼠标光标的位置变了,cursorPositionChanged()函数返回这个信息,这个函数式Qt提供的,接收信号的对象是当前主窗口,槽就是showTextInfo()函数,实时改变状态栏右下角的信息。

第二部分

各个功能的实现。菜单栏一共就三个选项File,Edit,Help,一个一个说。

Edit

Edit菜单项下面有五个子项(动作),分别是:

  • Undo :撤销
  • Redo :重做
  • Cut :剪切
  • Copy :复制
  • Paste :粘贴

这些功能Qt里都提供了函数实现,所以这部分功能就是连接信号与槽就行了,并且那些快捷键操作Qt也实现了。代码如下:

//头文件定义槽
private slots:
    void slotCut();
    void slotCopy();
    void slotPaste();
    void slotRedo();
    void slotUndo();

//源文件实现槽,并与信号连接
void MainWindow::slotCut(){
    textEdit->cut();
}

void MainWindow::slotCopy(){
    textEdit->copy();
}

void MainWindow::slotPaste(){
    textEdit->paste();
}

void MainWindow::slotRedo(){
    textEdit->redo();
}

void MainWindow::slotUndo(){
    textEdit->undo();
}

//连接信号与槽
connect(undoAct,SIGNAL(triggered()),this,SLOT(slotUndo()));
connect(redoAct,SIGNAL(triggered()),this,SLOT(slotRedo()));
connect(cutAct,SIGNAL(triggered()),this,SLOT(slotCut()));
connect(copyAct,SIGNAL(triggered()),this,SLOT(slotCopy()));
connect(pasteAct,SIGNAL(triggered()),this,SLOT(slotPaste()));

刚开始我并不知道Qt提供了这些函数,就自己写了复制和粘贴的函数,当写剪切的函数时,看文档发现Qt已经实现了这些个函数,就拿来用吧。有现成的轮子何必再自己造轮子呢!把自己写的那两个函数的代码也贴上来吧:

void MainWindow::slotCopy(){
    QTextCursor cur = textEdit->textCursor();//返回当前光标对象
    copyText = cur.selectedText();//coptText是一个全局QString对象,获取鼠标选中的文本
}

void MainWindow::slotPaste(){
    textEdit->textCursor().insertText(copyText);//在鼠标光标处插入刚才复制的文本
}

Help

Help菜单项就一个子菜单–about,弹出一个消息窗口,上面显示一些我想说的信息。它的动作名是aboutAct。槽是actAboutAuthor()函数。

void MainWindow::actAboutAuthor(){
    QMessageBox::about(this,"About","This software was made By DJ.");
}

connect(aboutAct,SIGNAL(triggered()),SLOT(actAboutAuthor()));

File

File菜单下有六个子项

  • New :新建
  • Open :打开
  • Save :保存
  • Save As :另存为
  • Print :打印,这个功能是调用打印机的,没实现
  • Quit :退出程序

当要新建,打开其他文件或退出程序时,需要判断当前的文件内容是不是被修改了,如果被修改了,就要提醒用户是不是要保存当前文件,所以需要一个函数来返回这个信息。函数如下:

bool MainWindow::maybeSave(){
    if(textEdit->document()->isModified()){//判断文件是否被修改
        QMessageBox::StandardButtons result;
        result = QMessageBox::warning(this,"Waring","Do you want to save the file?",
                    QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);

        if(result == QMessageBox::Cancel){//取消
            return false;
        }
        if(result == QMessageBox::Save){//保存
            return slotSave();
        }
        if(result == QMessageBox::Discard){//忽略,即不保存
            return true;
        }
    }else{
        return true;
    }
}

当保存文件时,还要判断这个文件是不是存在,如果存在直接保存就好,如果不存在,就要新建一个文件再保存,相当于另存为了。currentFile是全局QString变量,指当前的文件名,如果为空说明需要另外建一个文件保存当前文本信息,就调用另存为的响应函数;非空就直接在当前文件下保存内容。

bool MainWindow::slotSave(){
    if(currentFile.isEmpty()){
        return slotSaveAs();
    } else{
        return saveFile(currentFile);
        }
}

另存为的槽函数如下,getSaveFileName()函数弹出文件保存对话框并返回文件名。第三个参数是默认路径和默认文件名,第四个参数是文件过滤器。如果是一个过滤器多个文件的话,文件后缀以空格分开,如:

"Image Files (*.png *.jpg *.bmp)"

如果是多个过滤器的话,过滤器之间用两个分号隔开,如

"Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)"  

槽函数代码如下:

bool MainWindow::slotSaveAs(){
    QString fileName =QFileDialog::getSaveFileName(this,tr("Save As"),
                                "/home/mary",tr("Text Files (*.txt)"));
    if(fileName.isEmpty()){
        return false;
    }else{
        return saveFile(fileName);
    }
}

saveFile()函数代码如下:

bool MainWindow::saveFile(QString fileName){
    QFile file(fileName);
    if(!file.open(QFile::WriteOnly | QFile::Text)){
        QMessageBox::critical(this,
                              "critical",
                              "cannot write file"
                              );
        return false;
    }else{
        QTextStream out(&file);
        out<<textEdit->toPlainText();
        setFileName(fileName);
        return true;
    }
}

新建文件的槽函数如下:

void MainWindow::slotNew(){
    if(maybeSave()){
        textEdit->clear();
        setFileName("");
    }
}

void MainWindow::setFileName(QString fileName){
    currentFile = fileName;
    textEdit->document()->setModified(false);
    this->setWindowModified(false);
    fileName.isEmpty() ?
                this->setWindowFilePath("new.txt") :
                this->setWindowFilePath(fileName);
}

打开文件的槽函数如下:

void MainWindow::slotOpen(){
    if(maybeSave()){
        QString fileName = QFileDialog::getOpenFileName(this);
        if(!fileName.isEmpty()){
            loadFile(fileName);
        }
    }
}

//加载文件的函数
void MainWindow::loadFile(QString fileName){
    QFile file(fileName);
    if(!file.open(QFile::ReadOnly | QFile::Text)){
        QMessageBox::critical(this,
                              "critical",
                              "cannot read file"
                              );
    }else{
        QTextStream in(&file);
        textEdit->setText(in.readAll());
        setFileName(fileName);
    }
}

退出程序的槽函数如下:

void MainWindow::quitApp(){
    if(maybeSave()){
        qApp->closeAllWindows();//关闭所有窗口
    }
}

说到退出程序,还有另外一个情况要考虑,如果用户直接点击软件右上角的X号关闭软件的话,如果文本内容改变了也要提醒他是否要保存文本,这就要用到Qt的事件机制,写一个函数,参数是关闭事件。代码如下:

void MainWindow::closeEvent(QCloseEvent *e){
    if(maybeSave()){
        e->accept();
    }else{
       e->ignore();//忽略,直接关闭
    }
}

呼呼~终于写完了大部分了,我都写了三个小时了。~~(>_<)~~

第三部分

还有另外两个功能要说一下,工具栏上的更改字体和更改颜色的工能。Qt真是方便啊,它又提供了字体选择和颜色选择面板(好吧,java swing也提供了,而且颜色面板更丰富)。主要代码如下:

void MainWindow::changeFontFamily(){
    QFontDialog fontDlg; //字体选择对话框,可以选择字体,字体样式,大小
    QFont font;
    bool isChanged;

    font = fontDlg.getFont(&isChanged);
    if(isChanged){
        textEdit->setFont(font);
    }
}

void MainWindow::changeFontColor(){
    QColorDialog colorDlg; //颜色选择对话框
    QColor color;

    QPalette palette = textEdit->palette();//获得文本区域的调色板

    color = colorDlg.getColor(Qt::black);//默认是黑色
    if (color.isValid())
    {
        palette.setColor(QPalette::Text, color);//设置字体颜色
        textEdit->setPalette(palette);
    }
}

这里有必要说一下QPalette类,在Qt里每一个控件都有一个调色板,它控制着控件的所有颜色信息。对于文本区域QTextEdit控件,目前我知道两种颜色分量,字体颜色和背景颜色,值分别是QPalette::Text 和 QPalette::Base。所有,其实软件还可以增加一个小功能:改变文本区域的背景颜色。


来两张图片吧:
这里写图片描述
这里写图片描述


还有一个小知识点要记下来:在设置菜单项的Icon时要用到图片资源,要把资源添加到工程中。首先在工程目录下新建Images文件夹,把需要的图片资源全部放进去,然后在Qt Creator里右击工程名,选择添加新文件—>选择Qt Resource File ——->起个文件名,路径选择刚才新建的Images文件的路径—>完成。最后在Qt Creator里的工程目录下就会出现一个“资源”文件夹,打开.qrc结尾的文件,该文件的名字是刚才起的那个名字。添加前缀:必须以 / 开头,然后添加文件,从Images文件里选择要添加的文件资源,添加之后可以给图片起个别名,在程序里直接用别名,这样的好处是即使图片的文件名改了也没关系。

终于写完了!!每一个耐着性子读完这篇文章的朋友,新年快乐!!!!

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页