Daily Archives: 25/05/2016

Qt 5 C++ – QtWebEngine

Trong Qt có module QtWebEngine cung cấp các lớp C++ hỗ trợ dựng trang web từ mã HTML, XML, SVG, CSS, Javascript lên một chương trình Qt, dùng cho các hệ điều hành không có sẵn web engine. Đối với các hệ điều hành có sẵn web engine thì chúng ta có thể dùng module QtWebKit, tuy nhiên module này đã bị loại bỏ khỏi Qt 5.5, nên hầu như chúng ta sẽ dùng module QtWebEngine để làm việc với web.

Tổng quan về Qt WebEngine

Kiến trúc:

qtwebengine-architecture

Theo sơ đồ trên thì module QtWebEngine bao gồm những thành phần sau:

  • Qt WebEngine Widgets: cung cấp các thư viện tạo giao diện người dùng với Widget.
  • Qt WebEngine: cung cấp các lớp tạo giao diện người dùng với QtQuick.
  • Qt WebEngine Core: làm việc với Chromium.
  • Qt WebEngine Process: dựng trang, xử lý Javascript.

Module Qt WebEngine Core được xây dựng dựa trên Chromium. Các thành phần của Chromium chịu trách nhiệm trong việc xử lý kết nối mạng, xây dựng trang web, xử lý Javascript, HTML… Ngoài WebEngine thì Qt còn có module WebKit cũng có chức năng thao tác với trang web nhưng rất hạn chế, WebEngine nặng hơn, cung cấp nhiều chức năng thao tác trực tiếp với web hơn WebKit. Dù QtWebEngine được xây dựng trên Chromium nhưng không có nghĩa là module này có các dịch vụ hỗ trợ thêm như của trình duyệt Chrome do Google phát triển.

Cài đặt

Mặc định module QtWebEngine không có sẵn trong Qt Creator sử dụng trình biên dịch MinGW, bạn phải chuyển qua sử dụng Qt Creator dùng trình biên dịch MSVC hoặc sử dụng Add-In trong Visual Studio.

Capture Capture1

Hoặc có một cách khác là biên dịch lại toàn bộ mã nguồn Qt và Chromium sử dụng trình biên dịch MinGW, cách này rất phức tạp nên mình sẽ không đề cập ở đây.

Ngoài ra Visual Studio Add-In cũng không chấp nhận Visual Studio phiên bản Express (hoặc Community) nên cách này cũng khó khả thi (trừ khi bạn dùng crack). Nên tóm lại trong phần này mình sẽ sử dụng Qt Creator với trình biên dịch msvc từ Visual Studio 2015.

Lưu ý: tại thời điểm viết bài này trình biên dịch msvc đi kèm với Qt Creator khi download trên trang chủ không sử dụng được, mình phải cài Visual Studio 2015 Community rồi chỉ định Qt Creator sử dụng trình biên dịch của VS mới chạy được (khi cài Visual Studio 2015 bạn phải tích chọn cài 2 thành phần là Programming Languages→Visual C++Windows and Web Development→Universal Windows App Developments Tools vì đây là 2 thành phần cần thiết để biên dịch trong Qt nhưng không được cài mặc định).

Bây giờ chúng ta sẽ đi tìm hiểu một số chức năng có trong Qt WebEngine.

Mở trang web

Để mở một trang web và hiển thị thì chúng ta dùng lớp QWebEngineView. Chúng ta tạo một project mới.

...
QT += webenginewidgets
...

Trong file .pro chúng ta phải khai báo module QtWebEngine bằng dòng code trên.

#include <QMainWindow>

#include <QWebEngineView>

class Browser : public QMainWindow
{
    Q_OBJECT
public:
    explicit Browser(QWidget *parent = 0);

private:
    QWebEngineView *view;
};

Chúng ta định nghĩa lớp Browser kế thừa từ lớp QMainWindow, bên trong lớp này có một đối tượng QWebEngineView.

#include "browser.h"

Browser::Browser(QWidget *parent) : QMainWindow(parent)
{
    view = new QWebEngineView(this);
    view->load(QUrl("https://phocode.com"));
    setCentralWidget(view);
}

Để tải một trang web về thì chúng ta chỉ cần gọi phương thức load() và truyền vào một đối tượng QUrl. Phương thức setCentralWidget() sẽ thiết lập đối tượng QWebEngineView làm giao diện chính của cửa sổ.

#include "browser.h"
#include <QApplication>
#include <qtwebenginewidgetsglobal.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Browser browser;
    browser.show();

    return a.exec();
}

Trong file main.cpp chúng ta chỉ tạo đối tượng Browser rồi gọi phương thức show() như bình thường.

Capture

Một số tính năng của QWebEngineView

Lớp QWebEngineView có sẵn một số tính năng cơ bản của một trình duyệt web. Chúng ta sẽ tìm hiểu qua ví dụ dưới đây:

#include <QMainWindow>
#include <QWebEngineView>
#include <QLineEdit>
#include <QUrl>
#include <QToolBar>

class Browser : public QMainWindow
{
    Q_OBJECT
public:
    explicit Browser(QWidget *parent = 0);

signals:

public slots:
    void adjustLocation();
    void changeLocation();
    void adjustTitle();
    void setProgress(int p);
private:
    QWebEngineView *view;
    QLineEdit   *locationEdit;
    int progress;
};

Chúng ta định nghĩa lại lớp Browser. Lớp QWebEngineView có một số Signal đặc biệt được phát đi trong quá trình duyệt trang web nên chúng ta có thể định nghĩa một số phương thức Slot và thuộc tính để bắt và xử lý các signal đó.

#include "browser.h"

Browser::Browser(QWidget *parent) : QMainWindow(parent)
{
    view = new QWebEngineView(this);
    view->load(QUrl("http://google.com"));
    connect(view, SIGNAL(loadFinished(bool)), this, SLOT(adjustLocation()));
    connect(view, SIGNAL(titleChanged(QString)), this, SLOT(adjustTitle()));
    connect(view, SIGNAL(loadProgress(int)), this, SLOT(setProgress(int)));

    locationEdit = new QLineEdit(this);
    locationEdit->setSizePolicy(QSizePolicy::Expanding, locationEdit->sizePolicy().verticalPolicy());
    connect(locationEdit, SIGNAL(returnPressed()), SLOT(changeLocation()));

    QToolBar *toolBar = addToolBar(tr("Navigation"));
    toolBar->addAction(view->pageAction(QWebEnginePage::Back));
    toolBar->addAction(view->pageAction(QWebEnginePage::Forward));
    toolBar->addAction(view->pageAction(QWebEnginePage::Reload));
    toolBar->addAction(view->pageAction(QWebEnginePage::Stop));
    toolBar->addWidget(locationEdit);

    setCentralWidget(view);
}

void Browser::adjustLocation()
{    
    locationEdit->setText(view->url().toString());
}

void Browser::changeLocation()
{
    QUrl url = QUrl::fromUserInput(locationEdit->text());
    view->load(url);
    view->setFocus();
}

void Browser::adjustTitle()
{
    if(progress <= 0 || progress >= 100)
        setWindowTitle(view->title());
    else
        setWindowTitle(QString("%1 (%2%)").arg(view->title()).arg(progress));
}

void Browser::setProgress(int p)
{
    progress = p;
    adjustTitle();
}

Trong file browser.cpp chúng ta xây dựng lớp Browser thành một trình duyệt web đơn giản.

connect(view, SIGNAL(loadFinished(bool)), this, SLOT(adjustLocation()));
connect(view, SIGNAL(titleChanged(QString)), this, SLOT(adjustTitle()));
connect(view, SIGNAL(loadProgress(int)), this, SLOT(setProgress(int)));

Tín hiệu loadFinished() được phát đi khi QWebEngineView đã tải xong trang web. Chúng ta xử lý bằng phương thức adjustLocation(). 

Tín hiệu titleChanged() được phát đi khi tiêu đề của trang web thay đổi, tức là gần như mỗi lần loadFinished() được phát đi thì titleChanged() cũng được phát đi cùng luôn.

Tín hiệu loadProgress() được phát đi khi mỗi phần tử của trang web được tải xong, ví dụ một trang web mang theo rất nhiều file ảnh, file javascript, file css… mỗi lần những file này được tải xong thì tín hiệu này được phát đi, tín hiệu này mang theo một con số từ 0 đến 100 có nghĩa là trang web đang được tải bao nhiêu %.

locationEdit = new QLineEdit(this);
locationEdit->setSizePolicy(QSizePolicy::Expanding, locationEdit->sizePolicy().verticalPolicy());
connect(locationEdit, SIGNAL(returnPressed()), SLOT(changeLocation()));

Ở đây chúng ta định nghĩa một đối tượng QLineEdit dùng làm thanh địa chỉ của trình duyệt web. Phương thức setSizePolicy() sẽ quy định kích thước của QLineEdit. Tín hiệu returnPressed() được phát đi khi chúng ta gõ phím Enter trên QLineEdit, chúng ta bắt tín hiệu này bằng slot changeLocation().

QToolBar *toolBar = addToolBar(tr("Navigation"));
toolBar->addAction(view->pageAction(QWebEnginePage::Back));
toolBar->addAction(view->pageAction(QWebEnginePage::Forward));
toolBar->addAction(view->pageAction(QWebEnginePage::Reload));
toolBar->addAction(view->pageAction(QWebEnginePage::Stop));
toolBar->addWidget(locationEdit);

Ở đây chúng ta tạo thêm một thanh toolbar, ngoài việc dùng để chứa thanh địa chỉ ở trên, chúng ta còn dùng thêm các đối tượng Action có sẵn của lớp QWebEnginePage, lớp này có thuộc tính QWebEnginePage::WebAction cung cấp một số đối tượng Action có các chức năng cơ bản của một trình duyệt web và thậm chí là có cả icon sẵn. Ví dụ ở trên Back là nút quay lại trang vừa xem, Forward là nút đi tới trang vừa mở, Reload là nút refresh trình duyệt, Stop là nút dừng tải trang.

void Browser::adjustLocation()
{    
    locationEdit->setText(view->url().toString());
}

Phương thức adjustLocation() chỉ đơn giản là thiết lập lại đoạn text trong thanh địa chỉ.

void Browser::changeLocation()
{
    QUrl url = QUrl::fromUserInput(locationEdit->text());
    view->load(url);
    view->setFocus();
}

Phương thức changeLocation() sẽ được gọi khi chúng ta gõ phím Enter trên thanh địa chỉ, lúc này chúng ta tiến hành tải lại trang web.

void Browser::adjustTitle()
{
    if(progress <= 0 || progress >= 100)
        setWindowTitle(view->title());
    else
        setWindowTitle(QString("%1 (%2%)").arg(view->title()).arg(progress));
}

Phương thức adjustTitle() sẽ được gọi khi trang web được tải xong, hoặc một phần của trang web được tải xong, vì ở đây chúng ta chèn thêm số % trang web đã được tải là bao nhiêu.

void Browser::setProgress(int p)
{
    progress = p;
    adjustTitle();
}

Trong lớp Browser chúng ta định nghĩa thuộc tính progress dùng để lưu số % trang web đã được tải. Phương thức setProgress() sẽ được gọi khi QWebEngineView phát tín hiệu loadProgress(), lúc này chúng ta thiết lập thông số mới cho thuộc tính progress, đồng thời gọi lại phương thức adjustTitle().

#include "browser.h"
#include <QApplication>
#include <qtwebenginewidgetsglobal.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Browser browser;
    browser.show();

    return a.exec();
}

Trong file main.cpp chúng ta chỉ tạo đối tượng Browser và gọi phương thức show().

Untitled

Android – Đồ họa

Android cũng hỗ trợ các thư viện cho phép vẽ các đối tượng đồ họa 2D lên màn hình.

Để vẽ thì chúng ta gọi các phương thức vẽ bên trong phương thức onDraw() của lớp android.view.View, mỗi đối tượng View có một đối tượng Canvas của riêng nó để vẽ các đối tượng hình học như hình tròn, hình vuông, điểm… hoặc chúng ta có thể định nghĩa “cứng” ngay trong một file XML.

Ví dụ 1

Chúng ta sẽ vẽ hình tròn trên một đối tượng View, ở đây hình tròn sẽ được định nghĩa bên trong một file XML.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="oval">
    
    <solid 
        android:color="#666666"/>
    <size 
        android:width="70dp"
        android:height="70dp"/>
</shape> 

Chúng ta tạo một file có tên circle.xml và bên trong một thư mục drawable nào đó, chẳng hạn như thư mục drawable-hdpi. Trong file này chúng ta khai báo một thẻ shape, thẻ này sẽ định nghĩa một đối tượng hình học nào đó như hình tròn, hình vuông… chúng ta thiết lập đối tượng hình học đó trong thuộc tính android:shape, ở đây giá trị oval tức là vẽ hình tròn, ngoài ra còn 3 loại nữa là rectangle (hình chữ nhật), line (đường thẳng) và ring (hình vòng bánh xe). Thẻ solid quy định màu vẽ, thẻ size quy định kích thước của đối tượng hình học.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent">        

    <View 
        android:layout_width="100dp"
        android:layout_height="100dp" 
        android:layout_marginTop="5dp" 
        android:background="@drawable/oval"/>      
</LinearLayout>

Trong file main.xml. Chúng ta khai báo một thẻ View và định nghĩa thuộc tính android:background là tên file circle.xml, Android sẽ tự động tìm file này trong tất cả các thư mục drawable của nó.

package com.phocode;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Trong file Activity chúng ta không cần chỉnh sửa gì cả.

Screenshot_2016-05-25-08-34-58

Ví dụ 2

Trong ví dụ này chúng ta sẽ vẽ hình chữ nhật trên một lớp View. Công đoạn vẽ sẽ được thực hiện trong phương thức onDraw() của lớp View chứ không được định nghĩa trước trong file XML nữa.

package com.phocode;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;

public class Graphics extends View
{
    Paint p = new Paint();
 
    public Graphics(Context context)
    {
        super(context);
        p.setColor(Color.GREEN);
    }
 
    @Override
    public void onDraw(Canvas canvas)
    {
        canvas.drawRect(30, 30, 100, 100, p);
    }
}

Đầu tiên chúng ta định nghĩa lớp Graphics kế thừa từ lớp View. File Graphics.java nằm cùng thư mục với file Activity.

Paint p = new Paint();

Trong lớp này chúng ta định nghĩa một đối tượng Paint. Đối tượng này sẽ định nghĩa màu vẽ.

p.setColor(Color.GREEN);

Dòng code trên sẽ chuyển màu dùng để vẽ của đối tượng Paint sang màu xanh lá.

@Override
public void onDraw(Canvas canvas) 
{
    canvas.drawRect(30, 30, 100, 100, p);
}

Các phương thức vẽ được gọi trong phương thức onDraw(). Ở đây phương thức drawRect() sẽ vẽ một hình chữ vuông lên bề mặt của đối tượng View.

package com.phocode;

import android.app.Activity;
import android.os.Bundle;
import android.graphics.Color;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
 
        Graphics view = new Graphics(this);
        view.setBackgroundColor(Color.WHITE);
        setContentView(view);
    }
}

Trong file MainActivity.java, chúng ta tạo một đối tượng View riêng (bằng lớp Graphics) rồi truyền vào phương thức setContentView() chứ không phải là một file layout nào khác.

view.setBackgroundColor(Color.WHITE);

Phương thức setBackgroundColor() sẽ thiết lập màu nền cho đối tượng View.

Screenshot_2016-05-25-08-54-41