地址簿

地址簿示例展示了如何使用代理模型来显示单个模型数据的不同视图。

地址簿示例截图

该示例提供了一个地址簿,可将联系人按字母顺序分为 9 组:ABC、DEF、GHI、...VW、......、xyz。这是通过在同一模型上使用多个视图来实现的,每个视图都使用QSortFilterProxyModel 类的实例进行过滤。

概述

地址簿包含 5 个类:MainWindow,AddressWidget,TableModel,NewAddressTabAddDialogMainWindow 类使用AddressWidget 作为中心部件,并提供FileTools 菜单。

地址簿示例图

AddressWidget 类是QTabWidget 的子类,用于操作示例中显示的 10 个标签:9 个字母组标签和NewAddressTab 的实例。NewAddressTab 类是QWidget 的子类,仅在地址簿为空时使用,提示用户添加联系人。AddressWidget 还与TableModel 的实例交互,以添加、编辑和删除地址簿条目。

TableModel 是 的子类,提供标准的模型/视图 API 来访问数据。它保存着已添加联系人的列表。不过,这些数据并不是在一个标签页中全部可见。相反, 可根据字母组提供相同数据的 9 种不同视图。QAbstractTableModel QTableView

QSortFilterProxyModel 是负责为每组联系人筛选联系人的类。每个代理模型都使用 来过滤不属于相应字母组的联系人。 类用于从用户处获取通讯录信息。 会实例化 子类以添加联系人, 会实例化 子类以添加和编辑联系人。QRegularExpression AddDialog NewAddressTab QDialog AddressWidget

我们首先来看看TableModel 的实现。

TableModel 类定义

TableModel 类通过子类QAbstractTableModel 为访问联系人列表中的数据提供了标准 API。为此必须实现的基本功能有rowCount(),columnCount(),data(),headerData() 。要编辑 TableModel,必须实现insertRows(),removeRows(),setData()flags() 函数。

struct Contact
{
    QString name;
    QString address;

    bool operator==(const Contact &other) const
    {
        return name == other.name && address == other.address;
    }
};

inline QDataStream &operator<<(QDataStream &stream, const Contact &contact)
{
    return stream << contact.name << contact.address;
}

inline QDataStream &operator>>(QDataStream &stream, Contact &contact)
{
    return stream >> contact.name >> contact.address;
}

class TableModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    TableModel(QObject *parent = nullptr);
    TableModel(const QList<Contact> &contacts, QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent) const override;
    int columnCount(const QModelIndex &parent) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    const QList<Contact> &getContacts() const;

private:
    QList<Contact> contacts;
};

为方便起见,使用了两个构造函数,一个是使用TableModel 自己的QList<Contact> 的默认构造函数,另一个是将QList<Contact> 作为参数的构造函数。

TableModel 类的实现

我们按照头文件中的定义实现了两个构造函数。第二个构造函数使用参数值初始化模型中的联系人列表。

TableModel::TableModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

TableModel::TableModel(const QList<Contact> &contacts, QObject *parent)
    : QAbstractTableModel(parent), contacts(contacts)
{
}

rowCount()columnCount() 函数将返回模型的尺寸。rowCount() 的值会根据添加到通讯录中的联系人数量而变化,而columnCount() 的值始终为 2,因为我们只需要姓名地址列的空间。

int TableModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : contacts.size();
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 2;
}

data() 函数根据提供的模型索引内容返回姓名地址。存储在模型索引中的行号用于引用联系人列表中的项目。选择由QItemSelectionModel 处理,将通过AddressWidget 进行解释。

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= contacts.size() || index.row() < 0)
        return QVariant();

    if (role == Qt::DisplayRole) {
        const auto &contact = contacts.at(index.row());

        switch (index.column()) {
            case 0:
                return contact.name;
            case 1:
                return contact.address;
            default:
                break;
        }
    }
    return QVariant();
}

headerData() 功能显示表头、姓名地址。如果您需要为通讯簿中的条目编号,可以使用垂直标题,我们在本例中隐藏了垂直标题(请参阅AddressWidget 实现)。

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal) {
        switch (section) {
            case 0:
                return tr("Name");
            case 1:
                return tr("Address");
            default:
                break;
        }
    }
    return QVariant();
}

在添加新数据之前要调用insertRows() 函数,否则数据将不会显示。调用beginInsertRows()endInsertRows() 函数是为了确保所有连接的视图都知道这些更改。

bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
    Q_UNUSED(index);
    beginInsertRows(QModelIndex(), position, position + rows - 1);

    for (int row = 0; row < rows; ++row)
        contacts.insert(position, { QString(), QString() });

    endInsertRows();
    return true;
}

调用removeRows() 函数是为了删除数据。同样,beginRemoveRows() 和endRemoveRows() 会被调用,以确保所有连接的视图都知道这些更改。

bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
    Q_UNUSED(index);
    beginRemoveRows(QModelIndex(), position, position + rows - 1);

    for (int row = 0; row < rows; ++row)
        contacts.removeAt(position);

    endRemoveRows();
    return true;
}

setData() 函数是向表中逐项而不是逐行插入数据的函数。这意味着,要填充地址簿中的一行,必须调用setData() 两次,因为每一行有两列。发出dataChanged() 信号非常重要,因为它会通知所有连接的视图更新其显示内容。

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {
        const int row = index.row();
        auto contact = contacts.value(row);

        switch (index.column()) {
            case 0:
                contact.name = value.toString();
                break;
            case 1:
                contact.address = value.toString();
                break;
            default:
                return false;
        }
        contacts.replace(row, contact);
        emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});

        return true;
    }

    return false;
}

flags() 函数返回给定索引的项目标志。

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}

我们设置Qt::ItemIsEditable 标志是因为我们希望允许对TableModel 进行编辑。虽然在本例中我们并没有使用QTableView 对象的编辑功能,但我们在此启用了这些功能,以便在其他程序中重复使用该模型。

TableModel 中的最后一个函数getContacts() 返回QList<Contact> 对象,该对象包含地址簿中的所有联系人。我们稍后将使用该函数获取联系人列表,以检查是否存在条目,将联系人写入文件并读取回来。更多解释请参见AddressWidget

const QList<Contact> &TableModel::getContacts() const
{
    return contacts;
}

地址部件类定义

AddressWidget 类是本示例中的主要类,因为它提供了添加、编辑和删除联系人、将联系人保存到文件以及从文件中加载联系人的功能。

class AddressWidget : public QTabWidget
{
    Q_OBJECT

public:
    AddressWidget(QWidget *parent = nullptr);
    void readFromFile();
    void writeToFile();

public slots:
    void showAddEntryDialog();
    void addEntry(const QString &name, const QString &address);
    void editEntry();
    void removeEntry();

signals:
    void selectionChanged (const QItemSelection &selected);

private:
    void setupTabs();

    inline static QString fileName =
        QStandardPaths::standardLocations(QStandardPaths::TempLocation).value(0)
        + QStringLiteral("/addressbook.dat");
    TableModel *table;
    NewAddressTab *newAddressTab;
};

AddressWidget 该类扩展了 ,以容纳 10 个标签页( 和 9 个字母组标签页),并操作 、 对象、 、用于过滤条目的 对象和 、 对象。QTabWidgetNewAddressTab table TableModel proxyModel QSortFilterProxyModel tableView QTableView

地址部件类的实现

AddressWidget 构造函数接受一个父部件,并将NewAddressTabTableModelQSortFilterProxyModel 实例化。NewAddressTab 对象用于表示地址簿是空的,它被添加进来,其余 9 个选项卡通过setupTabs() 设置。

AddressWidget::AddressWidget(QWidget *parent)
    : QTabWidget(parent),
      table(new TableModel(this)),
      newAddressTab(new NewAddressTab(this))
{
    connect(newAddressTab, &NewAddressTab::sendDetails,
        this, &AddressWidget::addEntry);

    addTab(newAddressTab, tr("Address Book"));

    setupTabs();
}

setupTabs() 函数用于在AddressWidget 中设置 9 个字母组标签、表格视图和代理模型。每个代理模型依次设置为使用不区分大小写的QRegularExpression 对象,根据相关字母组过滤联系人姓名。表格视图也使用相应代理模型的sort() 函数按升序排序。

每个表视图的selectionMode 设置为QAbstractItemView::SingleSelectionselectionBehavior 设置为QAbstractItemView::SelectRows ,允许用户同时选择一行中的所有项目。每个QTableView 对象都会自动获得一个QItemSelectionModel ,用于跟踪所选索引。

void AddressWidget::setupTabs()
{
    using namespace Qt::StringLiterals;
    const auto groups = { "ABC"_L1, "DEF"_L1, "GHI"_L1, "JKL"_L1, "MNO"_L1, "PQR"_L1,
                          "STU"_L1, "VW"_L1, "XYZ"_L1 };

    for (QLatin1StringView str : groups) {
        const auto regExp = QRegularExpression(QLatin1StringView("^[%1].*").arg(str),
                                               QRegularExpression::CaseInsensitiveOption);

        auto proxyModel = new QSortFilterProxyModel(this);
        proxyModel->setSourceModel(table);
        proxyModel->setFilterRegularExpression(regExp);
        proxyModel->setFilterKeyColumn(0);

        QTableView *tableView = new QTableView;
        tableView->setModel(proxyModel);
        tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
        tableView->horizontalHeader()->setStretchLastSection(true);
        tableView->verticalHeader()->hide();
        tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
        tableView->setSelectionMode(QAbstractItemView::SingleSelection);
        tableView->setSortingEnabled(true);

        connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &AddressWidget::selectionChanged);

        connect(this, &QTabWidget::currentChanged, this, [this, tableView](int tabIndex) {
            if (widget(tabIndex) == tableView)
                emit selectionChanged(tableView->selectionModel()->selection());
        });

        addTab(tableView, str);
    }
}

QItemSelectionModel 类提供了一个selectionChanged 信号,该信号与AddressWidgetselectionChanged() 信号相连。我们还将QTabWidget::currentChanged() 信号连接到 lambda 表达式,该表达式也会发出AddressWidgetselectionChanged() 。这些连接对于在MainWindow 的 "工具 "菜单中启用Edit Entry...Remove Entry 操作十分必要。在MainWindow 的实现中会有进一步解释。

地址簿中的每个表格视图都会作为一个标签页添加到QTabWidget 中,并带有从QStringList 组中获取的相关标签。

信号和插槽连接

我们提供了两个addEntry() 函数:一个用于接受用户输入,另一个用于执行向地址簿添加新条目这一实际任务。我们将添加条目的工作分为两部分,以便newAddressTab 无需弹出对话框即可插入数据。

第一个addEntry() 函数是与MainWindowAdd Entry... 操作相连的槽。该函数创建一个AddDialog 对象,然后调用第二个addEntry() 函数将联系人实际添加到table 中。

void AddressWidget::showAddEntryDialog()
{
    AddDialog aDialog;

    if (aDialog.exec())
        addEntry(aDialog.name(), aDialog.address());
}

第二个addEntry() 函数进行基本验证,以防止地址簿中出现重复条目。如TableModel 所述,这也是我们需要获取方法getContacts() 的部分原因。

void AddressWidget::addEntry(const QString &name, const QString &address)
{
    if (!name.front().isLetter()) {
        QMessageBox::information(this, tr("Invalid name"),
            tr("The name must start with a letter."));
    } else if (!table->getContacts().contains({ name, address })) {
        table->insertRows(0, 1, QModelIndex());

        QModelIndex index = table->index(0, 0, QModelIndex());
        table->setData(index, name, Qt::EditRole);
        index = table->index(0, 1, QModelIndex());
        table->setData(index, address, Qt::EditRole);
        removeTab(indexOf(newAddressTab));
    } else {
        QMessageBox::information(this, tr("Duplicate Name"),
            tr("The name \"%1\" already exists.").arg(name));
    }
}

如果模型中尚未包含同名条目,我们就会调用setData() 将姓名和地址插入第一列和第二列。否则,我们将显示QMessageBox 通知用户。

注意: 一旦添加了联系人,newAddressTab 就会删除,因为地址簿不再是空的。

编辑条目只能更新联系人的地址,因为本示例不允许用户更改现有联系人的姓名。

首先,我们使用QTabWidget::currentWidget() 获取活动标签页的QTableView 对象。然后,我们从tableView 中提取selectionModel ,以获得所选索引。

void AddressWidget::editEntry()
{
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    QItemSelectionModel *selectionModel = temp->selectionModel();

    const QModelIndexList indexes = selectionModel->selectedRows();
    QString name;
    QString address;
    int row = -1;

    for (const QModelIndex &index : indexes) {
        row = proxy->mapToSource(index).row();
        QModelIndex nameIndex = table->index(row, 0, QModelIndex());
        QVariant varName = table->data(nameIndex, Qt::DisplayRole);
        name = varName.toString();

        QModelIndex addressIndex = table->index(row, 1, QModelIndex());
        QVariant varAddr = table->data(addressIndex, Qt::DisplayRole);
        address = varAddr.toString();
    }

接下来,我们从用户打算编辑的行中提取数据。这些数据将以不同的窗口标题显示在AddDialog 的实例中。只有当aDialog 中的数据发生变化时,才会更新table

    AddDialog aDialog;
    aDialog.setWindowTitle(tr("Edit a Contact"));
    aDialog.editAddress(name, address);

    if (aDialog.exec()) {
        const QString newAddress = aDialog.address();
        if (newAddress != address) {
            const QModelIndex index = table->index(row, 1, QModelIndex());
            table->setData(index, newAddress, Qt::EditRole);
        }
    }
}

编辑联系人对话框截图

使用removeEntry() 功能删除条目。通过QItemSelectionModel 对象(selectionModel )访问选定行,即可删除该行。只有当用户删除地址簿中的所有联系人时,才会将newAddressTab 重新添加到AddressWidget 中。

void AddressWidget::removeEntry()
{
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    QItemSelectionModel *selectionModel = temp->selectionModel();

    const QModelIndexList indexes = selectionModel->selectedRows();

    for (QModelIndex index : indexes) {
        int row = proxy->mapToSource(index).row();
        table->removeRows(row, 1, QModelIndex());
    }

    if (table->rowCount(QModelIndex()) == 0)
        insertTab(0, newAddressTab, tr("Address Book"));
}

writeToFile() 功能用于保存包含地址簿中所有联系人的文件。文件以自定义.dat 格式保存。联系人列表的内容将通过QDataStream 写入file 。如果文件无法打开,则会显示QMessageBox 以及相关的错误信息。

void AddressWidget::writeToFile()
{
    QFile file(fileName);

    if (!file.open(QIODevice::WriteOnly)) {
        QMessageBox::information(this, tr("Unable to open file"), file.errorString());
        return;
    }

    QDataStream out(&file);
    out << table->getContacts();
}

readFromFile() 函数加载一个包含地址簿中所有联系人的文件,该文件先前使用writeToFile() 保存。QDataStream 用于将.dat 文件的内容读入联系人列表,然后使用addEntry() 添加每个联系人。

void AddressWidget::readFromFile()
{
    QFile file(fileName);

    if (!file.open(QIODevice::ReadOnly)) {
        QMessageBox::information(this, tr("Unable to open file"),
            file.errorString());
        return;
    }

    QList<Contact> contacts;
    QDataStream in(&file);
    in >> contacts;

    if (contacts.isEmpty()) {
        QMessageBox::information(this, tr("No contacts in file"),
                                 tr("The file you are attempting to open contains no contacts."));
    } else {
        for (const auto &contact: std::as_const(contacts))
            addEntry(contact.name, contact.address);
    }
}

NewAddressTab 类定义

NewAddressTab 类提供了一个信息标签,告诉用户地址簿是空的。正如AddressWidget 的实现中所提到的,它根据地址簿的内容出现和消失。

NewAddressTab 的屏幕截图

NewAddressTab 类扩展了QWidget ,并包含QLabelQPushButton

class NewAddressTab : public QWidget
{
    Q_OBJECT

public:
    NewAddressTab(QWidget *parent = nullptr);

public slots:
    void addEntry();

signals:
    void sendDetails(const QString &name, const QString &address);
};

NewAddressTab 类的实现

构造函数将addButtondescriptionLabel 实例化,并将addButton 的信号连接到addEntry() 槽。

NewAddressTab::NewAddressTab(QWidget *parent)
    : QWidget(parent)
{
    auto descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. "
                                          "\nClick Add to add new contacts."));

    auto addButton = new QPushButton(tr("Add"));

    connect(addButton, &QAbstractButton::clicked, this, &NewAddressTab::addEntry);

    auto mainLayout = new QVBoxLayout;
    mainLayout->addWidget(descriptionLabel);
    mainLayout->addWidget(addButton, 0, Qt::AlignCenter);

    setLayout(mainLayout);
}

addEntry() 函数与AddressWidgetaddEntry() 函数类似,都是实例化一个AddDialog 对象。通过发出sendDetails() 信号,从对话框中提取数据并发送到AddressWidgetaddEntry() 插槽。

void NewAddressTab::addEntry()
{
    AddDialog aDialog;

    if (aDialog.exec())
        emit sendDetails(aDialog.name(), aDialog.address());
}

AddDialog 类定义

AddDialog 类扩展了QDialog ,为用户提供了QLineEditQTextEdit ,用于向地址簿输入数据。

class AddDialog : public QDialog
{
    Q_OBJECT

public:
    AddDialog(QWidget *parent = nullptr);

    QString name() const;
    QString address() const;
    void editAddress(const QString &name, const QString &address);

private:
    QLineEdit *nameText;
    QTextEdit *addressText;
};

AddDialog 类的实现

AddDialog 的构造函数会设置用户界面,创建必要的部件并将它们放入布局中。

AddDialog::AddDialog(QWidget *parent)
    : QDialog(parent),
      nameText(new QLineEdit),
      addressText(new QTextEdit)
{
    auto nameLabel = new QLabel(tr("Name"));
    auto addressLabel = new QLabel(tr("Address"));
    auto okButton = new QPushButton(tr("OK"));
    auto cancelButton = new QPushButton(tr("Cancel"));

    auto gLayout = new QGridLayout;
    gLayout->setColumnStretch(1, 2);
    gLayout->addWidget(nameLabel, 0, 0);
    gLayout->addWidget(nameText, 0, 1);

    gLayout->addWidget(addressLabel, 1, 0, Qt::AlignLeft|Qt::AlignTop);
    gLayout->addWidget(addressText, 1, 1, Qt::AlignLeft);

    auto buttonLayout = new QHBoxLayout;
    buttonLayout->addWidget(okButton);
    buttonLayout->addWidget(cancelButton);

    gLayout->addLayout(buttonLayout, 2, 1, Qt::AlignRight);

    auto mainLayout = new QVBoxLayout;
    mainLayout->addLayout(gLayout);
    setLayout(mainLayout);

    connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept);
    connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject);

    setWindowTitle(tr("Add a Contact"));
}

QString AddDialog::name() const
{
    return nameText->text();
}

QString AddDialog::address() const
{
    return addressText->toPlainText();
}

void AddDialog::editAddress(const QString &name, const QString &address)
{
    nameText->setReadOnly(true);
    nameText->setText(name);
    addressText->setPlainText(address);
}

为了使对话框具有所需的行为,我们将OKCancel 按钮连接到对话框的accept() 和reject() 插槽。由于对话框仅作为姓名和地址信息的容器,因此我们无需为其实现任何其他功能。

MainWindow 类定义

MainWindow 类扩展了QMainWindow ,并实现了操作地址簿所需的菜单和操作。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

private slots:
    void updateActions(const QItemSelection &selection);
    void openFile();
    void saveFile();

private:
    void createMenus();

    AddressWidget *addressWidget;
    QAction *editAct;
    QAction *removeAct;
};

MainWindow 类使用AddressWidget 作为中心部件,并提供带有OpenCloseExit 操作的文件菜单,以及带有Add Entry...Edit Entry...Remove Entry 操作的Tools 菜单。

主窗口类的实现

MainWindow 的构造函数实例化 AddressWidget,将其设置为中心部件,并调用createMenus() 函数。

MainWindow::MainWindow()
    : QMainWindow(),
      addressWidget(new AddressWidget)
{
    setCentralWidget(addressWidget);
    createMenus();
    setWindowTitle(tr("Address Book"));
}

createMenus() 函数设置了FileTools 菜单,将操作连接到各自的插槽。Edit Entry...Remove Entry 操作默认为禁用,因为这些操作无法在空地址簿中执行。只有添加了一个或多个联系人后,才会启用这些操作。

void MainWindow::createMenus()
{
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));

    QAction *openAct = new QAction(tr("&Open..."), this);
    fileMenu->addAction(openAct);
    connect(openAct, &QAction::triggered, this, &MainWindow::openFile);
    ...

    editAct = new QAction(tr("&Edit Entry..."), this);
    editAct->setEnabled(false);
    toolMenu->addAction(editAct);
    connect(editAct, &QAction::triggered, addressWidget, &AddressWidget::editEntry);

    toolMenu->addSeparator();

    removeAct = new QAction(tr("&Remove Entry"), this);
    removeAct->setEnabled(false);
    toolMenu->addAction(removeAct);
    connect(removeAct, &QAction::triggered, addressWidget, &AddressWidget::removeEntry);

    connect(addressWidget, &AddressWidget::selectionChanged,
        this, &MainWindow::updateActions);
}

除了将所有操作的信号连接到各自的插槽外,我们还将AddressWidgetselectionChanged() 信号连接到updateActions() 插槽。

openFile() 函数打开一个包含地址簿联系人的自定义addressbook.dat 文件。该函数是File 菜单中与openAct 相连的插槽。

void MainWindow::openFile()
{
    addressWidget->readFromFile();
}

saveFile() 功能将保存一个包含地址簿联系人的自定义addressbook.dat 文件。该功能是与File 菜单中的saveAct 相连的插槽。

void MainWindow::saveFile()
{
    addressWidget->writeToFile();
}

updateActions() 功能根据地址簿的内容启用或禁用Edit Entry...Remove Entry 。如果地址簿为空,则禁用这些操作;反之,则启用。该函数的插槽与AddressWidgetselectionChanged() 信号相连。

void MainWindow::updateActions(const QItemSelection &selection)
{
    QModelIndexList indexes = selection.indexes();

    if (!indexes.isEmpty()) {
        removeAct->setEnabled(true);
        editAct->setEnabled(true);
    } else {
        removeAct->setEnabled(false);
        editAct->setEnabled(false);
    }
}

main() 函数

地址簿的主函数实例化QApplication 并在运行事件循环之前打开MainWindow

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow mw;
    mw.show();
    return app.exec();
}

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.