1,簡單介紹
?????? 今天,我們來模擬offic軟件中的word文檔,運行如圖:
運行程序后會出現主界面,頂端的菜單欄包括“文件”“編輯”“格式”“窗口”和“幫助五個主菜單。
菜單欄下面是工具欄,包含了系統常用的功能按鈕。工具欄有四個工具條,分別將一組相關功能按鈕或控件組織在一起 。
工具欄的第一行有三個工具條: 第一個工具條包括新建、打開、保存、打印等文檔管理功能,
第二個工具條包括撤銷、重做、剪切、復制和粘貼這些最基本的文本編輯功能,
第三個工具條是各種較高級的文字字體格式設置按鈕,包括加粗、傾斜、加下畫線,還包括段落對齊及文本顏色設置。
在工具欄的第二行的工具條中有三個組合選擇框控件,用于為文檔添加段落標號和編號,以及選擇特殊字體和更改字號。利用該工具條可以完成更復雜的文檔排版和字體美化工作。
此外,在圖中還給出了使用該軟件制作出的二個文檔示例。用Qt版MyWord字處理軟件制作出的文檔統一以HTML格式存盤,可使用Web瀏覽器打開觀看效果。
開發這個軟件主要分為如下三個階段進行。
(1) 界面設計開發
界面設計開發內容包括菜單系統設計、工具欄設計、多窗體MDI程序框架的建立及多個文檔子窗口的管理和控制等。
(2) 文本編輯功能實現
文本編輯功能實現主要包括文檔的建立、打開和保存,文本的剪切、復制和粘貼,操作撤銷與恢復等這些最基本的文檔編輯功能。
(3) 排版美化功能實現
排版美化功能實現包括字體選擇,字形、字號和文字顏色的設置,文檔段落標號和編號的添加,段落對齊方式設置等高級功能實現。
但是今天,我們首先從架構設計分析,先完成他的文檔建立,保存等功能,在一步步去實現其他:
2,創建文件
首先我們新建文件,選擇:
名字按自己想法來取,后面這里我們先不要ui文件,選擇mainwindow:
后面就創建好了
對于各種新建,保存,另存為等功能,我們需要新建一個C++類。
類的名字自己取,基類選擇QTextEdit,因為:QTextEdit是Qt中提供的一個用于文本編輯的控件,支持對富文本進行編輯和格式化,可以用于各種應用程序中,如文本編輯器、筆記應用、電子郵件客戶端等。
這樣我們就創建好了一個mychild的源文件和頭文件。
我們說過,我們建立這個類要完成的就是新建,保存等操作,所以我們要定義一些函數和槽函數來實現:
#ifndef MYCHILD_H
#define MYCHILD_H#include<QTextEdit>class MyChild : public QTextEdit
{Q_OBJECT
public:MyChild();void NewFile();//新建文件bool LoadFile(const QString &filename);//導入文件bool Save();//保存bool SaveAs();//另存為bool SaveFile(QString filename);//保存文件QString userFriendlyCurrentFile();//用戶友好型當前文件QString currentFile(){return curFile;};//當前文件void MergeFormationOnWordOrSelection(const QTextCharFormat &format);//格式字體void SetAlign(int align);//對齊void SetStyle(int style);//段落標號,編號等風格protected:void closeEvent(QCloseEvent *event);// 可以通過參數event來控制是否讓窗體關閉。private slots:void documentWasModified();//修改文件private:QString curFile;// 當前文件bool isUntitled;//判斷是否命名bool MaybeSave();//是否保存void SetCurrentFile(const QString &fileName);//設置當前文件QString StrippedName(const QString &fullFilename);// 脫離文件名稱
};
?然后我們就需要在源文件中去實現函數:
3,代碼的實現
3.1MyChild()
首先,在構造函數中,我們需要設置如下:
MyChild::MyChild()
{setAttribute(Qt::WA_DeleteOnClose,true);//關閉窗口時銷毀isUntitled=true;
}
setAttribute用來設置窗口屬性,我們把它設置為:關閉窗口時銷毀。
Qt::WA_DeleteOnClose
- (1)調用close()方法,會向widget發送一個關閉事件(QCloseEvent),如果widget接受了關閉事件,窗口將會隱藏(實際上調用hide())。如果widget不接受關閉事件,那么窗口將什么也不做。也就是說close()方法只會隱藏窗口對象而已,并不會銷毀該對象。
- (2)倘若設置了WA_DeleteOnClose屬性,它接收到QCloseEvent事件后,除了調用hide()方法將窗口隱藏外,同時會調用deleteLater()方法將窗口釋放掉,不會再占用資源。
常用的setAttribute窗口屬性 :
//設置為模態框。(如果再設置無邊框窗口,那么模態會失效,不會阻塞其他窗口,須重新設置)
setAttribute(Qt::WA_ShowModal, true);//如果部件接收了關閉事件,則刪除這個部件,相當于delete
setAttribute(Qt::WA_DeleteOnClose, true);//意思是顯示小部件而不使其處于活動狀態,使它不能獲得焦點
setAttribute(Qt::WA_ShowWithoutActivating,true);//使透明效果生效
setAttribute(Qt::WA_TranslucentBackground);//穿透屬性,可以使部件不可點擊,只顯示外形,適合覆蓋中的部件使用
setAttribute(Qt::WA_TransparentForMouseEvents);//輸入法開關,如果一個編輯框不想讓用戶使用輸入法輸入字符打字,就可以將該屬性設置為false。
setAttribute(Qt::WA_InputMethodEnabled, false);//使用操作系統原生的本地窗口,可以提高兼容性
//但是在linux下,使用該屬性會有問題,因為linux下x11管理的默認原生窗口是白色矩形,就算添加了子部件,也會出現短暫的白色矩形閃爍。
setAttribute(Qt::WA_NativeWindow, true);
3.2 NewFile()
?在新建文件中,我們要保證每次新建文件名稱都不能和上次一樣,就像在電腦上,你多次點擊新建文件 ,他會出現新建文件夾1,2,3.。。。等等。
所以我們要使用靜態變量去實現:static局部變量只初始化一次,在函數退出時它不死亡,下次的運算依據是上一次的結果值。
void MyChild::NewFile()// 新建word文件
{static int sequenceNumber=1;isUntitled=true;curFile=tr("Word文檔-%1").arg(sequenceNumber++);setWindowTitle(curFile);//把新建的名稱設置到標題
}
3.3導入文件LoadFile()
在導入文件中,我們需要先進行判斷,保證導入的不能為空,而且不能為只讀文件。創建一個QByteArray數組來獲取file讀取到的所有文件,然后進行篩選,QTextCodecs 可以用于將一些本地編碼的字符串轉換為 Unicode。
bool MyChild::LoadFile(const QString &filename)// 導入文件
{if(!filename.isEmpty()){if(!QFile::exists(filename)){return false;}QFile file(filename);if (!file.open(QFile::ReadOnly))return false;// 提供一個字節數組,QByteArray可用于存儲原始字節(包括“\ 0” )和傳統的8位 “\ 0” 端接字符串 .QByteArray data=file.readAll();// Qt 使用 Unicode 來存儲、繪制、操作字符串。 在許多情況下,可能希望處理使用不同編碼的數據。QTextCodec *codec=Qt::codecForHtml(data);QString str=codec->toUnicode(data);if(Qt::mightBeRichText(str)){//如果是富文本就設置為HTMLthis->setHtml(str);}else{//否則不是富文本設置為簡單字符串格式str=QString::fromLocal8Bit(data);this->setPlainText(str);}SetCurrentFile(filename);connect(document(),SIGNAL(contentsChanged()),this,SLOT(documentWasModified()));}
}
?對轉化而來的字符串str進行判斷,如果是富文本,就設置為HTML,如果不是,就設置為簡單格式。
然后把打開的文件設置為當前文件。
再連接個槽函數,當問將發生改變時,調用修改文件函數。
3.4 保存文件
保存文件的時候需要判斷是直接保存,還是需要另存為保存。
另存為:
-
從文件對話框獲取文件路徑。
-
如果路徑不為空,則保存文件saveFile()
bool MyChild::Save()// 保存
{if(isUntitled){return SaveAs();}else{return SaveFile(curFile);}
}bool MyChild::SaveAs()// 另存為
{QString filename=QFileDialog::getSaveFileName(this,tr("另存為"),curFile,tr("HTML 文檔(*.html *html);;所有文件(*.*)"));if(filename.isEmpty()){return false;}return SaveFile(filename);
}bool MyChild::SaveFile(QString filename)// 保存文件
{if(!(filename.endsWith(".htm",Qt::CaseInsensitive)||filename.endsWith(".html",Qt::CaseInsensitive))){//默認保存為 HTML 文檔filename+=".html";}QTextDocumentWriter writer(filename);bool success=writer.write(this->document());if(success){SetCurrentFile(filename);}return success;
}
在保存文件這個SaveFile()函數里,我們首先就是判斷,文件結尾是否帶有.htm或者.html后綴,如果字符串的結尾引用.htm或者.html則返回true,否則返回false。忽略大小寫
CaseInsensitive:不區分大小寫,沒有后綴的話,我們需要加上。
QTextDocumentWriter:用于將QTextDocument寫入文件或其他設備的與格式無關的接口。
?保存文件對話框(對于某些格式QTextDocumentWriter可直接保存,其他不支持的格式就用QTextStream以流的形式保存 )
3.5 關閉文件closeEvent()
void MyChild::closeEvent(QCloseEvent *event)
{if(MaybeSave()){event->accept();}else{event->ignore();}
}
?MaybeSave()
bool MyChild::MaybeSave()//判斷是否修改且保存文檔
{if(!document()->isModified()){return true;}QMessageBox::StandardButton ret;ret=QMessageBox::warning(this,tr("Qt Word"),tr("文件'%1'已經被修改,是否保存?").arg(userFriendlyCurrentFile()),QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel);if(ret=QMessageBox::Save){return Save();}else if(ret==QMessageBox::Discard){return false;}return true;
}
我們先進行判斷文件是否被修改過, 是的話返回true;?
3.6 修改文件documentWasModified()
文件更改標簽
?編輯器內容是否被更改,可以使用QTextDocument類的isModified()函數獲知,這里使用了QTextEdit類,document()函數來獲取
?它的QTextDocument類對象。然后使用setWindowModified()函數設置窗口的更改狀態標志“*”,如果參數為true,則將在標題中設置了“[*]”號的地方顯示“*”號,表示該文件已經被修改。
void MyChild::documentWasModified()
{//在設置改變的時候,設置窗口已修改setWindowModified(document()->isModified());
}
QString MyChild::userFriendlyCurrentFile()
{return StrippedName(curFile);
}
QString MyChild::StrippedName(const QString &fullFilename)
{return QFileInfo(fullFilename).fileName();
}
3.7設置當前文件:SetCurrentFile
?canonicalFilePath ()可以除去路徑中符號鏈接,如“.”和“..”等符號。這個函數只是將加載文件的路徑首先保存到curFile中,然后再進行一些狀態的設置
void MyChild::SetCurrentFile(const QString &fileName)// 設置當前文件
{curFile=QFileInfo(fileName).canonicalFilePath();isUntitled=false; //文件已經被保存過document()->setModified(false);//文檔沒有被更改過setWindowModified(false);//窗口不顯示被更改標志setWindowTitle(userFriendlyCurrentFile()+"[*]");//設置窗口標題,userFriendlyCurrentFile ()返回文件名
}
3.8 格式字體設置MergeFormationOnWordOrSelection
?先定位光標就是當前選中文本光標位置。
WordUnderCursor:選擇光標下的單詞。如果光標不在可選字符的字符串中,則不選擇任何文本。
設置字體格式,調用QTextCursor的mergeCharFormat()函數,將參數format所表示的格式應用到光標所在處的字符上
void MyChild::MergeFormationOnWordOrSelection(const QTextCharFormat &format)
{QTextCursor cursor=this->textCursor();if(!(cursor.hasSelection())){cursor.select(QTextCursor::WordUnderCursor);}cursor.mergeCharFormat(format);this->mergeCurrentCharFormat(format);
}
?3.9設置段落對齊格式SetAlign
void MyChild::SetAlign(int align)
{if(align==1){this->setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);//水平靠左}else if(align==2){this->setAlignment(Qt::AlignCenter);//水平方向居中}else if(align==3){this->setAlignment(Qt::AlignRight|Qt::AlignAbsolute);//}else if(align==4){this->setAlignment(Qt::AlignJustify);//水平方向兩端對齊}
}
?設置文本光標,執行文本首部,QTextListFormat 主要用于描述文本符號,編號的格式。
//段落編號
void MyChild::SetStyle(int style)
{//多行文本框光標插入文本QTextCursor cursor=this->textCursor();if(style!=0){QTextListFormat::Style stylename=QTextListFormat::ListDisc;//樣式為圓圈switch(style){default:case 1:stylename=QTextListFormat::ListDisc;break;case 2:stylename=QTextListFormat::ListCircle;//空心圓break;case 3:stylename=QTextListFormat::ListSquare;//方塊break;case 4:stylename=QTextListFormat::ListDecimal;//阿拉伯數字break;case 5:stylename=QTextListFormat::ListLowerAlpha;//拉丁字符小寫break;case 6:stylename=QTextListFormat::ListUpperAlpha;//拉丁字符大寫break;case 7:stylename=QTextListFormat::ListLowerRoman;//小寫羅馬數字break;case 8:stylename=QTextListFormat::ListUpperRoman;//大寫羅馬break;}cursor.beginEditBlock();QTextBlockFormat blockFmt=cursor.blockFormat();QTextListFormat listFmt;if(cursor.currentList()){listFmt=cursor.currentList()->format();}else{listFmt.setIndent(blockFmt.indent()+1);blockFmt.setIndent(0);cursor.setBlockFormat(blockFmt);}listFmt.setStyle(stylename);cursor.createList(listFmt);cursor.endEditBlock();}else{QTextBlockFormat bfmt;bfmt.setObjectIndex(-1);cursor.mergeBlockFormat(bfmt);}
}
?
cursor.beginEditBlock()
是Qt中的一個函數,用于開始編輯操作的分組,以便在多個編輯操作之間進行撤銷和重做。調用此函數后,任何對文本的更改都將被視為一組操作。只有在調用了cursor.endEditBlock()
函數之后,才會結束此組操作,之后才能進行下一個操作分組。
此代碼段中,
- 首先通過
cursor.blockFormat()
獲取當前光標所在文本塊的格式,并通過cursor.currentList()
判斷當前光標所在的文本塊是否是列表。 - 如果是,則獲取列表格式并賦值給
listFmt
變量; - 如果不是,則設置
listFmt
的縮進為當前文本塊縮進加1,并將文本塊縮進設置為0。 - 然后通過
cursor.createList(listFmt)
創建一個新的列表,并將樣式設置為stylename
。 - 最后調用
cursor.endEditBlock()
函數結束此次編輯分組。
?如果style=0:
創建一個QTextBlockFormat對象,bfmt的對象索引設置為-1,
用bfmt塊格式修改當前塊(或包含在選擇中的所有塊)的塊格式。
感謝閱讀!!!!!
?