< Lập trình tân binh | 3.12. [Thực hành] BanDoX, trình duyệt web của Tân binh

3.12. [Thực hành] BanDoX, trình duyệt web của Tân binh

Từ khi chúng ta lần đầu làm quen với Qt đến giờ, chúng ta hầu như chỉ thảo luận về những thành phần cơ bản Qt. Nhiều bạn sẽ bắt đầu thắc mắc là liệu chúng ta có thể làm được gì chỉ với những công cụ đơn giản như vậy. Vậy thì những bài thực hành dạng này chính là dành cho các bạn. Đây là cơ hội để chứng tỏ rằng chỉ với những kiến thức nền tảng đó, chúng ta có thể tạo ra các sản phẩm mà không lâu trước các bạn vẫn luôn cho là xa vời và cần đến đội ngũ phát triển hùng hậu.

Không đùa nhé, mục tiêu của bài thực hành lần này là tạo ra 1 …trình duyệt web ! Đừng lo, dễ như lấy đồ trong túi ấy mà.

Chúng ta sẽ bắt đầu bằng việc khám phá về khái niệm động cơ web để hiểu hơn cơ chế hoạt động của những trình duyệt khác. Tiếp đó, chúng ta sẽ cùng phác thảo qua về lộ trình xây dựng chương trình này để chắc chắn rằng mọi người đều đi đúng hướng và chúng ta không bỏ quên chi tiết nào.

Trình duyệt web và động cơ web

Như mọi khi, trước khi cắm đầu vào viết mã rồi vò đầu bứt tóc để chữa lỗi, chúng ta sẽ cùng ngồi lại 1 chút để suy nghĩ về những gì chúng ta định viết. Đây được gọi là là pha thiết kế.

Tôi biết là đã nhắc đi nhắc lại điều này vô số lần nhưng tôi thấy nó rất đáng giá vì đây là 1 bước rất quan trọng. Bởi vì nếu tôi chỉ đưa ra yêu cầu là tạo ra 1 trình duyệt web rồi để các bạn ngay lập tức lao vào tạo dự án mới và viết mã cho hàm main() thì chắc chắn kết quả chờ đợi chỉ là 1 sản phẩm thất bại.

Thực ra, pha thiết kế chính là bước khó khăn nhất trong cả dự án, thậm chí là khó hơn nhiều phần viết mã. Không khó hiểu bởi vì một khi chúng ta đã thiết kế rõ ràng cơ chế hoạt động của chương trình thì sẽ tránh được việc phải viết những đoạn mã khó nhằn không cần thiết và đơn giản hóa đoạn mã thực thi rất nhiều.

Trong phần mở đầu dưới đây, tôi sẽ giải thích qua cho các bạn về cách hoạt động của các trình duyệt web, 1 chút kiến thức thường thức để hiểu hơn về những gì chúng ta định thực hiện.

Sau đấy, chúng ta sẽ cùng điểm qua 1 vài lời khuyên về cách tổ chức mã nguồn chương trình : tạo ra lớp nào, bắt đầu từ đâu, vv…

Các trình duyệt phổ biển

Bắt đầu từ cơ bản của cơ bản : thế nào là trình duyệt web ?

Câu hỏi có vẻ ngớ ngẩn nhưng giúp chúng ta chắc chắn là không có ai tụt lại phía sau.

Trình duyệt web là 1 chương trình cho phép chúng ta tra cứu các trang web. Các trình duyệt nổi tiếng nhất có thể kể đến là Internet Explorer, Mozilla Firefox, Google Chrome hay là Safari. Ngoài ra còn vô số những trình duyệt ít được sử dụng hơn như Opera, Konqueror, Lynx, vv…

Thật may là không cần biết đến tất cả các trình duyệt chỉ để tạo ra 1 chương trình tương tự như thế. Tuy nhiên, cái chúng ta cần biết, đó là mỗi trình duyệt được cấu thành từ cái mà chúng ta gọi là động cơ web. Vậy thì đó là gì ?

Động cơ web

Tất cả các trang web đều được viết bằng ngôn ngữ HTML hoặc XHTML. Dưới đây là ví dụ đoạn mã HTML của 1 trang web đơn giản.

<!DOCTYPE html>
<html>
   <head>
      <title>Trang web thần thánh</title>
      <meta charset="utf-8"/>
   </head>
   <body>
   </body>
</html>

Trong mắt lập trình viên chúng ta thì đoạn mã trên vừa gọn vừa đẹp biết bao. Tuy nhiên, đấy không phải những gì người dùng « muốn » thấy khi duyệt trang hằng ngày.

Vậy nên mục tiêu là chuyển đổi đoạn mã trên thành kết quả thực tế hơn, giống những trang web mà chúng ta vẫn gặp trên mạng. Đây là vai trò của động cơ web.

Cơ chế hoạt động của chúng được thể hiện trong minh họa dưới đây.

Nghe thì có vẻ đơn giản nhưng đây thực sự là công việc rất khó khăn và khá tinh tế. Các động cơ web thường là thành quả nỗ lực của nhiều lập trình viên kinh nghiệm. Thậm chí họ thú thực đôi khi cũng thấy nó khá là khó nuốt. Một vài động cơ được đánh giá là tốt hơn những cái khác nhưng không một động cơ nào có thể nhận là tuyệt đối hoàn chỉnh. 1 động cơ web hoàn hảo gần như là không thể tồn tại bởi thế giới mạng vẫn đang thay đổi từng ngày.

Khi lập trình 1 trình duyệt web, người ta thường sử dụng động cơ web như 1 thư viện phụ thuộc của chương trình, có nghĩa là động cơ web không phải là 1 chương trình mà là 1 thành phần được sử dụng bởi các chương trình khác.

Sẽ dễ dàng hơn nếu chúng ta có hình minh họa, hãy xem thử Firefox được cấu thành thế nào.

Vùng màu xanh là trình duyệt web còn vùng màu vàng được chứa bên trong nó là động cơ web.

? Thật nhảm nhí ! Vậy trình duyệt web chỉ là 2, 3 cái nút bé tẹo bên trên thế thôi à ?

Không hề, trình duyệt thực hiện nhiều hơn những gì các bạn nghĩ. Thực vậy, chúng không chỉ khống chế hoạt động của các nút « Trang trước », « Trang sau », « Tải lại trang »/ « Làm mới trang » mà còn quản lý danh sách « Trang ưa thích », hệ thống hiển thị nhiều thẻ, tùy chỉnh hiển thị hay vùng tìm kiếm, vv…

Tất cả chỗ đó cũng đã là 1 lượng công việc khá khổng lồ rồi ! Trong thực tế, đội ngũ phát triển Firefox cũng không phải một đội với nhóm phát triển động cơ web của họ. Họ có nhiều ê kíp hoạt động độc lập, song song với nhau trên các bộ phận khác nhau của trình duyệt.

1 phần lớn các trình duyệt trên thị trường thậm chí không quan tâm đến mảng động cơ web. Chúng hài lòng với việc sử dụng các gói có sẵn. Chính vì thế chúng ta hay bắt gặp những trình duyệt sử dụng cùng 1 nền tảng động cơ web, ví dụ như Google Chrome, Safari hay Cốc cốc, vv… đều sử dụng Webkit, là 1 trong những động cơ nổi tiếng nhất.

Tạo ra 1 động cơ web nằm ngoài trình độ hiện thời của các bạn (thậm chí cả của tôi) nên chúng ta sẽ, giống như nhiều trình duyệt khác, sử dụng 1 động cơ có sẵn.

Vậy thì phải chọn động cơ nào ? Qt khuyến khích sử dụng Webkit trong chương trình của họ. Vậy nên đây sẽ là lựa chọn cho giải pháp phần mềm của chúng ta.

Tùy chỉnh dự án để sử dụng Webkit

Webkit là 1 trong các gói thư viện của Qt. Nó không nằm trong gói GUI mà chúng ta vẫn hay sử dụng để tạo cửa sổ mà nằm trong 1 gói tách biệt.

Để có thể sử dụng nó, chúng ta cần thay đổi tệp .pro của dự án để Qt biết là nó cần phải tải Webkit.

Đây là ví dụ về tệp .pro của dự án sử dụng Webkit.

QT += widgets webkitwidgets

# Input
HEADERS += CuaSoChinh.h
SOURCES += CuaSoChinh.cpp main.cpp

Ngoài ra, chúng ta cũng cần thêm 1 câu lệnh include vào trong tệp mã nguồn mà chúng ta đinh sử dụng Webkit.

#include <QtWebKitWidgets>

Cuối cùng, chúng ta cần thêm 1 số DLL mới nếu muốn chương trình hoạt động độc lập.

Sẵn sàng rồi chứ ? Chúng ta bắt đầu thôi.

Tổ chức mã nguồn dự án
Mục tiêu

Trước hết, chúng ta cần định hình rõ trong đầu về chương trình mà chúng ta muốn viết.

Trong số những siêu cấp tính năng của trình duyệt mà chúng ta gọi là « BanDoX », có thể kể ra 1 số tính năng cơ bản.

  • Di chuyển tới trang trước và sau trong lịch sử duyệt web.
  • Dừng việc tải trang
  • Tải lại, làm mới trang
  • Quay về trang chủ
  • Nhập địa chỉ trang web muốn xem
  • Chuyển thẻ duyệt web
  • Hiển thị tiến độ tải trang trong thanh trạng thái

Danh mục « Tệp » cho phép đóng mở thẻ cũng như thoát chương trình

Danh mục « Duyệt trang » chứa các công cụ tương tự như thanh công cụ (khá dễ dàng nhờ lớp QAction)

Danh mục « Trợ giúp » cho phép hiển thị 1 vài thông tin chung về chương trình cũng như dự án.

Nghe có vẻ như không nhiều nhưng thật sự thì lượng công việc cũng khá lớn rồi. Nếu các bạn cảm thấy khó khăn thì trước hết chưa cần để ý vội đến tính năng quản lý nhiều thẻ. Tôi thì cho rằng có 1 vài thách thức nho nhỏ như vậy để cố gắng thì cũng tốt.

Các tệp của dự án

Tôi có thói quen tạo lớp cho mỗi loại cửa sổ và bởi vì chương trình của chúng ta chỉ có 1 cửa sổ duy nhất, ít nhất là trong giai đoạn đầu, nên chúng ta sẽ chỉ có những tệp mã sau đây :

  • main.cpp
  • CuaSoChinh.h
  • CuaSoChinh.cpp

Về phần các biểu tượng sử dụng trong chương trình, chúng ta có thể tìm thấy trên mạng vô số bộ biểu tượng đẹp và lạ mắt và nhất là miễn phí. Mỗi người có sở thích của riêng mình. Tôi tin rằng ai cũng sẽ có thể tìm được 1 bộ biểu tượng ưng ý.

Sử dụng QWebView để hiển thị trang web

QWebView là 1 trong các widget quan trọng mà chúng ta sẽ sử dụng trong bài thực hành này. Nó cho phép hiển thị các trang web. Đây chính là động cơ web của chúng ta.

Có thể các bạn không biết sử dụng, thậm chí chưa từng nghe nói đến tên lớp này, thế nhưng tôi tin tưởng các bạn lại là chuyên gia tra cứu và sử dụng tài liệu Qt. Các bạn sẽ thấy, lớp này cũng không quá phức tạp đâu.

Hãy quan sát kỹ các tín hiệu và các slot mà QWebView cung cấp cho chúng ta, hầu như tất cả những gì chúng ta cần đều có đủ. Giả dụ như tín hiệu loadProgress(int) sẽ rất hữu dụng khi chúng ta muốn quan sát tiến trình thực hiện tải trang.

Để tạo ra widget và tải trang với QWebView rất đơn giản.

QWebView *trangWeb = new QWebView();
trangWeb->load(QUrl("http://laptrinhtanbinh.com/"));

Đấy là tất cả những gì tôi có thể nói về lớp này. Nếu cần thêm thông tin thì tài liệu Qt đã nằm ngay trên bàn các bạn rồi.

Quản lý nhiều thẻ duyệt trang

Vấn đề của QWebView đó là mỗi lần chỉ cho phép hiển thị 1 trang duy nhất nên về cơ chế quản lý theo thẻ, chúng ta sẽ phải tự thực hiện.

Các bạn còn nhớ QTabWidget chứ? Đây là 1 widget dạng chứa có thể chứa trong nó bất cứ widget nào nên đương nhiên là tính cả QWebView vào đó. Khi kết hợp tốt QTabWidgetQWebView thì chúng ta đã có vốn để tạo ra được 1 trình duyệt hoàn chỉnh quản lý duyệt trang theo thẻ.

Chút nhắc nhở về việc tìm cách lấy ra widget nội dung được chứa trong các widget chứa.

Như các bạn biết, QTabWidget sử dụng các widget con thuộc kiểu QWidget cơ bản để chứa các widget nội dung. Trong ví dụ của chúng ta, QTabWidget chứa các QWidget rồi các QWidget này bản thân chúng mới chứa QWebView là nội dung trang web của chúng ta.

Phương thức findChild() của QObject cho phép chúng ta lấy ra widget con được chứa trong 1 widget khác.

Ví dụ như nếu tôi có đối tượng QWidget tên là trangThe thì tôi có thể tìm được đối tượng QWebView chứa trong nó bằng câu lệnh sau.

QWebView *trangWeb = trangThe->findChild<QWebView *>();

Thậm chí tôi có thể cho các bạn đoạn mã hoàn chỉnh của phương thức cho phép trả về đối tượng QWebView đang được quan sát bởi người dùng.

QWebView *CuaSoChinh::trangHienTai() {
    return danhSachThe->currentWidget()->findChild<QWebView *>();
}

danhSachThe là 1 đối tượng kiểu QTabWidget. Phương thức currentWidget() cho phép trả về thẻ (kiểu QWidget) đang được sử dụng bởi người dùng.

Tiếp đó, chúng ta cần lấy ra đối tượng QWebView được chứa bên trong QWidget nhờ phương thức findChild(). Phương thức này làm việc với các khuôn mẫu C++ (template), trong trường hợp này là <QWebView *> để xác định là kết quả mà nó cần trả về thuộc kiểu dữ liệu nào. Về phần khái niệm mới mẻ “khuôn mẫu”, chúng ta sẽ có cơ hội tìm hiểu trong 1 bài học tới.

Lên đường !

Vậy là khá đủ để các bạn có thể hoàn thành xuất sắc bài thực hành này rồi.

Chú ý là bài thực hành này liên quan khá nhiều đến những kiến thức chúng ta từng thảo luận về QMainWindow, vậy nên đừng ngại quay lại đọc bài học để hiểu rõ cách thức hoạt động của lớp này.

Do chúng ta cần thực hiện khá nhiều thao tác khởi tạo các đối tượng trong phương thức khởi tạo, lời khuyên của tôi là hãy tách chúng ra thành nhiều phần khác nhau, đóng gói trong các phương thức, giúp cho việc đọc lại và kiểm tra mã trở nên dễ dàng hơn.

CuaSoChinh::CuaSoChinh() {
    khoiTaoThaoTac();
    khoiTaoDanhMuc();
    khoiTaoThanhCongCu();
    /* Cac thao tac khoi tao khac */
}

Chúc may mắn !

Mã nguồn cửa sổ chính

Ở đây, tôi sẽ không đề cập đến main.cpp vì mã nguồn trong nó không có gì đặc biệt. Hãy tập trung vào các tệp mã nguồn của cửa sổ chính.

CuaSoChinh.h (phiên bản sơ khai)

Chúng ta sẽ bắt đầu với bộ khung đơn giản của lớp này. Trong quá trình viết, chúng ta sẽ thêm dần các phương thức và thuộc tính nếu cần thiết.

#ifndef CUASOCHINH_H
#define CUASOCHINH_H

#include <QtWidgets>
#include <QtWebKitWidgets>

class CuaSoChinh : public QMainWindow {
    Q_OBJECT
    public:
        CuaSoChinh();

    private:
        void khoiTaoThaoTac();
        void khoiTaoDanhMuc();
        void khoiTaoThanhCongCu();
        void khoiTaoThanhTrangThai();
 
    private slots:

    private:
        QTabWidget *danhSachThe;
        QAction *thaoTacThemThe;
        QAction *thaoTacDongThe;
        QAction *thaoTacThoat;
        QAction *thaoTacTroGiup;
        QAction *thaotacTaiTrangTruoc;
        QAction *thaoTactaiTrangSau;
        QAction *thaoTacDungTaiTrang;
        QAction *thaoTacLamMoiTrang;
        QAction *thaoTacTaiTrangChu;
        QAction *thaoTacTaiTrang;

        QLineEdit *diaChi;
        QProgressBar *tienTrinh;
};

#endif

Lớp này kế thừa từ lớp mẹ QMainWindow. Chúng ta sử dụng thêm vào QtWidgetsQtWebWidgets để có thể sử dụng các thành phần đồ họa người dùng cũng như là động cơ web.

Ý tưởng ở đây, như đã nói ở trên, là chúng ta sẽ chia nhỏ các thao tác khởi tạo thành các phương thức nhỏ hơn, phụ trách tạo ra các bộ phận khác nhau của cửa sổ chình như danh mục, thanh công cụ, thanh trạng thái, vv…

Chúng ta cũng để dành chỗ cho các slot và tín hiệu nhưng hiện thời thì vẫn để trống, sẽ thêm dần dần sau.

Về phần các thuộc tính của lớp, đa phần là các thao tác chính mà chúng ta muốn thực hiện trong trình duyệt. Chúng ta thậm chí không cần thuộc tính kiểu QWebView do chúng có thể được tạo ra dễ dàng theo dòng thực thi của chương trình và có thể dễ dàng được lấy ra nhờ phương thức trangHienTai() mà tôi đã giới thiệu với các bạn lúc trước.

Tiếp theo, hãy cùng nghiên cứu mã xử lý của phương thức khởi tạo cửa sổ.

Xây dựng cửa sổ

Trực tiếp bên trong tệp CuaSoChinh.cpp, chúng ta có phương thức khởi tạo.

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh() {
    // Tao cac widget cua cua so
    khoiTaoThaoTac();
    khoiTaoDanhMuc();
    khoiTaoThanhCongCu();
    khoiTaoThanhTrangThai();

    // Tao danh sach the va tai trang chu
    danhSachThe = new QTabWidget();
    danhSachThe->addTab(taoTheMoi(tr("http://laptrinhtanbinh.com")), tr("(Thẻ mới)"));
    connect(danhSachThe, SIGNAL(currentChanged(int)), this, SLOT(chuyenThe(int)));
    setCentralWidget(danhSachThe);

    // Tuy chinh cac chi tiet cua cua so
    setMinimumSize(500, 350);
    setWindowIcon(QIcon("images/bando.png"));
    setWindowTitle(tr("Bản đồ X"));
}

Các phương thức khoiTaoThaoTac(), khoiTaoDanhMuc(), khoiTaoThanhCongCu()khoiTaoThanhTrangThai() sẽ không được đề cập ở đây do chúng khá dài và không có quá nhiều khác biệt so với các chương trình khác. Chúng đơn giản là tạo ra các widget và đặt chúng vào vị trí trong cửa sổ. Tuy nhiên, các bạn luôn có thể tìm được phiên bản đầy đủ của chúng trong tệp mã nguồn đính kèm ở cuối bài này.

Trái lại, phần thú vị trong phương thức khởi tạo này là đoạn mã tạo ra đối tượng QTabWidget và thêm vào thẻ đầu tiên cho nó. Để tạo ra thẻ, chúng ta sử dụng phương thức tên là taoTheMoi() có trách nhiệm tạo ra 1 thẻ trang web mới, đồng thời tạo ra khung hiển thị trang QWebView cho vào trong thẻ. Trong khung hiển thị, trang chủ http://laptrinhtanbinh.com sẽ vô tình được tự động tải mặc định. :D

Các bạn chú ý rằng ở đây chúng ta sử dụng phương thức tr() cho tất cả các đoạn văn bản hiển thị. Điều này cho phép trình duyệt hỗ trợ tính năng hiển thị đa ngôn ngữ nếu muốn. Tính năng này không khiến chương trình trở nên phức tạp hơn. Vậy thì chẳng có lý do gì để bỏ qua nó cả.

Cuối cùng, chúng ta kết nối tín hiệu currentChanged() của QTabWidget với 1 slot cá nhân là chuyenThe(). Đây là slot chúng ta sẽ tự viết, được kích hoạt mỗi khi người dùng chuyển thẻ, có vai trò thực hiện 1 số thay đổi sang địa chỉ URL tương ứng của thẻ, đổi tên cửa sổ cho tương ứng với trang đang xem, vv…

Phần tiếp theo là 1 vài phương thức quản lý danh sách thẻ.

Phương thức quản lý danh sách thẻ

Chúng ta chỉ có 2 phương thức kiểu này :

  • taoTheMoi() : từng được nhắc đến trong phương thức khởi tạo, dùng để tạo thẻ trang web mới.
  • trangHienTai() : phương thức hữu dụng để lấy ra đối tượng QWebView đang được xem bới người dùng.
QWidget* CuaSoChinh::taoTheMoi(QString url) {
    QWidget *trangThe = new QWidget();
    QWebView *khungTrangWeb = new QWebView();

    QVBoxLayout *lop = new QVBoxLayout();
    lop->setContentsMargins(0,0,0,0);
    lop->addWidget(khungTrangWeb);
    trangThe ->setLayout(lop);

    if (url.isEmpty()) {
        khungTrangWeb->load(QUrl(tr("html/trangTrang.html")));
    } else {
        if (url.left(7) != "http://") {
            url = "http://" + url;
        }
        khungTrangWeb->load(QUrl(url));
    }

    // Quan ly tin hieu gui boi trang the
    connect(khungTrangWeb, SIGNAL(titleChanged(QString)), this, SLOT(thayTieuDe(QString)));
    connect(khungTrangWeb, SIGNAL(urlChanged(QUrl)), this, SLOT(thayDiaChi(QUrl)));
    connect(khungTrangWeb, SIGNAL(loadStarted()), this, SLOT(batDauTienTrinh()));
    connect(khungTrangWeb, SIGNAL(loadProgress(int)), this, SLOT(taiTienTrinh(int)));
    connect(khungTrangWeb, SIGNAL(loadFinished(bool)), this, SLOT(ketThucTienTrinh(bool)));

    return trangThe;
}

QWebView* CuaSoChinh::trangHienTai() {
    return danhSachThe->currentWidget()->findChild<QWebView *>();
}

Không cần nói thêm nhiều về trangHienTai() do chúng ta đã giải thích về nó từ bên trên.

Về phần taoTheMoi(), phương thức này thực hiện đúng như những gì chúng ta định làm : tạo thẻ mới, thêm khung hiển thị trang web, tải trang dựa vào địa chỉ được cung cấp, vv…

1 xử lý thêm được áp dụng lên địa chỉ nhập vào là tự động thêm http://.

Nếu không có địa chỉ nào được nhập vào thì chương trình tự động tải 1 trang HTML trắng nằm trong thư mục html của dự án.

Chúng ta cũng kết nối nhiều tín hiệu với các slot khác nhau. Những cái tên nói lên tất cả, không cần thiết phải giải thích nhiều.

Không có khó khăn gì trong phần này. Chỉ có điều là chúng ta còn cần viết mã cho khá nhiều slot cá nhân.

Các slot tùy chỉnh cá nhân

Phần công việc còn lại, đấy là hoàn tất mã nguồn tệp CuaSoChinh.cpp với các slot mà chúng ta định sử dụng : trangTruoc(), trangSau(), vv… Phần này chắc không thể làm khó được ai trong các bạn. Chúng ta sẽ quan tâm đến phần thú vị hơn, đó là mã xử lý trong tệp .cpp của các slot này.

Mã xử lý của các slot

Slot kích hoạt bởi thao tác trên thanh công cụ

Hãy bắt đầu với các slot tương ứng với các thao tác trên thanh công cụ.

void CuaSoChinh::taiTrangTruoc() {
    trangHienTai()->back();
}

void CuaSoChinh::taiTrangSau() {
    trangHienTai()->forward();
}

void CuaSoChinh::taiTrangChu() {
    trangHienTai()->load(QUrl(tr("http://laptrinhtanbinh.com")));
}

void CuaSoChinh::dungTai() {
    trangHienTai()->stop();
}

void CuaSoChinh::taiLai() {
    trangHienTai()->reload();
}

Chúng ta sử dụng phương thức vô cùng tiện lợi là trangHienTai() để lấy ra con trỏ trỏ về khung hiển thị trang kiểu QWebView mà người dùng đang xem (chú ý là lịch sử duyệt trang chỉ có ý nghĩa trong phạm vi thẻ đang xem mà không liên quan gì đến các thẻ khác).

Tất cả các phương thức như back(), forward(), vv… chính là các slot. Chúng ta sử dụng chúng như những phương thức thường trong trường hợp này. Điều này không có gì là bất hợp lý do các slot về cơ bản cũng chỉ là các phương thức được kết nối với 1 tín hiệu.

? Tại sao không liên kết trực tiếp các tín hiệu gửi đi bởi QAction với các slot của QWebView ?

Chúng ta có thể làm vậy nếu không tính đến chuyện sử dụng tính năng nhiều thẻ bởi vấn đề nằm chính ở số lượng thẻ chúng ta cần quản lý.

Ví dụ chúng ta không thể kết nối đối tượng QAction « lamMoiTrang » với đối tượng QWebView do đối tượng QWebView cần kết nối hoàn toàn phụ thuộc vào thẻ hiện tại đang được sử dụng. Đấy chính là lý do chúng ta liên kết thông qua 1 slot tự chế để chắc chắn là sẽ kết nối đến đúng đối tượng.

Slot kích hoạt nhờ các thao tác khác trong danh mục

Dưới đây là các slot được kích hoạt nhờ các thao tác trong danh mục.

  • Thêm thẻ
  • Đóng thẻ
  • Mở mục trợ giúp
  • vv…
void CuaSoChinh::troGiup() {
    QMessageBox::information(this, tr("Trợ giúp"), tr("BanDoX được thực hiện trong dự án minh họa của lớp học ngôn ngữ C++ của <a href=\"http://laptrinhtanbinh.com\">Lập trình Tân Binh</a>.<br />Mọi câu hỏi thắc mắc có thể được đưa ra trong phần thảo luận cuối bài học."));
}

void CuaSoChinh::themThe() {
    int chiSoTheMoi = danhSachThe->addTab(taoTheMoi(), tr("Thẻ mới"));
    danhSachThe->setCurrentIndex(chiSoTheMoi);
    diaChi->setText("");
    diaChi->setFocus(Qt::OtherFocusReason);
}

void CuaSoChinh::dongThe() {
    // Khong the dong neu chi co 1 the duy nhat
    if (danhSachThe->count() > 1) {
        danhSachThe->removeTab(danhSachThe->currentIndex());
    } else {
        QMessageBox::critical(this, tr("Lỗi"), tr("Cần có it nhất 1 thẻ trong trình duyệt!"));
    }
}

Slot troGiup() đơn giản là hiển thị 1 hộp thoại thông báo.

themThe() tạo ra thêm thẻ mới trong cửa sổ nhờ sử dụng phương thức addTab() của QTabWidget, giống như chúng ta từng làm trong phần khởi tạo. Khi thẻ mới được tạo ra, chúng ta bắt buộc hiển thị thẻ này nhờ phương thức setCurrentIndex(), trong đó chúng ta sử dụng tham số là chỉ số của thẻ chúng ta vừa tạo ra.

Chúng ta xóa vùng nhập địa chỉ và đặt con trỏ vào đó để người dùng có thể ngay lập tức bắt đầu nhập địa chỉ trang.

! Thao tác « Thêm thẻ » thường có tổ hợp phím tắt là Ctrl + T, cho phép người dùng có thể tạo thẻ bất cứ lúc nào nhờ bàn phím. Chúng ta cũng có thể thêm vào 1 nút bấm trong thanh công cụ cho phép thực hiện nhiệm vụ này hoặc thậm chí thêm 1 nút nhỏ vào vị trí góc của QTabWidget như vẫn thường thấy ở Firefox hay Chrome. Để làm điều trên, các bạn có thể tham khảo phương thức setCornerWidget().

dongThe() có nhiệm vụ đóng thẻ được chọn. Nó thực hiện kiểm tra xem thẻ cần đóng có phải là thẻ duy nhất trong cửa sổ không vì không thể tồn tại đối tượng QTabWidget mà không chứa thẻ nào.

Các slot tải trang và chuyển thẻ

Các slot này được kích hoạt khi chúng ta yêu cầu tải 1 trang (gõ địa chỉ và ấn Enter hoặc ấn nút tải trang trên thanh công cụ) và khi chúng ta chuyển thẻ sử dụng.

void CuaSoChinh::taiTrang() {
    QString url = diaChi->text();
    // Them "http://" neu dia chi chua hoan chinh
    if (url.left(7) != "http://") {
        url = "http://" + url;
        diaChi->setText(url);
    }
    trangHienTai()->load(QUrl(url));
}

void CuaSoChinh::chuyenThe(int chiSo) {
    thayTieuDe(trangHienTai()->title());
    thayDiaChi(trangHienTai()->url());
}

Chúng ta kiểm tra xem địa chỉ có hoàn chỉnh không, nếu không thì thêm http:// để tạo ra địa chỉ hợp lệ.

Khi người dùng chuyển thẻ, chúng ta sẽ cập nhật giá trị mới của 2 thành phần của cửa sổ : tiêu đề trang ở góc trên bên trái và địa chỉ trong vùng nhập địa chỉ. thayTieuDe()thayDiaChi() là các slot cá nhân mà chúng ta có thể sử dụng như những phương thức thường. Các slot này cũng có thể tự động được kích hoạt khi nhận các tín hiệu của QWebView.

Hãy xem mã xử lý của các slot này.

Slot kích hoạt bởi tín hiệu cua QWebView

1 đối tượng QWebView khi được sử dụng sẽ gửi đi các tín hiệu. Chúng được nối với các slot cá nhân mà chúng ta sẽ viết dưới đây cho cửa sổ.

void CuaSoChinh::thayTieuDe(const QString& tieuDe) {
    QString tieuDeRutGon = tieuDe;

    // Cat bot tieu de de tranh tao re cac the qua rong
    if (tieuDe.size() > 40) {
        tieuDeRutGon = tieuDe.left(40) + "...";
    }
    setWindowTitle(tieuDeRutGon + " - " + tr("BanDoX"));
    danhSachThe->setTabText(danhSachThe ->currentIndex(), tieuDeRutGon);
}

void CuaSoChinh::thayDiaChi(const QUrl& url) {
    if (url.toString() != tr("html/trangTrang.html")) {
        diaChi->setText(url.toString());
    }
}

void CuaSoChinh::batDauTienTrinh() {
    tienTrinh->setVisible(true);
}

void CuaSoChinh::taiTienTrinh(int tienDo) {
    tienTrinh->setValue(tienDo);
}

void CuaSoChinh::ketThucTienTrinh(bool ok) {
    tienTrinh->setVisible(false);
    statusBar()->showMessage(tr("Hoàn tất"), 2000);
}

Các slot trên không quá phức tạp, chúng cập nhật giá trị của các thành phần cửa sổ khi cần thiết.

1 số trong số chúng khá hữu dụng, ví dụ như thayDiaChi(). Trong thực tế, khi người dùng ấn vào đường dẫn trong trang, chúng ta sẽ cần tải trang mới và thay đổi giá trị của địa chỉ hiện tại.

Kết luận và cải tiển chương trình
Tải mã nguồn và tệp thực thi

Dưới đây là đường dẫn để tải mã nguồn cũng như tệp thực thi.

Mã nguồn chương trình

Đừng quên thêm các tệp thư viện DLL cần thiết vào cùng thư mục với tệp thực thi để tệp này có thể hoạt động được.

Cải tiến chương trình

Trình duyệt của chúng ta có thể cải tiến nữa không ?

Chắc chắn là có chứ. Sản phẩm của chúng ta vẫn còn khoảng cách xa với tiêu chuẩn trình duyệt hoàn hảo. Tôi có 1 vài ý tưởng có thể dùng để cải tiến nó. Những ý tưởng này dựa trên đặc điểm của các trình duyệt vốn có. Thế nhưng nếu các bạn có những ý tưởng sáng tạo dù hơi điên rồ thì cũng đừng ngại thử nghiệm chúng. Đấy là cách nhanh nhất khiến các sản phẩm tin học phát triển.

  • Hiển thị lịch sử duyệt web trong 1 danh sách: chúng ta có lớp QWebHistory cho phép quản lý toàn bộ lịch sử duyệt web của 1 đối tượng QWebView. Nếu cần, các bạn hãy tham khảo tài liệu của lớp này để xem làm sao có thể trích xuất được danh sách các trang web đã được xem.
  • Tìm kiếm trong trang: cho phép tìm kiếm văn bản trong nội dung trang web. Chú ý, QWebView có phương thức findText() có thể sẽ hữu ích.
  • Tùy chỉnh cửa sổ: chúng ta có thể tạo ra cửa sổ chứa những tùy chỉnh có thể áp dụng cho trình duyệt như thay đổi kích thước, cỡ chữ mặc định, địa chỉ trang chủ, vv… Để thực hiện, hãy tham khảo về QWebSettings. Chúng ta có thể lưu các tùy chọn vào tệp. Tuy nhiên, lựa chọn tốt hơn là sử dụng lớp QSettings vốn được thiết kế cho công việc này. Về cơ bản, các tùy chọn thường được lưu trong các tệp .ini hay .conf, vv… nhưng cũng có thể lưu trong các bản ghi của Windows.
  • Quản lý trang yêu thích: 1 tính năng mà khá nhiều trình duyệt hiện nay hỗ trợ. Người dùng có thể lưu lại địa chỉ của các trang web hữu dụng hoặc được yêu thích. Chúng ta cũng có thể tận dụng QSettings cho tính năng này.