QT模型视图MVC系列教程(5)---自定义委托Delegate编辑时显示

目录

1、官方例程赏析

2、自定义委托编程方法

3、编程示例


本文由【暴躁的野生猿】发表于CSDN,如有非法转载请帮忙举报谢谢。

1、官方例程赏析

QT提供的表格视图QTabelView或者表格控件QTableWidget中,我们一般用于显示一些文字内容,但是在官方教程中搜索delegate,可以看到QT官方例程,使用委托搞出来的一些什么花样操作:

(1)官方例程pixelDelegate。下图所示的东西竟然是个表格视图QTableView,而且并没有自定义视图(本系列教程后面会有自定义视图的博文,请关注)!表格的每个单元格里都放了一个黑色圆,这个黑色圆的半径代表了一个灰度值,灰度值来源于载入的一个jpg图片。鼠标框选,其实就是在框选单元格。

QT模型视图MVC系列教程(5)---自定义委托Delegate编辑时显示_第1张图片

(2)官方例程starDelegate,把几首音乐的评分展示为星级,并支持星级编辑。这个委托支持读写。

QT模型视图MVC系列教程(5)---自定义委托Delegate编辑时显示_第2张图片

(3)官方例程SpinboxDelegate。这个程序实现了单元格中只允许用户输入[0, 100]之间的整数。

QT模型视图MVC系列教程(5)---自定义委托Delegate编辑时显示_第3张图片

可见:委托的作用是让程序员实现视图中内容的自定义展示方式和编辑方式。

下面列举几个场景,这些场景适合都适合用委托:

(1)表格中要显示或编辑一列日期,默认的表格的单元格的输入框都是类似QLineEdit的一个小控件,程序员想要监控用户输入的合法性,实现起来较为繁琐。

类似场景还有:① 某一列单元格只允许输入[1,120]之间的正整数,② 只允许从下拉列表中选择数据。等等。

(2)表格中有一列内容是学生的成绩,为了便于观察成绩的分布,用户希望把成绩大小展现为进度条样式,或者类似上图的星级,如果学生的成绩低于60分,则显示为红色。

以上场景就是涉及到了单元格内容的展示方式和编辑方式,就是把单元格要显示和编辑的内容,用QT内置的各种QWidget或者自定义的Widget来进行输出显示和输入处理。

2、自定义委托编程方法

查询帮助文档可知,所以委托的祖宗类是QAbstractItemDelegate,QT内置了它的两个子类QItemDelegate and QStyledItemDelegate,其中QStyledItemDelegate会使用当前样式绘制内容,支持样式表,推荐程序员继承这个来实现自定义委托。

如官方例所示,如果我们期望通过画星星数,来显示数据,而QT内置的widget是没有这种功能的,这时有两种方案可选:

(1) 直接重写QT内置的祖宗类委托QAbstractItemDelegate的【绘制函数paint()】,以及【推荐尺寸函数sizeHint()】:
void QStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
QSize QStyledItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const

(2) 自定义widget,并使用这个widget进行委托,帮助手册指出,这时必须覆盖至少4个虚函数:

  • ① QWidget *QStyledItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
    功能:创建一个widget并返回。该函数会在用户双击单元格时,被自动调起,并显示出来供用户编辑数据,用户编辑完之后会自动析构。在这里可以设置widget的初状态,大小,显示的初值等。
    参数表:
    parent为View对象,例如QTableWidget、QTableView、QListWidget、QListView等;
    option为视图送进来的对应单元格的样式,包括单元格在View的坐标、单元格的尺寸、文字对齐方式、字体等;
    index为单元格的对应的模型索引,没什么好说的。
  • ② void QStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
    功能:当双击单元格时,widget会显示出来,然后本函数会被调起,程序员应当在这里设置刚弹出的widget要显示的东西(一般是要对应显示当前model里的值,比较符合人的直觉逻辑)。
    参数表:editor弹出的widget
  • ③ void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
    功能:该函数会在两种情形下被调起:一是用户双击单元格,widget控件弹出的瞬间,二是在widget正在显示时,用户更改了单元格的位置。因此在以上两种情况下,我们都需要个根据形参会传入的单元格的信息option,重新设置widget的位置或者大小。
    参数表:editor 、option如前所述。
  • ④ void QStyledItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
    功能:把用户编辑完的数据通过model存储起来。当用户在widget里编辑完数据之后,该函数会被自动调起。何时会发生“编辑完数据”事件?一般用户在别的单元格单击时会触发,有的控件会在用户回车时自动触发(例如QSpinBox),另外程序员还可以通过强制hide这个widget来人为触发这一事件(下文的例子就是这么做的)。

下面讲一下以上两种方法的优劣,以及最适合的使用场景:
方法(1)适合于纯自由绘制图形,以及图形长久保持在界面上时。(例子见我的下一篇博文)
方法(2)更适合于你打算使用QT内置的QWidget或自定义的Widget时,例如QSpinbox、QComboBox、QDateEdit及其子类等。

什么叫“图形长久保持在界面上”呢?比如文章开头的官方示例中,星标就是保持显示的,而另一个例子SpinBox就不是,spinbox只在用户双击单元格进入编辑态以后才会显示,再比如使用QComboBox下拉列表控件委托时,只有在进入编辑态,才会显示下拉箭头,如果用户不双击这个单元格,他甚至可能不会意识到这个单元格是可下拉的;再比如下面我写的日历控件的例子,只有进入编辑态,日历才会显示。

3、编程示例

基于上述方法(2),在表格中使用QT内置的日期控件来编辑日期。 

先来个动图看看效果:当双击单元格时,会弹出日历控件,用户单击日历上的日期时,日历隐藏,同时用户单击的日期会显示在单元格上,并通过Model被存了下来。

QT模型视图MVC系列教程(5)---自定义委托Delegate编辑时显示_第4张图片

在下面这个代码实例中,我不仅覆盖了官方手册所要求的的4个虚函数,还写了一个单击的槽函数,用于快速退出日历控件(通过hide实现的),要不然只有用户点击其他单元格后,这个日历控件才消失,体验不好。

#ifndef CUSTOMDELEGATE_H
#define CUSTOMDELEGATE_H

#include 
#include 
#include 

class CustomDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    CustomDelegate(QObject *parent = 0);

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;


private slots:
    void when_calendarClicked(const QDate &date);

private:

};

#endif // CUSTOMDELEGATE_H

#include "customDelegate.h"
#include 
#include 
CustomDelegate::CustomDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{

}

QWidget *CustomDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index)
    QCalendarWidget *calendar = new QCalendarWidget(parent);
    calendar->resize(200, 150);//设置弹出的日历控件初始大小
    connect(calendar, SIGNAL(clicked(QDate)), this, SLOT(when_calendarClicked(QDate)));
    qDebug() << __func__;
    return calendar;
}
/*
设置刚弹出日历时,日历上要显示的日期,一般会将其设置为对应model中的当前值
*/
void CustomDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QString dateStr = index.model()->data(index).toString();
    QCalendarWidget *calendarWidget = static_cast(editor);
    calendarWidget->setSelectedDate(QDate::fromString(dateStr, "yyyy.MM.dd"));
    qDebug() << __func__;
}
/*
设置日历控件弹出时显示的位置、以及修改日历控件的大小(如果需要修改的话)
*/
void CustomDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index)
    editor->move(option.rect.x(), option.rect.y() + option.rect.height());//把日历放到了被编辑的单元格的正下方
    //editor->move(option.rect.x(), option.rect.y());//把日历左上角与单元格左上角对齐(会遮住单元格,看起来不爽)
    //    editor->resize(50,50);//修改日历显示的大小
    qDebug() << __func__;
}

/*
把用户单击的日期,通过Model记录下来
*/
void CustomDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QCalendarWidget *calendarWidget = static_cast(editor);
    model->setData(index, QVariant(calendarWidget->selectedDate().toString("yyyy.MM.dd")));
    qDebug() << __func__;
}

/*
当日历被单击时
*/
void CustomDelegate::when_calendarClicked(const QDate &date)
{
    Q_UNUSED(date)
    QCalendarWidget *calendar =static_cast(QObject::sender());
    qDebug() << calendar->selectedDate();
    //calendar->hide();
    calendar->close();

}
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    MyTableModel *model = new MyTableModel(this);
    ui->tableView->setModel(model);

    //给第1列(出生日期)设置日历控件代理
    ui->tableView->setItemDelegateForColumn(1, new CustomDelegate(this));
}

其中,tableView->setItemDelegateForColumn 可以给某一列单元格设置代理
void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate); 可以给某一行单元格设置代理
void setItemDelegate(QAbstractItemDelegate *delegate);可以给所有单元格设置统一的代理

我们发现,QT没有提供对单独某个单元格的代理,可能是因为大家几乎都不会有这种需求。

你可能感兴趣的:(QT,QT/模型视图MVC,QT,MVC,自定义委托,delegate,模型视图)