存档在 2013年12月

QT-2D坐标变换系统

2013年12月26日

函数接口

translate()函数,进行平移变换;scale()函数,进行比例变换;rotate()函数,进行旋转变换;shear()函数,进行扭曲变换。
函数save()和restore(),利用它们来保存和弹出坐标系的状态,从而实现快速利用几个变换来绘图

坐标系简介

Qt中每一个窗口都有一个坐标系,默认的,窗口左上角为坐标原点,然后水平向右依次增大,水平向左依次减小,垂直向下依次增大,垂直向上依次减小。原点即为(0,0)点,然后以像素为单位增减。 如下的代码简单演示了这段话。

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setBrush(Qt::red);
    painter.drawRect(0,0,100,100);
    painter.setBrush(Qt::yellow);
    painter.drawRect(-50,-50,100,100);
}

先在原点(0,0)绘制了一个长宽都是100像素的红色矩形,又在(-50,-50)点绘制了一个同样大小的黄色矩形。可以看到,我们只能看到黄色矩形的一部分。效果如下图。
1

坐标系变换

坐标系变换是利用变换矩阵来进行的,我们可以利用QTransform类来设置变换矩阵,因为一般我们不需要进行更改,所以这里不在涉及。下面我们只是对坐标系的平移,缩放,旋转,扭曲等应用进行介绍。

1.translate()平移

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,50,50);
    painter.translate(100,100); //将点(100,100)设为原点
    painter.setBrush(Qt::red);
    painter.drawRect(0,0,50,50); //在新坐标系下画矩形
    painter.translate(-100,-100);//平移原点到原来位置
    painter.drawLine(0,0,20,20); //在原来坐标系下划线
}

先画一矩形,再平移原点并作为新坐标系,然后画一矩形,再平移原点到旧的坐标系并划线。
2

2.利用scale()函数进行比例变换,实现缩放效果

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,100,100);
    painter.scale(2,2); //放大两倍
    painter.setBrush(Qt::red);
    painter.drawRect(50,50,50,50);
}

3
我们将横纵坐标都扩大2倍,然后运行程序,查看效果:我们点击矩形右下顶点,是(100,100),比以前的(50,50)扩大了2倍。

3.利用shear()函数就行扭曲变换

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setBrush(Qt::yellow);
    painter.drawRect(0,0,50,50);
    painter.shear(0,1); //纵向扭曲变形
    painter.setBrush(Qt::red);
    painter.drawRect(50,0,50,50);
}

结果如下,painter.shear(0,1),是对纵向进行扭曲,0表示不扭曲,当将第一个0更改时就会对横行进行扭曲,关于扭曲变换到底是什么效果,你观察一下是很容易发现的。
4
这里,painter.shear(0,1),是对纵向进行扭曲,0表示不扭曲,当将第一个0更改时就会对横行进行扭曲,关于扭曲变换到底是什么效果,你观察一下是很容易发现的。

4.rotate()函数进行比例变换,实现缩放效果

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.drawLine(0,0,100,0);
    painter.rotate(30); //以原点为中心,顺时针旋转30度
    painter.drawLine(0,0,100,0);
    painter.translate(100,100);
    painter.rotate(30);
    painter.drawLine(0,0,100,0);
}

因为默认的rotate()函数是以原点为中心进行顺时针旋转的,所以我们要想使其以其他点为中心进行旋转,就要先进行原点的变换。这里的painter.translate(100,100)将(100,100)设置为新的原点.
5
如图所示两条直线并不平行,假设我们想要的是画两条平行的线段,原来来 painter.rotate(30)将坐标轴已经旋转,可以 painter.rotate(-30)来补偿先前的旋转。

坐标系状态的保护

我们可以先利用save()函数来保存坐标系现在的状态,然后进行变换操作,操作完之后,再用restore()函数将以前的坐标系状态恢复,其实就是一个入栈和出栈的操作。

void MainWindow::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.save(); //保存坐标系状态
    painter.translate(100,100);
    painter.drawLine(0,0,50,50);
    painter.restore(); //恢复以前的坐标系状态
    painter.drawLine(0,0,50,50);
}

这样就画出两条平行的直线。

Qt坐标变换

2013年12月26日

简介

在QT中,界面的几何尺寸获取可以通过如下图的示意来操作。
0_1314892991cj5q.gif
在Qt中的一个绘画设备是一个可画的二维平面。QWidget、QPixmap、QPicture和QPrinter都是绘画设备。QPainter是一个可以在上面画的对象。
原本坐标系的原点坐标是在绘图设备的左上角(0,0),X 向右增长, Y 向下增长。
coor
现在想把它变成,原点坐标在窗口中间,X 向右增长,Y向上增长,即是标准的数学中的坐标系。
coor2
painter.setWindow(x,y,width(),height());
用setwindow这个函数,表示什么意思。前两个参数左上角位置,后两个参数宽高。
可以把变换好的坐标系在纸上画出来,左上角坐标传给前两个参数,后两个参数宽高可以算出来。
width = 50-(-50) = 100;
height = -50-50 = -100;
就是用变换好的坐标系右下角坐标减去左上角坐标。那么就是

painter.setWindow(-50,50,100,-100);

一般使用如下方式

painter.setWindow(-width()/2,height()/2,width(),-height());

就可以达到目的了。

和坐标变换相关的类

这里是一些和坐标系统关系很近的类:
1. QPoint是坐标系统中的单个二维点。Qt中处理点的绝大多数函数都可以接受QPoint参数或者两个整数参数,比如QPainter::drawPoint()。
2. QSize是单个二维矢量。在内部,QPoint和QSize是一样的,但是一个点和一个大小不同,所以这两个类都存在。同样,绝大多数函数都可以接受一个QSize或者两个整数,比如QWidget::resize()。 
3. QRect是一个二维的矩形。绝大多数函数都可以接受一个QRect或者四个整数,比如QWidget::setGeometry()。
4. QRegion是点的一个任意组合,包括所有通常的组合操作,例如QRegion::intersect(),并且一些常用的函数返回一个矩形的列表,这个矩形的列表的集合等于一个区域。QRegion被例如QPainter::setClipRegion()、QWidget::repaint()和QPaintEvent::region()使用。
5. QPainter是一个绘图的类。它可以用同样的代码在任何一个设备上绘画。设备中是存在差异的,QPrinter::newPage()是一个很好的例子,但QPainter可以对所有的设备按同一种方式工作。
6. QPaintDevice是一个QPainter可以在上面绘画的设备。这里有两种都基于像素的内部的设备,和两种外部设备,QPrinter和QPicture(它把QPainter的命令都记录在一个文件或者其它的QIODevice,然后再运行它们)。其它的设备都可以被定义。

Qt窗口与视口

2013年12月26日

为什么使用这两个概念?

为了让在逻辑坐标下自己作的图,在窗口大小变换时能自动适应,而不用去管窗体目前(物理上)是多大的。

概念区别

我们先要明白视口和窗口,物理坐标和逻辑坐标的概念,所谓视口与其物理坐标 ,就是指在屏幕上的看到的实际的一个个像素和其坐标,而所谓窗口和逻辑坐标,就是指我们指定了一个这个窗体的坐标系,在这个坐标系内做的东西是先在其内画好,在转换到物理坐标系内去。本来物理坐标和逻辑坐标显然是一样的,除非你调用了setWindow方法去设置了逻辑坐标。

相关描述

在QPainter中存在setWindow和setViewPort,分别用来设置绘图窗口和绘图的视口。

首先要说明的是,这个窗口和视口和可能和其它地方说明的窗口和视口含义不一样,把相关知识描述如下:
以在QWidget中绘图为例,QWidget大小(600,600)。Qt绘图时,如果不显式的指定窗口和视口,默认的窗口和视口都是(0,0,widget.width(),widget.height())。那么窗口-视口变换过程中的线性关系是X’ = X;Y’ = Y;其中(X,Y)是窗口中的坐标,(X’,Y’)是视口中的坐标。那么此时绘制的图形是以一个不变的关系绘制在QWidget上,没有任何的平移等效果。

如果使用setWindow(-300,-300,600,600)和setViewPort(-100,-100,600,600)之后呢,那么这个窗口-视口的线性关系则需要重新计算了,怎么计算?
设X’=aX+b;Y’=cY+d; 那么需要确定参数a,b,c,d。如何确定?
使用窗口坐标和视口坐标进行计算,在窗口(-300,-300,600,600)中,实际上是指定了窗口的两个坐标,左上角(-300,-300),右下角(300,300);在视口(-100,-100,600,600)中,指定了视口左上角坐标(-100,-100),视口右下角(500,500).根据左上角对应左上角,右下角对应右下角的关系,得到:x方向上有:-100=-300a+b,500=300a+b,解得a=1,b=200;y方向有:-100=-300c+d,500=300c+d,解得c=1,d=200。得到关系后再来绘图则是经过该关系变换后的值了。
比如painter.drawPoint(0,10),对应x=0,得到x’=0*a+b=200;对应y=10,得到y’=10*c+d=210;即实际上是在widget的(200,210)处绘制了一个点。

以上即是QPainter窗口-视口机制的具体计算过程。

谈QWidget及其派生类

2013年12月25日

QWidget

QWidget 是Qt中所有widget部件(比如QDialog、QPushButton、QLabel)的基类,任何你可以通过其派生类实现的东西,你都可以通过QWidget实现。

QDialog有模态非模态之说

QWidget 也有

QDialog dlg(this)是一个窗口

QWidget wgt(this)也可以是窗口

QMainWindow可以有菜单栏、工具栏等

QWidget 同样可以有这些

Window 与 Widget异同

Qt中的部件有Window和普通widget之说:

Window

窗口

Window是这样的Widget:它不是其他Widget的一部分区域,通常有标题栏等窗口装饰器!(和是否有parent无关)

Widget

普通部件(非窗口)

除Window外的部件

如何可以知道一个Widget是否是Window?通过函数QWidget::isWindow()

inline bool QWidget::isWindow() const
{ return (windowType() & Qt::Window); }

其中的windowType()为

inline Qt::WindowType QWidget::windowType() const
{ return static_cast<Qt::WindowType>(int(data->window_flags & Qt::WindowType_Mask)); }

根据其提示,可以得知和windowFlags()有关。

来看下面的函数

QWidget::QWidget(QWidget * parent = 0, Qt::WindowFlags f = 0 )
void QWidget::setWindowFlags(Qt::WindowFlags type )

QDialog::QDialog(QWidget * parent = 0, Qt::WindowFlags f = 0 )
...

你可以通过构造函数或者成员函数setWindowFlags传递这个参数。 而且,你从前面的isWindow()的源码可以得出结论: 一个Widget是不是一个Window,只取决于它的的WindowFlags中是否包含Qt::Window

综上所述,一个Widget是不是一个Window,只取决于它的的WindowFlags中是否包含Qt::Window,即与 Qt::WindowFlags有关,与parent无关。下面先例举所有的WindowFlags,其实WindowFlags是WindowType强化了类型安全。

    enum WindowType {
        Widget = 0x00000000,
        Window = 0x00000001,
        Dialog = 0x00000002 | Window,
        Sheet = 0x00000004 | Window,
        Drawer = Sheet | Dialog,
        Popup = 0x00000008 | Window,
        Tool = Popup | Dialog,
        ToolTip = Popup | Sheet,
        SplashScreen = ToolTip | Dialog,
        Desktop = 0x00000010 | Window,
        SubWindow = 0x00000012,
        ForeignWindow = 0x00000020 | Window,
        CoverWindow = 0x00000040 | Window,

        WindowType_Mask = 0x000000ff,
        MSWindowsFixedSizeDialogHint = 0x00000100,
        MSWindowsOwnDC = 0x00000200,
        BypassWindowManagerHint = 0x00000400,
        X11BypassWindowManagerHint = BypassWindowManagerHint,
        FramelessWindowHint = 0x00000800,
        WindowTitleHint = 0x00001000,
        WindowSystemMenuHint = 0x00002000,
        WindowMinimizeButtonHint = 0x00004000,
        WindowMaximizeButtonHint = 0x00008000,
        WindowMinMaxButtonsHint = WindowMinimizeButtonHint | WindowMaximizeButtonHint,
        WindowContextHelpButtonHint = 0x00010000,
        WindowShadeButtonHint = 0x00020000,
        WindowStaysOnTopHint = 0x00040000,
        WindowTransparentForInput = 0x00080000,
        WindowOverridesSystemGestures = 0x00100000,
        WindowDoesNotAcceptFocus = 0x00200000,

        CustomizeWindowHint = 0x02000000,
        WindowStaysOnBottomHint = 0x04000000,
        WindowCloseButtonHint = 0x08000000,
        MacWindowToolBarButtonHint = 0x10000000,
        BypassGraphicsProxyWidget = 0x20000000,
        WindowOkButtonHint = 0x00080000,
        WindowCancelButtonHint = 0x00100000,
        NoDropShadowWindowHint = 0x40000000,
        WindowFullscreenButtonHint = 0x80000000
    };

    Q_DECLARE_FLAGS(WindowFlags, WindowType)

来看下面几个例子:
1.比如:一个QPushButton,如果没有parent,它就是一个窗口。而设置了parent,它却不是一个窗口了。感觉就是和parent有关,而与flags无关。
其实对于QWidget,如果其parent为空,构造时会有下面的动作:

uint type = (flags & Qt::WindowType_Mask);

    if ((type == Qt::Widget || type == Qt::SubWindow) && w && !w->parent()) {
        type = Qt::Window;
        flags |= Qt::Window;
    }

当没有parent时, Qt::WindowFlags f =0,并且!w->parent()为true,得到flags |= Qt::Window,所以为windows。
当有parent时,w->parent()返回非null,Qt::WindowFlags f =0,所以为widget。

2.再来看个例子

QDialog::QDialog(QWidget *parent, Qt::WindowFlags f)
    : QWidget(*new QDialogPrivate, parent,
              f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0)))
{
...

QDialog是派生类,它构造函数中传给基类的参数包含了Qt::Dailog(即0x00000002|Qt::Window)。既然包含了Qt::Widnow标记,也即为window了。

3.再看

QPushButton * btn = new QPushButton(this);
btn-&gt;setWindowFlags(Qt::Window);

通过setWindowFlags也是可以的,前边也说了,可以通过构造函数设置,也可以通过setWindowFlags函数,运行后可以看见QPushButton 变成了窗口。

QT开源例程阅读:Dock Widgets Example

2013年12月24日

描述

描述如何使用QDockWindow与RichTextEdit。

应用

代码阅读

QMainWindowset的CentralWidget函数,用于设置将给定的控件置于QMainWindow的中心。
富文本操作
1.清空文本区域

textEdit->clear();

2.创建该文本区的Cursor,并移动鼠标指针到文本区的开始。

    QTextCursor cursor(textEdit->textCursor());
    cursor.movePosition(QTextCursor::Start);

3.创建和格式化一个frame

QTextFrame *topFrame = cursor.currentFrame();
    QTextFrameFormat topFrameFormat = topFrame->frameFormat();
    topFrameFormat.setPadding(16);
    topFrame->setFrameFormat(topFrameFormat);

4.文本格式化对象

    QTextCharFormat textFormat;
    QTextCharFormat boldFormat;
    boldFormat.setFontWeight(QFont::Bold);
    QTextCharFormat italicFormat;
    italicFormat.setFontItalic(true);

    QTextTableFormat tableFormat;
    tableFormat.setBorder(1);
    tableFormat.setCellPadding(16);
    tableFormat.setAlignment(Qt::AlignRight);

5.如何使用文本格式化对象,如下分别插入表格,文字和空行,格式为4中定义的3种。

 cursor.insertTable(1, 1, tableFormat);
    cursor.insertText("The Firm", boldFormat);
    cursor.insertBlock();

6.更改光标位置

cursor.setPosition(topFrame->lastPosition());

7.QMainWindow的菜单栏可以通过menuBar()返回,通过menuBar()->addMenu(tr(“&File”));来增加菜单。
像QMainWindow里添加QDockWidget

addDockWidget(Qt::RightDockWidgetArea, dock);

7.QDockWidget使用,设置停靠区域,如下为只允许左右停靠区停靠,默认为可以上下左右停靠。

dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

8.如何设置QDockWidget里的控件,将其作为父亲指针传入并setWidget即可。

customerList = new QListWidget(dock);//创建ListWidget
 dock->setWidget(customerList);//给dock设置控件
addDockWidget(Qt::RightDockWidgetArea, dock);像MainWindow增加DockWidget

9.给ListWidget关联信号与槽函数

    connect(customerList, SIGNAL(currentTextChanged(QString)),
            this, SLOT(insertCustomer(QString)));
    connect(paragraphsList, SIGNAL(currentTextChanged(QString)),
            this, SLOT(addParagraph(QString)));

QT开源例程阅读:Config Dialog Example

2013年12月23日

描述

该例子项目主要是应用QStackedWidget,QStackedWidget对一组页面进行管理,可以每次显示一个页面。
分页是从0开始,如果要使某个页面可见,调用函数setCurrentIndex(int)来设置。
页号的获取,可以通过indexOf来获取子窗口部件的页号。
创建一个QStackedWidget后,通过addWidget()来添加控件到该布局管理器,使用方法同其他布局管理器。

应用

一般用于安装软件时选项设置或者一些配置页面。

详细阅读

代码中使用了一个QListWidget,为一个列表管理器,其中需要添加QListWidgetItem项,可以通过如下函数获取当前某Item所在行号,current为QListWidgetItem*。

contentsWidget->row(current)

选择不同的Item,会触信号

SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)

该信号对应的槽函数,完成页面的切换,实际上调用函数setCurrentIndex来根据页号显示页面。

void ConfigDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous)
{
    if (!current)
        current = previous;

    pagesWidget->setCurrentIndex(contentsWidget->row(current));
}

其他

已经定义如下两个数据成员

QListWidget *contentsWidget;
QStackedWidget *pagesWidget;

1.为了设置QListWidget中的项以图片模式显示,需要使用如下代码设置

contentsWidget->setViewMode(QListView::IconMode);

2.设置Item是否可以自由移动,Static意味着Item不能移动,Free意味着能拖动和释放到任何地方,Snap意味着能拖动和释放Items,但是仅仅在gridSize属性指定的范围内。

contentsWidget->setMovement(QListView::Static);

3.设置QListWidget当前的Item

contentsWidget->setCurrentRow(0);

4.其他占位控件,addStretch增加可伸缩占位,其中1为伸缩因子。addSpacing为不可伸缩占位。

    mainLayout->addStretch(1);
    mainLayout->addSpacing(12);

5.QListWidget中的选择项被改变后,将会发送事件currentItemChanged。

    connect(contentsWidget,
            SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
            this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*)));

QT开源例程阅读:Nested Layouts

2013年12月22日

Widgets Tutorial – Nested Layouts

描述

该例子演示M/V模型与QTableView的使用,以及界面布局构建。
Model为QStandardItemModel
View为QTableView

Model的修改与数据添加

1.通过如下代码来修改横向header,修改header后,QTableView会自动决定列的数目,其中使用了QStringList()来存放标题行和数据每一行的列表。

model.setHorizontalHeaderLabels(
        QStringList() << QApplication::translate("nestedlayouts", "Name")
                      << QApplication::translate("nestedlayouts", "Office"));

2.QList来存放整张表,每一行的数据以String列表形式放于QStringList()中。QStringList重载了<<符号来添加元素。

    QList<QStringList> rows = QList<QStringList>()
        << (QStringList() << "Verne Nilsen" << "123")
        << (QStringList() << "Carlos Tang" << "77")
        << (QStringList() << "Bronwyn Hawcroft" << "119")
        << (QStringList() << "Alessandro Hanssen" << "32")
        << (QStringList() << "Andrew John Bakken" << "54")
        << (QStringList() << "Vanessa Weatherley" << "85")
        << (QStringList() << "Rebecca Dickens" << "17")
        << (QStringList() << "David Bradley" << "42")
        << (QStringList() << "Knut Walters" << "25")
        << (QStringList() << "Andrea Jones" << "34");

下面来看MODEL与VIEW的联系

1. QTableView的每一格为一个item,每一行的数据存放在QStringList,所以需要将以行存放的String转换为items,并使用appendRow将items以行为单位添加到Model中。
读取每一行的QStringList,将其中的Strings转换为以行为单位的items。

    foreach (QStringList row, rows) {
        QList<QStandardItem *> items;
        foreach (QString text, row)
            items.append(new QStandardItem(text));
        model.appendRow(items);
    }

2.Model与View的关联

resultView->setModel(&model);

其他

第一行隐藏了左侧的列标题
第二行使得QTableView,仅仅显示可用的列单元。

    resultView->verticalHeader()->hide();
    resultView->horizontalHeader()->setStretchLastSection(true);

Qt方案中文显示:直接在代码中使用中文

2013年12月22日

Qt应用程序进行中文显示有两种解决方案,一种是直接在代码中使用中文,利用QTextCodec类来实现,另一种是使用qt平台的国际化支持机制,通过语言翻译来实现。第一种方案直接明了,相对来说也简单方便一点,除了编码时麻烦点(来回切换输入法),而第二种方法的优点是具有良好的扩展性,代码中全部使用英文,然后使用中文翻译文件来进行语言翻译,当需要其它语言方案时,只需要添加翻译文件就可以。这里先介绍第一种方案。

方案实现

直接使用中文,利用QTextCodec类来实现中文的显示方案实现通过下面的三个步骤:
1.中文字体文件的存在:前文提到过字体文件的内容,文泉驿字体库是支持中文的。
2.应用程序代码中QTextCodec的设置和QFont字体的设置:对整个应用程序来说,一般是放在main函数中进行设置,大概的代码如下:

int main(int argc, char *argv[])
{
    QApplication app(argc,argv);
    ...
    QTextCodec* codec = QTextCodec::codecForName("UTF-8");
    app.setFont(QFont("wenquanyi",16,75,FALSE,QFont::Unicode));
    app.setDefaultCodec(codec);
    ...

    return app.exec();
}

这里需要说明一下TextCodec和Font的区别:

  • TextCodec:上面app.setDefaultCodec(codec)语句设置的TextCodec编码格式是指代码源文件的编码格式,不一致则无法会出现中文乱码。
  • Font: 上面设置的字体是说明应用使用的字体格式,这个参考前文的内容很好理解,字体的参数内容如果和字体文件的不一致,也只会使用最接近的字体文件格式,指定的参数无效,即如果只有wenquanyi_160_75.qpf字体文件,那么设置下面的字体和上面的设置效果是一样的。
app.setFont(QFont("wenquanyi",22,87,FALSE,QFont::Unicode));

3.第三步就是在源代码文件中直接编辑使用中文,然后确保该源文件的编码格式和上面TextCodec中设置的格式是一致的。如果不一致,例如TextCodec设置的是“GBK”,而源文件用的确是UTF-8格式,则中文显示乱码——我曾遇到的问题,改成一致后就OK了。
只要确保上面三步都是正确的,就可以实现qt应用程序中的中文显示了。

关于QObject::tr()函数与中文显示的说明

上面的方案实现中文的显示,是可以不使用QObject::tr()函数的,因为该函数是针对第二种方案设计的。
看一下该函数的源代码:

QString QObject::tr( const char *text, const char * comment )
{
    if ( qApp )
    return qApp->translate( "QObject", text, comment );
    else
    return QString::fromLatin1(text);
}

它使用QApplication类的translate翻译函数对输入参数text进行翻译,输出翻译的字符串。translate函数的原型:

QString QApplication::translate ( const char * scope, const char * key,
       const char * comment ) const;

该函数查找已经安装好的翻译信息文件,对key参数进行查找翻译,如果找到则返回翻译后的文本字串,如果找不到或者不存在相应的信息文件,则直接返回key本身,即不进行翻译。

因此可以看出,在第一种方案中,我们没有安装翻译器和翻译信息文件,也就没有必要使用QObject::tr()函数。实际上在使用tr时,还支持专门为tr翻译的文本指定TextCodec编码格式,以识别翻译出的文本,指定的编码格式应该和翻译信息文件中的编码格式一致。