QML Book 第二章 概览

2.概览

本章将介绍如果开始使用 Qt 5 进行开发。将展示如何安装 Qt SDK,以及如何使用 Qt Creator IDE 创建以及运行一个简单的 hello world 应用程序。

提示
本章的源代码可以在这里下载。

2.1.安装 Qt 5 SDK

Qt软件工具包包含了编译桌面或者嵌入式应用程序的工具。最新的版本可以从Qt-Project 主页下载。我们将使用这种方法开始。软件工具包自身包含了一个维护工具允许你更新到最新版本的软件工具包。

Qt 软件工具包非常容易安装,并且附带了一个它自身的快速集成开发环境叫做 Qt Creator。这个集成开发环境可以让你高效的使用 Qt 进行开发,我们推荐给所有的读者使用。在任何况下 Qt 都可以通过命令的方式来编译,你可以自由的选择你的代码编辑器。

当我们开始安装 SDK 时,最好选择默认的选项确保 Qt 5.x 可以使用。然后一切准备就绪。

2.2.编写示例程序

为了测试我们的安装是否成功,我们可以创建一个简单的应用程序 hello world。打开 Qt Creator 并且创建一个 Qt Quick UI Project(File -> New File 或者 Project -> Qt Quick Project -> Qt Quick UI)并且给项目取名 HelloWorld。

提示:
Qt Creator 集成开发环境允许我们创建不同类型的应用程序。如果没有特别说明,我们都将默认创建 Qt Quick UI Project 项目。

提示:
典型的 Qt Quick 应用程序是由称为 QmlEngine 的运行时进行加载的,它加载初始的 QML 代码。 开发人员可以使用运行时注册的 C++ 类型以与本地代码进行接口交互。这些 C++ 类型也可以绑定到一个插件中,然后使用 import 语句来动态加载。qmlscene 和 qml 工具是预制的运行时库,可以在 QML 中直接使用。一开始我们不会介绍本地化的代码开发知识,我们将只关注 Qt 5 的 QML 方面的知识。

Qt Creator 将会为我们自动地创建几个文件。HellWorld.qmlproject 文件是项目文件,保存了项目的配置信息。这个文件由 Qt Creator 管理,我们不需要编辑它。

另一个文件 HelloWorld.qml 是我们的应用程序代码。打开它并尝试看看应用我们这个程序的作用,然后继续阅读。

// HelloWorld.qml

import QtQuick 2.0

Rectangle {
    width: 360
    height: 360
    Text {
        anchors.centerIn: parent
        text: "Hello World"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}

这个 HelloWord.qml 是用 QML 语言编写的。我们将在下一章中更深入地讨论 QML 语言。上面的代码的意思是在一系列分层元素中描述了用户界面:该代码显示了一个 360 x 360 像素的矩形,居中显示了内容为 “Hello World” 的文本元素,鼠标监听区域覆盖整个矩形区域,当用户点击它时,应用程序退出。

接下来,我们可以点击
运行

按钮或者从菜单选择 Bulid->Run 运行上面的代码。

Qt Creator 将启动 qmlscene,并将 QML 文档作为第一个参数传递。 qmlscene 将解析文档并启动用户界面。现在我们应该可以看到下面这样的应用界面:

应用界面

Qt 5 似乎在正常地工作,我们继续下面的内容吧。

建议
如果您是系统集成商,您将需要安装 Qt SDK 以获取最新的稳定的 Qt 版本以及根据特定设备目标的源代码编译的 Qt 版本。

从头开始构建
如果要从命令行构建 Qt 5,首先需要获取代码存储库的副本并构建它。

git clone git://gitorious.org/qt/qt5.git
cd qt5
./init-repository
./configure -prefix $PWD/qtbase -opensource
make -j4

在成功汇编和喝完2杯咖啡之后,Qt 5 将在 qtbase 文件夹中编译完成。其实喝任何饮料都行的,但是,我们建议喝咖啡以获得最佳效果。

如果要测试您的编译是否成功,只需启动 qtbase/bin/qmlscene 并选择一个 Qt Quick 示例来运行它......或随我们进入下一章。

为了测试我们的安装,我们可以创建一个小的 hello world 应用程序。可以使用我们喜欢的文本编辑器创建一个简单的 example.qml 文件,然后粘贴以下内容:

// HelloWorld.qml

import QtQuick 2.5

Rectangle {
    width: 360
    height: 360
    Text {
        anchors.centerIn: parent
        text: "Greetings from Qt 5"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}

我们使用如下命令并通过 Qt 5 附带的默认运行环境运行示例:

qtbase/bin/qmlscene

2.3.应用程序类型

本节将介绍一些可能使用 Qt 5 编写的不同应用程序类型。它不仅限于所提供的这些内容,但这些应该可以让读者更好地了解 Qt 5 的一般功能。

2.3.1.控制台应用程序

控制台应用程序不提供任何图形用户界面,通常将其作为系统服务或通过命令行进行调用。Qt 5 附带了一系列现成的组件,可帮助我们非常有效地创建控制台跨平台应用程序。例如网络文件API、字符串处理、以及自 Qt 5.1 起提供的高效命令行解析器。由于 Qt 是基于 C++ 的高级应用程序接口,我们能够快速的编程并且程序拥有快速的执行速度。不要把 Qt 仅仅当作是一个 UI 工具包——它其实能帮助我们做更多的事。

字符串处理

在第一个例子中我们展示了怎样简单的增加两个字符串常量。这不是一个有用的应用程序,但能让我们了解没有事件循环的本地端 C++ 应用程序的大概模样。

// module or class includes
#include <QtCore>

// text stream is text-codec aware
QTextStream cout(stdout, QIODevice::WriteOnly);

int main(int argc, char** argv)
{
    // avoid compiler warnings
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    QString s1("Paris");
    QString s2("London");
    // string concatenation
    QString s = s1 + " " + s2 + "!";
    cout << s << endl;
}
容器类

这个例子在应用程序中增加了一个链表和一个链表迭代器。Qt 自带大量方便使用的容器类,并且其中的元素使用相同的应用程序接口模式。

QString s1("Hello");
QString s2("Qt");
QList<QString> list;
// stream into containers
list <<  s1 << s2;
// Java and STL like iterators
QListIterator<QString> iter(list);
while(iter.hasNext()) {
    cout << iter.next();
    if(iter.hasNext()) {
        cout << " ";
    }
}
cout << "!" << endl;

下面我们显示一些高级列表功能,它允许我们将字符串列表加入到一个字符串中。 当您需要继续进行基于行的文本输入时,这非常方便。使用 QString::split() 函数可以将这个操作进行逆向处理(将字符串转换为字符串链表)。

QString s1("Hello");
QString s2("Qt");
// convenient container classes
QStringList list;
list <<  s1 << s2;
// join strings
QString s = list.join(" ") + "!";
cout << s << endl;
文件 IO

在下一个片段中,我们从本地目录中读取一个CSV文件,并循环遍历行,以从每行中提取内容。为了做到这一点,我们需要从CSV文件中获取大约20行的编码。文件读取只给我们一个字节流,能够将其转换成一个有效的 Unicode 文本,我们需要使用文本流,并将文件作为较低级别的流传递。对于编写CSV文件,我们只需要在写入模式下打开文件,并将文本一行一行地输入到文本流中。

QList<QStringList> data;
// file operations
QFile file("sample.csv");
if(file.open(QIODevice::ReadOnly)) {
    QTextStream stream(&file);
    // loop forever macro
    forever {
        QString line = stream.readLine();
        // test for null string 'String()'
        if(line.isNull()) {
            break;
        }
        // test for empty string 'QString("")'
        if(line.isEmpty()) {
            continue;
        }
        QStringList row;
        // for each loop to iterate over containers
        foreach(const QString& cell, line.split(",")) {
            row.append(cell.trimmed());
        }
        data.append(row);
    }
}
// No cleanup necessary.

至此,关于使用 Qt 进行控制台应用程序的编写介绍就告一段落。

2.3.2.窗口应用程序

基于控制台的应用程序非常方便,但有时您需要有一个 UI 来显示。 此外,基于 UI 的应用程序可能需要后端来读/写文件,通过网络进行通信,或将数据保存在容器中。

在第一个窗口应用程序片段中,我们根据需要尽可能少地创建一个窗口并显示它。没有父对象的窗口部件是 Qt 世界中的一个窗口。我们使用智能指针来确保当智能指针指向范围外时窗口会被删除掉。应用程序对象封装了 Qt 运行时库,并且通过调用 exec() 启动事件循环。应用程序只能对鼠标、键盘或者其他事件提供程序(如网络或文件 IO)触发的事件发生响应。当退出事件循环时,应用程序才会退出。这可以通过在应用程序上调用 quit() 或关闭窗口来完成。

当我们运行代码时,我们将看到一个大小为 240 x 120 像素的空白窗口。

#include <QtGui>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QScopedPointer<QWidget> widget(new CustomWidget());
    widget->resize(240, 120);
    widget->show();
    return app.exec();
}
自定义窗口部件

当使用用户界面时,我们可能需要创建自定义的窗口部件。 通常,窗口部件是一个填充绘制的窗口区域。另外,窗口部件具有如何处理键盘或鼠标输入以及如何对外部触发器做出反应的内部知识。为了在 Qt 中做这些,我们需要从 QWidget 派生并覆盖绘画和事件处理的几个函数。

#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H

#include <QtWidgets>

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = 0);
    void paintEvent(QPaintEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
private:
    QPoint m_lastPos;
};

#endif // CUSTOMWIDGET_H

在实现中,我们在窗口部件上绘制一个小边框,并在最后一个鼠标位置上绘制一个小矩形。这对于低级别的自定义窗口部件是非常典型的。鼠标或键盘事件会更改窗口部件的内部状态并触发绘画更新。我们不会在这段代码中详细介绍,但是希望你知道我们有这个能力。Qt 带有一大批现成的桌面小部件,因此我们不必自定义窗口部件的概率很高。

#include "customwidget.h"

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
}

void CustomWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QRect r1 = rect().adjusted(10,10,-10,-10);
    painter.setPen(QColor("#33B5E5"));
    painter.drawRect(r1);

    QRect r2(QPoint(0,0),QSize(40,40));
    if(m_lastPos.isNull()) {
        r2.moveCenter(r1.center());
    } else {
        r2.moveCenter(m_lastPos);
    }
    painter.fillRect(r2, QColor("#FFBB33"));
}

void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    m_lastPos = event->pos();
    update();
}

void CustomWidget::mouseMoveEvent(QMouseEvent *event)
{
    m_lastPos = event->pos();
    update();
}
桌面窗口部件

Qt 开发人员已经为您完成了所有这些工作,并提供了一组桌面小部件,它们将在不同的操作系统上看起来很像是原生的窗口部件。然后,我们的工作就是将部件容器中的这些不同的部件排列成更大的面板。Qt中的小部件也可以是其他小部件的容器。这是通过亲子关系来实现的。。这意味着我们需要准备类似按钮(button),复选框(check box),单选按钮(radio button)的窗口部件并且对它们进行布局。完成此操作的一种方法如下所示。

这是一个所谓的 widget 容器的头文件。

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = 0);
private slots:
    void itemClicked(QListWidgetItem* item);
    void updateItem();
private:
    QListWidget *m_widget;
    QLineEdit *m_edit;
    QPushButton *m_button;
};

在实现中,我们使用布局来更好地安排我们的小部件。 当容器小部件重新调整大小时,布局管理器会根据某些大小策略重新布置小部件。在这个例子中,我们有一个列表,一个行编辑和一个垂直排列的按钮,可以编辑城市列表。我们使用 Qt 的信号和槽来连接发送器和接收器对象。

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
    QVBoxLayout *layout = new QVBoxLayout(this);
    m_widget = new QListWidget(this);
    layout->addWidget(m_widget);

    m_edit = new QLineEdit(this);
    layout->addWidget(m_edit);

    m_button = new QPushButton("Quit", this);
    layout->addWidget(m_button);
    setLayout(layout);

    QStringList cities;
    cities << "Paris" << "London" << "Munich";
    foreach(const QString& city, cities) {
        m_widget->addItem(city);
    }

    connect(m_widget, SIGNAL(itemClicked(QListWidgetItem*)), 
            this, SLOT(itemClicked(QListWidgetItem*)));
    connect(m_edit, SIGNAL(editingFinished()), this, SLOT(updateItem()));
    connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
}

void CustomWidget::itemClicked(QListWidgetItem *item)
{
    Q_ASSERT(item);
    m_edit->setText(item->text());
}

void CustomWidget::updateItem()
{
    QListWidgetItem* item = m_widget->currentItem();
    if(item) {
        item->setText(m_edit->text());
    }
}
绘制图形

有一些问题最好用可视化的方式表达。如果手边的问题看起来有点像几何对象,qt graphics view 是一个很好的选择。一个图形视窗(graphics view)能够在一个场景(scene)排列简单的几何图形。用户可以与这些图形交互,它们使用一定的算法放置在场景(scene)上。填充一个图形视图你需要一个图形窗口(graphics view)和一个图形场景(graphics scene)。一个图形场景(scene)连接在一个图形窗口(view)上,图形对象(graphics item)是被放在图形场景(scene)上的。这里有一个简单的例子,首先头文件定义了图形窗口(view)与图形场景(scene)。

class CustomWidgetV2 : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidgetV2(QWidget *parent = 0);
private:
    QGraphicsView *m_view;
    QGraphicsScene *m_scene;

};

在实现中首先将图形场景(scene)与图形窗口(view)连接。图形窗口(view)是一个窗口部件,能够被我们的窗口部件容器包含。最后我们添加一个小的矩形框在图形场景(scene)中。然后它会被渲染到我们的图形窗口(view)上。

#include "customwidgetv2.h"

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
    m_view = new QGraphicsView(this);
    m_scene = new QGraphicsScene(this);
    m_view->setScene(m_scene);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setMargin(0);
    layout->addWidget(m_view);
    setLayout(layout);

    QGraphicsItem* rect1 = m_scene->addRect(0,0, 40, 40, Qt::NoPen, QColor("#FFBB33"));
    rect1->setFlags(QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsMovable);
}

2.3.3. 数据适配

到目前为止,我们主要涵盖基本数据类型以及如何使用视图部件和图形视图。通常在我们的应用程序中,我们将需要更大量的结构化数据,也必须持续存储。数据也需要显示。对于这个 Qt 使用的模型。一个简单的模型是字符串列表模型,它被字符串填充,然后附加到列表视图。

m_view = new QListView(this);
m_model = new QStringListModel(this);
view->setModel(m_model);

QList<QString> cities;
cities << "Munich" << "Paris" << "London";
model->setStringList(cities);

另一个比较普遍的用法是使用 SQL(结构化数据查询语言)来存储和读取数据。Qt 自身附带了内嵌版的 SQLLite 并且也支持其它的数据引擎(比如 MySQL,PostgresSQL,等等)。首先你需要使用一个模式来创建你的数据库,比如像这样:

CREATE TABLE city (name TEXT, country TEXT);
INSERT INTO city value ("Munich", "Germany");
INSERT INTO city value ("Paris", "France");
INSERT INTO city value ("London", "United Kingdom");

为了能够在使用 sql,我们需要在我们的项目文件(*.pro)中加入 sql 模块。

QT += sql

然后我们可以使用 C++ 打开我们的数据库。首先,我们需要检索指定数据库引擎的新数据库对象。使用此数据库对象,我们打开数据库。对于 SQLite 这样的数据库我们可以指定一个数据库文件的路径。Qt提供了一些高级的数据库模型,其中有一种表格模型(table model)使用表格标示符和一个选项分支语句(where clause)来选择数据。这个模型的结果能够与一个链表视图连接,就像之前连接其它数据模型一样。

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName('cities.db');
if(!db.open()) {
    qFatal("unable to open database");
}

m_model = QSqlTableModel(this);
m_model->setTable("city");
m_model->setHeaderData(0, Qt::Horizontal, "City");
m_model->setHeaderData(1, Qt::Horizontal, "Country");

view->setModel(m_model);
m_model->select();

对于更高级别的模型操作,Qt 提供了一个排序文件代理模型,允许我们使用基础的分类排序和数据过滤来操作其它的模型。

QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
proxy->setSourceModel(m_model);
view->setModel(proxy);
view->setSortingEnabled(true);

过滤是基于要过滤的列和一个字符串作为过滤器参数完成的。

proxy->setFilterKeyColumn(0);
proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
proxy->setFilterFixedString(QString)

过滤器代理模型实际上比这里演示得更强大。现在我们记住存在这样一个强大的工具就足够了。

提示
这里是综述了我们可以在 Qt 5 中开发的不同类型的经典应用程序。桌面应用程序正在发生着改变,不久之后移动设备将会占据我们的世界。移动设备的用户界面设计非常不同。它们相对于桌面应用程序更加简洁,只需要专注的做一件事情。动画效果是一个非常重要的部分,用户界面需要生动活泼。传统的 Qt 技术已经不适于这些市场了。

2.3.4. Qt Quick 应用

在现代的软件开发中有一个内在的冲突,用户界面的改变速度远远高于我们的后端服务。在传统的技术中我们开发的前端需要与后端保持相同的步调。当一个项目在开发时用户想要改变用户界面,或者在一个项目中开发一个用户界面的想法就会引发这个冲突。敏捷的项目需要敏捷的方法。

Qt Quick 提供了一个类似 HTML 声明语言的环境应用程序作为你的用户界面前端(the front-end),在你的后端使用本地的 C++ 代码。这使我们能够获得两端的最佳的开发效果。

下面是一个简单的 Qt Quick UI:

import QtQuick 2.5

Rectangle {
    width: 240; height: 1230
    Rectangle {
        width: 40; height: 40
        anchors.centerIn: parent
        color: '#FFBB33'
    }
}

这种声明语言被称作 QML,它需要运行时才能执行。Qt 提供了一个典型的叫做 qmlscene 的运行环境,但是想要写一个自定义的运行环境也不是很困难,为此,我们需要一个 quick view 并且将 QML 文档作为它的资源。剩下的事情就只是显示用户界面了。

QQuickView* view = new QQuickView();
QUrl source = QUrl::fromLocalFile("main.qml");
view->setSource(source);
view.show();

回到我们早前的例子中。 在这个例子中,我们使用了一个 C++ 城市模型。如果我们可以在我们声明的 QML 代码中使用这个模型,那就再好不过了。

为了实现这一点,我们首先编写我们的前端,看看我们将如何使用城市模型。在这种情况下,前端需要一个名为 cityModel 的对象,我们可以在列表视图中使用它。

import QtQuick 2.5

Rectangle {
    width: 240; height: 120
    ListView {
        width: 180; height: 120
        anchors.centerIn: parent
        model: cityModel
        delegate: Text { text: model.city }
    }
}

为了启用 cityModel,我们可以复用我们以前的模型,并将context属性添加到我们的 root context 中(root context 是主文档中的另一个 root-element)。

m_model = QSqlTableModel(this);
... // some magic code
QHash<int, QByteArray> roles;
roles[Qt::UserRole+1] = "city";
roles[Qt::UserRole+2] = "country";
m_model->setRoleNames(roles);
view->rootContext()->setContextProperty("cityModel", m_model);

注意
这不是完全正确的用法,作为包含在 SQL 表格模型列中的数据,一个 QML 模型的任务是来表达这些数据。所以需要做一个在列和任务之间的映射关系。请查看 QML 和 QSqlTableModel 帮助文档获得更详细的信息。

2.4. 总结

我们已经看到如何安装 Qt SDK 以及如何创建我们的第一个应用程序。然后,我们介绍了不同的应用程序类型,以概述 Qt,展示了 Qt 为应用程序开发提供的一些功能。希望这些能使大家对 Qt 有一个初步的印象,Qt 是一个非常丰富的用户界面工具包,并提供应用程序开发人员可以期待的一切。不过,Qt 并不会将您锁定到特定的库中,因为您可以随时使用其他库或者扩展 Qt。Qt 对于不同类型的应用程序开发支持非常丰富:包括控制台程序,经典的桌面用户界面程序和触屏用户界面程序。

本文参考链接:Get Started

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • QML 性能上的注意事项和建议 赵者也[https://www.jianshu.com/u/7b2ff27d6fd...
    赵者也阅读 15,963评论 1 11
  • 1.初识 Qt5 本书将为大家介绍使用 Qt 5.x 版本开发应用程序的不同方面。我们将专注于新的 Qt Quic...
    赵者也阅读 2,537评论 0 8
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,501评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 昨天终于看了那部我想看的电影,《摔跤吧,爸爸》。这部电影评分很高而它确实值得拥有这个分数。阿米尔汗是一个很棒的演员...
    风铃儿风铃儿阅读 196评论 0 0