catalog

  • version
    • model
      • 函数
      • 遍历
    • 视图
      • 当前项、选中项
      • Select
        • SelectionMode
        • SelectionBahavior
        • QItemSelectionModel
      • QTableView
        • 网格线
  • ——
  • version
    • model
    • view
    • Delegate
    • QFileSystemModel
  • ___
  • version
    • example
      • Table
        • TableView
        • TableModel
          • data
          • headerData
          • flags
          • edit

version

model


model 就是一个 数据结构,形如上图。

他一定必有的是: 一个root, 指向一个 主table。
这个主table,有一个headers(即标签,比如你可以自定义为:姓名,年龄、、、)

这1个root 和 主table的n*m个cell,官方称为: item 一个项
可以发现,一个项 其实是个指针,他可以指向一个 子表table


即,model这个数据结构,他是一种 递归的结构,因为每一个item(cell),他都是一个指针,链接一个新table

因为每一个cell,都是一个指针,可以无限拓展。
为了方便管理,每个model 都是1个root 来统一管理(和链表的根指针 思想一样)
我们形象的说,这个结构是model,其实 并不存在model这个定义,qt里只有:itemModel

这就和链表list一样,list里没有存储所有节点的指针,或者说 并没有list这个东西。
我们只是拿到了一个链表指针head,然后不停的走next,遍历整个链表。
即,并不存在list这个结构体定义,我们只是对list里的每个节点node,定义node的结构体!!!

同样model也是完全一样的,没有对model的结构体定义。
只是对model里的每一个cell,有对cell的结构体定义。
这样,根据cell的next指针,来遍历整个model

这个cell,在qt里称为:QAbstractItemModel,qt里并没有QAbstractModel
即使是一些qt写好的现成model,比如:QFileSystemModel、、、 虽然名字里没有item,看似他是一个model
但其实不是的!,他也是继承自QAbstractItemModel


这是他的存储结构。 可以发现,这个存储结构,他几乎可以表示出,任何你能想到的数据结构。

比如,常用的list、table、tree,都可以表示出来。


这是他的存储结构。 我们不仅要存储,还要用来使用 即索引retrieve。

每一个item(cell) 他里面存储的 只有数据,而为了定位 每一个item的位置 因为model结构很复杂,位置定位即为重要

qt为每一个item,都配备了一个名为index的数据结构 QModelIndex(他是单独的类,没有父类)

QModelIndex里有int row, int column, QModelIndex parent
表示当前这个index, 相对他所在的table里的行号和列号,parent表示该table的 父index。
没有child,因为一个index 也就是一个cell,他的儿子 是一个table,而不是一个cell!!!
所以,不会通过index来获取他的子表。 具体方式下面会介绍。
总之,index存储: 当前这个cell,在当前子表table的位置,以及父index


因此,一个cur_index,就是来指定一个cell。
一个cur_index,他里面的内容是: row, column, Parent
Parent是一个index,他指向了一个table。这个table的[row, column],就是cur_index

可以发现,我们没有办法指定root 这个index,因为他没有parent!!!
所以,qt规定,root这个index 你可以直接构造出来。他就是QModelIndex()

其他的index,不能直接构造出来。因为需要获取他的父index


他有个函数data(),表示当前cell里的数据。
但注意, index里没有data这个变量,index只存储位置信息。
data是一个功能函数,他会找到: 该index所引用的cell里的data,即他的找的item里的数据

即,我们先要拿到index对象 精确定位到哪一个cell之后,再去获取该位置上的cell

虽然我们使用的数据 就是这个主table,以及他的儿子们,但是 model会自动给我们加一个root

也就是,一个model = 1个root + 我们的table
我们要获取我们的table,肯定要先获取root!!! 因为root是tabel的父指针。 就和list一样,必须要先得到head指针

qt中规定,每一个model 的 root,他的定位,都为:QModelIndex(),即一个空的index对象。

每个model里,一个空的index对象,就表示root这个cell

我们得到root后,就可以往下遍历 我们table里的cell


我们一般认为,model里每个cell存储的数据 就是纯数据。 比如db里的数据。

但在qt里,他的范围更广。 从而让ui存储的数据 几乎没有!!

比如,你的一个cell 对应到ui的一个cell。
ui的一个cell,他里面不仅有data,还有: 字体颜色,背景颜色,文本对齐方式,, 很多ui的属性。

这在model里, model的cell 也是要存储这些信息的!!!

即一个cell,他里面存储的 不仅只有data,还有很多附加信息(字体颜色…)

即,一个cell里的数据,是分类的,在qt中称为role

你去获取一个cell的数据:itemData(index) 根据定位 获取一个cell的数据。 他的返回值是:QMap<int, QVariant>
其中,QVariant自然是cell里存储的数据, 这个int 便是类型

qt里有定义enum:

namespace Qt{enum ItemDataRole{DisplayRole  ' 显示的文本 'FontRole  ' 字体格式 'TextAlignmentRole  ' 对齐方式 'BackgroundRole  ' 背景颜色 'ForegroundRole  ' 前景颜色 '、、、}
}

一般,我们不直接操作item。 因为item里的数据,是分类的。我们要操作的 是某一个role

所以,qt封装了函数,供我们方便使用:
setData(index, value, role)
QVariant data(index, role)


函数

实际使用时,我们并不是直接得到root,然后每次我们自己写函数来移动指针 定位到某个cell,这也太麻烦了…

qt给我们封装了,就叫做: QAbstractItemModel
setHeaderData(第几个, 方向, 数据, role) 设置表头信息
headerData(第几个, 方向, role) 获取表头信息
注意,header表头 他也是item!!! 即他也分role
但root并不指向header!!root指向主表 主表里都是数据
所以, 一个model,并不只是一个root 他里面还有header,是对整个模型的封装

int rowCount(index) 获取index 他指向的子表的行数!!
int columnCount(index) 获取index 他指向的子表的列数!!

QModelIndex index(row, column, QModelIndex parent) parent他指向了一个table表,获取这个table表的[row, column]这个cell
QVariant data(index, role) index所代表的cell里的数据(等价于:index.data(role)
bool setData(index, value, role) 设置index所代表的cell里的数据

遍历

给你一个model XX_model model; 且XX_model 是QAbstractModel的子类,请你遍历这个model里的所有文本数据。
因为一个cell里有很多cell,只遍历文本数据 DisplayRole

void dfs(const XX_model& model, QModelIndex parent){DE<< model.data(parent, Qt::DisplayRole).toString();int rows = model.rowCount(parent); ' parent所指向的子表 'int columns = model.columnCount(parent);FOR(i, 0, rows-1, 1){FOR(j, 0, columns-1, 1){dfs(model, model.index(i, j, parent));}}
}

视图

当前项、选中项

一个视图,会一直有一个“当前项”,因为当键盘上下移动导航时,会需要这个项,所以他必须一直存在

当我们鼠标选中时,又会产生“选中项”。 一个项,可以同时是: 当前项 和 选中项

当前项 选中项
任何时刻,始终只有1个 可以有[0, inf]个
当键盘移动、鼠标点击时,当前项就会改变 鼠标单击、键盘ctrl/shift都可以选中。这要看selectionMode,可以自定义模式

Select

SelectionMode

void QAbstractView::setSelectionMode(QAbstractItemView::SelectionMode mode)
设置用户的选中模式:

  • SingleSelection: 最开始有1个当前项,没有选中项。只要点击,那么当前项和选中项永远相同,而且只有1个
    、、、、、、点击一个item,其他已经选中的item就会未选中。总之,全局只有1个item是选中的!!
    、、、、、、不可以使用ctrl多选,不可以使用shift多选。因为说了,只有1个item是选中的,就是鼠标点击的
  • ExtendedSelection: ctrl和shift可以使用
  • MultiSelection:点击谁 谁就选中,且一直是选中的。只有再次点击他,他才会未选中
  • NoSelection: 不能选中(只有当前项[虚线],没有选中项)

SelectionBahavior

他和上面selectionMode,不冲突!!!

  • QAbstractItemView::SelectItems: 默认
  • QAbstractItemView::SelectRows: 如果一个item是选中的,则他的所在行的所有item,都会选中
  • QAbstractItemView::SelectColumns:如果一个item是选中的,则他的所在列的所有item,都会选中

QItemSelectionModel

当调用: view.setModel( &model )时, 就产生一个QItemSelectionModel对象!!!

他是控制 这个view里的 所有和选中有关的操作。

当 当前项 发生改变,就会 发出 QItemSelectionModel::currentChanged(QModelIndex cur, QModelIndex pre)信号,表示: 之前pre是当前项,现在变成了cur

当 选中项 发生改变,就会 发出 QItemSelectionModel::selectionChanged(QItemSelection cur, QItemSelection pre)信号,表示: 之前pre是选中的,现在cur是选中的
其中的QItemSelection是一个集合,表示一些index。
、、、他有个indexs()函数,返回所有的index
、、、还有个select(index top_lef, index bot_rig),表示将[top_lef, bot_rig]这个矩阵的所有index,加入集合里。

QItemSelectionModel

  • clear: 清空所有的选中项
  • select: 将一些index,设置成选中

比如,我们用另一种方式,实现 选中一行:

v.setSelectionMode(QAbstractItemView::NoSelection);connect(v.selectionModel(), &QItemSelectionModel::currentChanged, this,[&](const QModelIndex& cur, const QModelIndex& pre){v.selectionModel()->clear(); ' 把所有选中 清空 'v.selectionModel()->select(cur,QItemSelectionModel::Select | QItemSelectionModel::Rows);
}

注意,必须要设置NoSelection,否则不生效。 因为先处理我们的槽函数,然后还是会进入selectionMode
导致,我们自定义设置的select函数,被顶替了。
(不推荐使用这个,推荐用 selectionBehavior)

QTableView

网格线

setShowGrid(false) 是否展示网格
setGridStyle(Qt::DashLine) 网格风格

——

version

model

model 为 view和delegate 提供了访问数据的标准接口,这个标准接口,是由QAbstactItemModel规定的

当model里的数据changed,model通过信号与槽机制,告诉view

data不必存放在model里,data可以在数据库/文件/…

因为QAbstactItemModel是抽象类,我们使用的,肯定他是的子类。

qt提供了一些现成的 子类模型(非抽象类):

  • QStringListModel
  • QStandardItemModel:
  • QFileSystemModel:
  • QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel

这些都是标准的模型。
如果不能满足需求,可以子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来自定义

view

view视图层 的基类是 QAbstractItemView。

qt提供了现成的视图:
QListView、QTableView、QTreeView

Delegate

delegate委托 的基类是: QAbstractItemDelegate

qt现成的委托有: QStyledItemDelegate、QItemDelegate

qt使用的默认委托是:QStyledItemDelegate

QStyledItemDelegate使用当前的样式来绘制,所以,在实现自定义委托时,使用QStyledItemDelegate作为基类

QFileSystemModel

QFileSystemModel model;
model.setRootPath("D:/"); ' 但其实,model里是有你整个电脑文件系统的 'QTreeView treeView;
treeView.setModel(&model);
treeView.setRootIndex(model.index("D:/")); ' model有很多的index。 而view只能选择某一个目录下的 'QListView listView;
listView.setModel(&model);
listView.setRootIndex(model.index("D:/"));


treeVIew可以展开看子目录,而list只能看当前目录。

都不能编辑,因为model默认没有编辑能力。

___

version

一些ui部件,是要展示数据所谓数据,也就是ui上的文本的(比如label上的数据、table里的数据)
而这些数据 往往是外部动态的(比如来自于数据库)

也就是, ui部件 如何与 数据,相关联?
有两种方式:

  • ui部件,内部再存储一份数据,即自己本身自带数据,这是很直观的想法
    即,ui内部存储的数据 与 外部引用的数据,是两个东西。我们程序员需要自己 去确保,这两份数据是保证高度相同的。
    这也说明,这种方式的 耦合性 很高。
    但是,在大型项目里,这会导致数据无法同步的问题
  • ui部件,内部不会存储数据。ui部件 通过标准化的接口 来访问 外部数据 数据只有1份!!
    这称为: model - view编程。

标准部件standard widget,是使用的 第一种方式,即ui部件内部 会另存一份数据。


view widget,是使用的 第二种方式,ui部件内部并没有数据 通过(指针)链接 外部数据。

标准widget 与 view widget 同一个ui部件,对应2种实现方式

像button、lineedit这些部件,里面存储的数据 比较单一 只有1个
而像:table二维表格、list一维列表、tree可展开列表,这些部件 他里面存储的数据 是很多的!!
比如table,他二维表格 共n*m个项,都要存储数据。

example

Table

TableView

表示,存放表格table 的view

QTableView tableView;
tableView.setGeometry(0, 0, 1000, 1000);
tableView.show();

你这样,他是 完全空白的,连表格线都没有!!!
因为,view的row/column的个数、cell的内容,完全是由model来决定的。
view没有model,自然就是0行 0列,什么都没有。

view 必须和 model,链接一起来使用

MyTableModel model;
tableView.setModel( &model );tableView.show();

view的setModel()函数,便是将 view和model,链接起来。

既然是table,那么MyTableModel里,需要提供哪些信息呢??

  • model里,需要提供: row个数、column个数
  • model里,需要提供,这row*column个cell的 数据!!

TableModel

class MyTabelModel : public QAbstractTableModel
{Q_OBJECT
public:MyTabelModel(QObject *parent = nullptr);int rowCount(const QModelIndex &parent = QModelIndex()) const override;int columnCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

我们的tableModel,必须实现父类的 这3个纯虚函数。
row个数、column个数、以及 这个row*column个cell的内容

int rowCount(...){ return 2; }
int columnCount(...){ return 3; }
QVarient data(const QModelIndex &index, int role = Qt::DisplayRole){return QString("Row%1, Column%2").arg(index.row() + 1).arg(index.column() +1);
}

示例:

QTableView tableView;MyTableModel myModel();
tableView.setModel( &myModel );tableView.show();


data函数参数中的QModelIndex index,他里面指定了 是哪个cell,即row和column

model除了可以控制view的数据(文本),还可以控制每个cell的外观
比如说,你的model只有1个cell,调试可以发现,data函数 是会调用很多次的。
一开始的展示 data会调用很多次,每次鼠标经过每个cell data也会调用很多次

第一个参数index肯定都一样的 因为只有1个cell,关键是第二个参数int role
根据不同的role,你应该给他返回不同的东西。
data调用对应的role的值: 6 7 9 10 1 0 8 6 7 9 10 1 0 8
当role = 6 = Qt::DisplayRole,当调用data的参数role=6时,你应该给他返回 这个cell的数据(即文本内容)
当role = Qt::BackgroundRole,你应该返回一个QBrush,来表示该cell的背景颜色
当role = Qt::FontRole,你应该返回一个QFont,来表示该cell上文本的字体格式

所以,这个role 称为一个cell的 数据角色, 表示 该cell里的数据(文本,背景色,前景色,对齐…)


比如,只有一个cell:

QVariant data(const QModelIndex &index, int role){if (role == Qt::DisplayRole) {return QString("展示内容");} else if (role == Qt::BackgroundRole) {return QBrush("red");} else if (role == Qt::FontRole) {return QFont("宋体", 12);} else if (role == Qt::TextAlignmentRole) {return Qt::AlignLeft;} else if (role == Qt::CheckStateRole) {return Qt::Checked;} else if (role == Qt::ForegroundRole) {return QBrush("green");}
}

问题: 当model里的数据改变了,怎么能让view知道 需要重新去读data,而且精确到 需要重新读哪些cell的data

if (role == Qt::DisplayRole) {return QTime::currentTime().toString();
}

即,数据一直在变。 但实际效果是: view的文本 是不变的。 只有当鼠标经过、与该cell相关的操作时,文本才会更新。

所以,我们需要显式的 让view去读data。

connect(&timer, &QTimer::timeout, this, [&](){QModelIndex topLeft = createIndex(0, 0);emit QAbstractItemModel::dataChanged(topLeft,topLeft, {Qt::DisplayRole});
});

dataChanged(QModelIndex, QModelIndex, QVector<int>)
表示,从左上cell 到 右下cell 这个区域,重新读role(这个role由vector指定)
如果vector是空,即重新读所有的role

当我们为view.setModel设置model时,他其实会自动调用dataChanged函数


data函数,是设置nm个cell。
表格除了这n
m个cell,还有2个header(即表头,水平和垂直)

表头的也是 role,即文本内容、颜色、等…

cell是实现data函数,表头是实现headerData函数

QVariant MyModel::headerData(int index, Qt::Orientation orientation, int role){if (role == Qt::DisplayRole) { ' 设置表头的文本 'switch (orientation) { ' 水平表头 'case Qt::Horizontal:switch (index) {case 0:return QString("水平0");case 1:return QString("水平1");case 2:return QString("水平2");}case Qt::Vertical:switch (index) {case 0:return QString("垂直0");case 1:return QString("垂直1");case 2:return QString("垂直2");}}}
}

data

data(const QModelIndex &index, int role)
data修饰的对象是: view里的cell 方向是:[view] -> [model]
view 单向去获取 model 里的数据。

他是以每个cell 的 每个role为单位的,
每个cell单元格每个role(文本、颜色),都需要调用data函数

headerData

headerData(int index, Qt::Orientation orientation, int role)
headerData修饰的对象是: view里的两个header表头 方向是:[view] -> [model]
view 单向去获取 model 里的数据。

他是以每个header的每个单元格 的 每个role为单位的,
每个header单元格每个role(文本、颜色),都需要调用headerData函数

flags

Qt::ItemFlags flags(const QModelIndex &index) const override;
flags函数,返回一个cell,他的属性
比如:是否able,是否可以选中,是否可以修改文本,是否可以更改checkBox…

如果你不实现,即QAbstractItemModel会有个默认实现,他只实现了Qt::ItemIsSelectable、Qt::ItemIsEnabled

即,默认是 不能修改文本,不能更改checkBox


第一次展示、每次鼠标经过.....操作,都会: 先调用flags函数,再调用data

先调用flags函数,来看你当前操作 是否可以继续。

比如,不允许edit,你点击了文本,那他就没有反映。

Qt::ItemFlags MyModel::flags(const QModelIndex &index) const {return Qt::ItemIsUserCheckable |   ' 可以点击checkBox 'Qt::ItemFlag::ItemIsEditable |  ' 可以点击文本 'QAbstractItemModel::flags(index);
}
edit

当我们对flags 如上更改后,现在可以点击文本、checkBox。但点击后,数据还是不变的。

我们需要重写一个bool setData(const QModelIndex &index, const QVariant &value, int role) override;函数


比如,我们对一个cell 点击他的文本输入了新内容 或 checkBox后,会先进入该cell的flags函数里,看当前操作是否继续。

然后,进入setData函数里:

  • value是你的操作。 比如你修改了文本,那么value就是你新输入的内容。
    如果你点击了checkBox,value=0 则为,此时的checkBox是未选中的。
  • role是类型,有2种: 修改文本、 点击checkBox
  • 返回值意思是: 因为你需要去value,去修改你后台数据。 返回值是 你是否修改后台数据成功了。 这一般都是true

在setData函数里,你应该手动发信号dataChanged,让view调用data函数。
因为,view是没有数据的!!! 他只是读了一个数据,然后把数据丢给屏幕!!
view里没有数据,你修改文本,你新的内容 view不会存储!!他只是调用setData函数,作为函数参数 传递给model


我们暂时用Check数组来表示: 你后台,用来存储每个checkBox的选中情况(0为未选中)
用Data数组来表示: 你后台,用来存储每个cell的文本

QVariant MyModel::data(const QModelIndex &index, int role) const
{if (role == Qt::DisplayRole){return Data[index.row()][index.column()];}else if (role == Qt::CheckStateRole) {if(Check[index.row()][index.column()] == 0) return Qt::Unchecked;return Qt::Checked;}
}
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role){if(role == Qt::CheckStateRole){ ' 点击checkBox 'Check[index.row()][index.column()] = value.toInt();}else if(role == Qt::EditRole){ ' 修改文本 'Data[index.row()][index.column()] = value.toString();}emit QAbstractItemModel::dataChanged(index, index, {role} );' 记得要发信号。 让view去data里读!! 'return true;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const {return Qt::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEditable | QAbstractItemModel::flags(index);
}

Model/View模型视图相关推荐

  1. C++Qt开发——Mode View(模型视图)

    Model/View(模型/视图)结构 简介 Model/View(模型/视图)结构是 Qt 中用界面组件显示与编辑数据的一种结构,视图(View)是显示和编辑数据的界面组件,模型(Model)是视图 ...

  2. 深入理解模型视图、自定义模型

    一.深入理解模型 在 model/view 架构中,model 提供一种标准接口,供视图和委托访问数据.在 Qt 中,这个接口由QAbstractItemModel类进行定义.不管底层数据是如何存储的 ...

  3. Qt Model/View编程介绍

    Qt中包含了一系列的项视图类,它们使用model/view的架构去管理数据之间的关系以及它们被展示给用户的方式.这种由这种架构引进的功能分离特性给了开发者很大的灵活性去自定义自己的展示方式,并且提供了 ...

  4. 模型视图简介、QListWidget、QTreeWidget、QTableWidget、QStringListModel、QFileSystemModel

    一.模型视图简介 有时,我们的系统需要显示大量数据,比如从数据库中读取数据,以自己的方式显示在自己的应用程序的界面中.早期的 Qt 要实现这个功能,需要定义一个组件,在这个组件中保存一个数据对象,比如 ...

  5. Ember——在构建Ember应用程序时,我们会使用到六个主要部件:应用程序(Application)、模型(Model)、视图(View)、模板(Template)、路由(...

    在构建Ember应用程序时,我们会使用到六个主要部件: 模板(Template).应用程序(Application).视图(View).路由(Routing).控制器(Controller)和模型(M ...

  6. QT Basic 014 Model/View programming (模型、视图编程)

    前言:本文不是纯文本翻译,加入了对概念的理解,纯文本翻译,请看文后的一个链接. Model/View Programming Introduction to Model/View Programmin ...

  7. QT Model/View 编程:MVC模型视图编程:实例实现(二)

    目录 样例001:现有模型中使用视图Using views with an existing model 样例002:使用模型索引 样例003:使用模型 样例004:使用模型的多个视图 样例005:委 ...

  8. Qt Model/View(MVD)模型分析

           最近在看Qt的Model/View Framework,在网上搜了搜,好像中文的除了几篇翻译没有什么有价值的文章.E文的除了Qt的官方介绍,其它文章也很少.看到一个老外在blog中写道M ...

  9. Qt 学习之路:模型-视图高级技术

      PathView PathView是 QtQuick 中最强大的视图,同时也是最复杂的.PathView允许创建一种更灵活的视图.在这种视图中,数据项并不是方方正正,而是可以沿着任意路径布局.沿着 ...

最新文章

  1. JSF or PHP or ADF?!! :(
  2. 探索 Python、机器学习和 NLTK 库 开发一个应用程序,使用 Python、NLTK 和机器学习对 RSS 提要进行分类
  3. 微服务精华问答 | 微服务有什么优点和不足呢?
  4. cesium加载百度地图_Cesium专栏-百度地图加载(附源码下载)
  5. 基于大数据搭建社交好友推荐系统
  6. eclipse 构建maven web工程
  7. 服务器启动之后运行脚本在/etc/rc.d/rc.local中配置
  8. void value not ignored as it ought to be
  9. 23个机器学习最佳入门项目(附源代码)
  10. 阿里云 vs Azure-存储 CDN
  11. css实现鼠标悬停效果
  12. 语音识别引擎_linux 语音识别引擎_中文实时语音识别引擎 - 云+社区 - 腾讯云
  13. 我的时间管理及未来两年IT规划
  14. mysql的下载安装
  15. 孔庆东看金庸小说的奇情怪恋
  16. 慕课软件质量保证与测试(第三章.课后作业)
  17. 使用pyenv进行Python多版本控制
  18. webview加载的页面和浏览器渲染的页面不一致_QQ音乐Android客户端Web页面通用性能优化实践...
  19. 这篇文章讲了一个新科技
  20. pb调用精伦电子sdtapi.dll读卡函数的心得

热门文章

  1. html语言 表格,HTML中表格的实现
  2. 浅谈数据结构与算法分析学习及如何进行算法分析
  3. 基于区块链的融合通信初探(二)
  4. html怎样设置form的位置,html中 table 和 form的位置
  5. ubuntu下禁止TeamViewer开机自启分析
  6. 安装OpenStack-wallaby
  7. 块状链表(STL rope)
  8. 【HTML——旋转晕眩】(效果+代码)
  9. 记一次有惊无险的Linux数据恢复过程
  10. 用HTML+CSS实现一下百度顶部的搜索框