Qt按字母自动分类的地址簿

概要

  • 主要涉及到的类有QSortFilterProxyModel、QTableView、QAbstractTableModel。
  • QAbstractTableModel提供了标准的模型API来访问数据。QTableView视图用于显示QAbstractTableModel里的数据,而QSortFilterProxyModel相当于QAbstractTableModel的代理,对QAbstractTableModel里的数据进行管理。

代码分析

main.cpp

#include "mainwindow.h"

#include <QApplication>

//! [0]
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    //创建一个QMainWindow窗口mw
    MainWindow mw;
    //显示mw窗口
    mw.show();
    return app.exec();
}

MainWindow.h

主窗口类包括一个菜单栏,一个中央小部件(addressWidget)和一些操作。文件菜单包括打开、保存和退出操作,工具菜单包括添加、编辑和删除地址操作。当选中一个条目时,就可以使用编辑和删除功能。主窗口类还包括打开和保存文件的槽函数,以及更新操作状态的槽函数。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "addresswidget.h"

#include <QMainWindow>

//! [0]
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

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

private:
    void createMenus();

    AddressWidget *addressWidget;
    QMenu *fileMenu;
    QMenu *toolMenu;
    QAction *openAct;
    QAction *saveAct;
    QAction *exitAct;
    QAction *addAct;
    QAction *editAct;
    QAction *removeAct;
};
//! [0]

#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"

#include <QAction>
#include <QFileDialog>
#include <QMenuBar>

//! [0]
MainWindow::MainWindow()
{
	//创建了一个AddressWidget对象,并将其设置为主窗口的中央部件
    addressWidget = new AddressWidget;
    setCentralWidget(addressWidget);
    //自定义的创建菜单函数
    createMenus();
    //设置窗口标题
    setWindowTitle(tr("Address Book"));
}
//! [0]

//! [1a]
void MainWindow::createMenus()
{
	//创建一个名为fileMenu的菜单,并将其添加到菜单栏中
    fileMenu = menuBar()->addMenu(tr("&File"));
	//创建一个名为openAct的动作,并将其添加到fileMenu菜单中。当该动作被触发时,将调用MainWindow类的openFile()函数
    openAct = new QAction(tr("&Open..."), this);
    fileMenu->addAction(openAct);
    connect(openAct, &QAction::triggered, this, &MainWindow::openFile);
//! [1a]
	//创建一个名为saveAct的动作,并将其添加到fileMenu菜单中。当该动作被发时,将调用MainWindow类的saveFile()函数
    saveAct = new QAction(tr("&Save As..."), this);
    fileMenu->addAction(saveAct);
    connect(saveAct, &QAction::triggered, this, &MainWindow::saveFile);
	//在fileMenu菜单中添加一个分隔线
    fileMenu->addSeparator();
	//创建一个名为exitAct的动作,并将其添加到fileMenu菜单中。当该动作被触发时,将关闭应用程序
    exitAct = new QAction(tr("E&xit"), this);
    fileMenu->addAction(exitAct);
    connect(exitAct, &QAction::triggered, this, &QWidget::close);
	//创建一个名为toolMenu的菜单,并将其添加到菜单栏中
    toolMenu = menuBar()->addMenu(tr("&Tools"));
	//创建一个名为addAct的动作,并将其添加到toolMenu菜单中。当该动作被触发时,将调用AddressWidget类的showAddEntryDialog()函数
    addAct = new QAction(tr("&Add Entry..."), this);
    toolMenu->addAction(addAct);
    connect(addAct, &QAction::triggered, addressWidget, &AddressWidget::showAddEntryDialog);

//! [1b]
	//创建一个名为editAct的动作,并将其添加到toolMenu菜单中。初始时该动作不可用。当该动作被触发时,将调用AddressWidget类的editEntry()函数
    editAct = new QAction(tr("&Edit Entry..."), this);
    editAct->setEnabled(false);
    toolMenu->addAction(editAct);
    connect(editAct, &QAction::triggered, addressWidget, &AddressWidget::editEntry);
	//在toolMenu菜单中添加一个分隔线
    toolMenu->addSeparator();
	//创建一个名为removeAct的动作,并将其添加到toolMenu菜单中。初始时该动作不可用。当该动作被触发时,将调用AddressWidget类的removeEntry()函数
    removeAct = new QAction(tr("&Remove Entry"), this);
    removeAct->setEnabled(false);
    toolMenu->addAction(removeAct);
    connect(removeAct, &QAction::triggered, addressWidget, &AddressWidget::removeEntry);
	//将AddressWidget类的selectionChanged信号连接到MainWindow类的updateActions()槽函数。当AddressWidget类的选择的item发生变化时,将更新动作的可用性状态
    connect(addressWidget, &AddressWidget::selectionChanged,
        this, &MainWindow::updateActions);
}
//! [1b]

//! [2]
void MainWindow::openFile()
{
	//使用QFileDialog类打开一个文件对话框,以便选择要打开的文件
    QString fileName = QFileDialog::getOpenFileName(this);
    if (!fileName.isEmpty())
    	//传递给AddressWidget类的readFromFile函数,以便从文件中读取地址簿数据
        addressWidget->readFromFile(fileName);
}
//! [2]

//! [3]
void MainWindow::saveFile()
{
	//使用QFileDialog类打开一个文件对话框,以便选择要保存的文件
    QString fileName = QFileDialog::getSaveFileName(this);
    if (!fileName.isEmpty())
    	//传递给AddressWidget类的writeToFile函数,以便将地址簿数据写入文件中
        addressWidget->writeToFile(fileName);
}
//! [3]

//! [4]
//接受一个QItemSelection对象作为参数
void MainWindow::updateActions(const QItemSelection &selection)
{
    QModelIndexList indexes = selection.indexes();
	//根据选择的项更新编辑和删除操作的状态。如果至少选择了一个item,这些操作将会被启用。否则,它们将会被禁用
    if (!indexes.isEmpty()) {
        removeAct->setEnabled(true);
        editAct->setEnabled(true);
    } else {
        removeAct->setEnabled(false);
        editAct->setEnabled(false);
    }
}
//! [4]

addresswidget.h

AddressWidget的类,它继承自QTabWidget类。AddressWidget类包含了一些用于管理地址簿数据的函数

#ifndef ADDRESSWIDGET_H
#define ADDRESSWIDGET_H

#include "newaddresstab.h"
#include "tablemodel.h"

#include <QItemSelection>
#include <QTabWidget>

QT_BEGIN_NAMESPACE
class QSortFilterProxyModel;
class QItemSelectionModel;
QT_END_NAMESPACE

//! [0]
class AddressWidget : public QTabWidget
{
    Q_OBJECT

public:
    AddressWidget(QWidget *parent = 0);
    void readFromFile(const QString &fileName);
    void writeToFile(const QString &fileName);

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

signals:
    void selectionChanged (const QItemSelection &selected);

private:
    void setupTabs();

    TableModel *table;
    NewAddressTab *newAddressTab;
    QSortFilterProxyModel *proxyModel;
};

addresswidget.cpp

#include "adddialog.h"
#include "addresswidget.h"

#include <QtWidgets>

//! [0]
AddressWidget::AddressWidget(QWidget *parent)
    : QTabWidget(parent)
{
	//只定义了一个名为table的tablemodel,多个tableview共用一个tablemodel,只是tableview只显示想显示的内容
    table = new TableModel(this);
    newAddressTab = new NewAddressTab(this);
    connect(newAddressTab, &NewAddressTab::sendDetails,
        this, &AddressWidget::addEntry);
	//将newAddressTab添加到AddressWidget对象中
    addTab(newAddressTab, "Address Book");
	//调用setupTabs函数来设置地址簿的标签页
    setupTabs();
}
//! [0]

//! [2]
void AddressWidget::showAddEntryDialog()
{
    AddDialog aDialog;
	//点击“OK”按钮时调用addEntry函数
    if (aDialog.exec()) {
        QString name = aDialog.nameText->text();
        QString address = aDialog.addressText->toPlainText();

        addEntry(name, address);
    }
}
//! [2]

//! [3]
//添加表格数据
void AddressWidget::addEntry(QString name, QString address)
{
	//首先检查联系人信息是否已经存在
    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));
    }
}
//! [3]

//! [4a]
//编辑表格数据
void AddressWidget::editEntry()
{
	//获取当前活动的tab页,并将其强制转为QTableView类型的指针
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    //获取temp的model,并将其强制转换为QSortFilterProxyModel类型的指针proxy,因为tableview直接setModel的是QSortFilterProxyModel,它是一个代理模型,用于对原始数据模型进行排序、过滤等操作
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    //获取当前QTableView的选择模型,并将其存储在QItemSelectionModel类型的指针中。选择模型用于跟踪表格中的选择
    QItemSelectionModel *selectionModel = temp->selectionModel();
	//获取当前选择的行的索引
    QModelIndexList indexes = selectionModel->selectedRows();
    QString name;
    QString address;
    int row = -1;
	//遍历选择的行,并从模型中获取行号、姓名和地址
    foreach (QModelIndex index, indexes) {
    	//表格使用了QSortFilterProxyModel,则使用proxy->mapToSource获取当前选中的联系人信息的行号,这里需要把index从proxy转到对应的源table的index
        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();
    }
//! [4a]

//! [4b]
    AddDialog aDialog;
    aDialog.setWindowTitle(tr("Edit a Contact"));

    aDialog.nameText->setReadOnly(true);
    aDialog.nameText->setText(name);
    aDialog.addressText->setText(address);
	//显示`aDialog`并等待用户完成编辑。如果用户单击了对话框上的“OK”按钮,则获取`addressText`的当前文本,并将其与`address`的值进行比较。如果它们不相等,则将新的地址数据设置到表格中。否则,不做任何事情
    if (aDialog.exec()) {
        QString newAddress = aDialog.addressText->toPlainText();
        if (newAddress != address) {
            QModelIndex index = table->index(row, 1, QModelIndex());
            table->setData(index, newAddress, Qt::EditRole);
        }
    }
}
//! [4b]

//! [5]
//移除表格数据
void AddressWidget::removeEntry()
{
	//获取当前的 QTableView 对象,即当前的地址簿表格视图
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    QItemSelectionModel *selectionModel = temp->selectionModel();
	//获取当前选择的items的索引列表
    QModelIndexList indexes = selectionModel->selectedRows();
	//遍历索引列表,获取每个条目的原始行号,并在数据模型中删除对应的行
    foreach (QModelIndex index, indexes) {
    	//这里需要把index从proxy转到对应的源table的index
        int row = proxy->mapToSource(index).row();
        table->removeRows(row, 1, QModelIndex());
    }

    if (table->rowCount(QModelIndex()) == 0) {
        insertTab(0, newAddressTab, "Address Book");
    }
}
//! [5]

//! [1]
//初始化表格
void AddressWidget::setupTabs()
{
    QStringList groups;
    groups << "ABC" << "DEF" << "GHI" << "JKL" << "MNO" << "PQR" << "STU" << "VW" << "XYZ";

    for (int i = 0; i < groups.size(); ++i) {
        QString str = groups.at(i);
        QString regExp = QString("^[%1].*").arg(str);
		//创建一个 QSortFilterProxyModel 对象,用于过滤和排序地址簿数据
        proxyModel = new QSortFilterProxyModel(this);
        //设置过滤的model为table
        proxyModel->setSourceModel(table);
        //用正则来过滤model的数据
        proxyModel->setFilterRegExp(QRegExp(regExp, Qt::CaseInsensitive));
        proxyModel->setFilterKeyColumn(0);

        QTableView *tableView = new QTableView;
        tableView->setModel(proxyModel);
		//设置 QTableView 的选择行为、水平表头、垂直表头、编辑触发方式、选择模式和排序方式
        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](int tabIndex) {
            auto *tableView = qobject_cast<QTableView *>(widget(tabIndex));
            if (tableView)
                emit selectionChanged(tableView->selectionModel()->selection());
        });
		//当前类是一个QTabWidget,把一个个tableView作为tab页添加
        addTab(tableView, str);
    }
}
//! [1]

//! [7]
//从指定的文件中读取联系人信息,并将其添加到AddressWidget中
void AddressWidget::readFromFile(const QString &fileName)
{
    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: qAsConst(contacts))
            addEntry(contact.name, contact.address);
    }
}
//! [7]

//! [6]
//将通讯录中的联系人信息写入到文件中
void AddressWidget::writeToFile(const QString &fileName)
{
    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();
}

newaddresstab.h

用于添加联系人姓名和地址的临时界面,添加成功后便从addresswidget中移除这个tab页
在这里插入图片描述

#ifndef NEWADDRESSTAB_H
#define NEWADDRESSTAB_H

#include <QWidget>

QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
class QVBoxLayout;
QT_END_NAMESPACE

//! [0]
class NewAddressTab : public QWidget
{
    Q_OBJECT

public:
    NewAddressTab(QWidget *parent = 0);

public slots:
    void addEntry();

signals:
    void sendDetails(QString name, QString address);

private:
    QLabel *descriptionLabel;
    QPushButton *addButton;
    QVBoxLayout *mainLayout;

};
//! [0]

#endif

newaddresstab.cpp

#include "adddialog.h"
#include "newaddresstab.h"

#include <QtWidgets>

//! [0]
NewAddressTab::NewAddressTab(QWidget *parent)
{
    Q_UNUSED(parent);

    descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. "
                                      "\nClick Add to add new contacts."));

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

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

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

    setLayout(mainLayout);
}
//! [0]

//! [1]
//从对话框中获取联系人的姓名和地址,并通过`sendDetails()`信号将其发送出去
void NewAddressTab::addEntry()
{
    AddDialog aDialog;

    if (aDialog.exec()) {
        QString name = aDialog.nameText->text();
        QString address = aDialog.addressText->toPlainText();

        emit sendDetails(name, address);
    }
}

adddialog.h

添加联系人时弹出的对话框
在这里插入图片描述

#ifndef ADDDIALOG_H
#define ADDDIALOG_H

#include <QDialog>

QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
class QTextEdit;
class QLineEdit;
QT_END_NAMESPACE

//! [0]
class AddDialog : public QDialog
{
    Q_OBJECT

public:
    AddDialog(QWidget *parent = 0);
    QLineEdit *nameText;
    QTextEdit *addressText;

private:
    QLabel *nameLabel;
    QLabel *addressLabel;
    QPushButton *okButton;
    QPushButton *cancelButton;
};
//! [0]

#endif // ADDDIALOG_H

adddialog.cpp

#include "adddialog.h"

#include <QtWidgets>

//! [0]
AddDialog::AddDialog(QWidget *parent)
    : QDialog(parent)
{
    nameLabel = new QLabel("Name");
    addressLabel = new QLabel("Address");
    okButton = new QPushButton("OK");
    cancelButton = new QPushButton("Cancel");

    nameText = new QLineEdit;
    addressText = new QTextEdit;

    QGridLayout *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);

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

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

    QVBoxLayout *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"));
}

tablemodel.h

#ifndef TABLEMODEL_H
#define TABLEMODEL_H

#include <QAbstractTableModel>
#include <QList>

//! [0]
//tablemodel的基本数据结构,比较简单
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;
}
//tablemodel继承QAbstractTableModel并重写许多函数,数据只有一个QList<Contact> contacts
class TableModel : public QAbstractTableModel
{
    Q_OBJECT

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

    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;
    QList<Contact> getContacts() const;

private:
    QList<Contact> contacts;
};
//! [0]

#endif // TABLEMODEL_H

tablemodel.cpp

#include "tablemodel.h"

//! [0]
TableModel::TableModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

TableModel::TableModel(QList<Contact> contacts, QObject *parent)
    : QAbstractTableModel(parent)
    , contacts(contacts)
{
}
//! [0]

//! [1]
//返回数据的大小
int TableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return contacts.size();
}
//返回列数
int TableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return 2;
}
//! [1]

//! [2]
//返回各类数据
QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= contacts.size() || index.row() < 0)
        return QVariant();
	//Qt::DisplayRole一般返回界面显示的内容
    if (role == Qt::DisplayRole) {
        const auto &contact = contacts.at(index.row());

        if (index.column() == 0)
            return contact.name;
        else if (index.column() == 1)
            return contact.address;
    }
    return QVariant();
}
//! [2]

//! [3]
//返回表格模型的头数据
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:
                return QVariant();
        }
    }
    return QVariant();
}
//! [3]

//! [4]
//插入数据
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;
}
//! [4]

//! [5]
//移除数据
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;
}
//! [5]

//! [6]
//设置各角色的数据
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {
        int row = index.row();

        auto contact = contacts.value(row);

        if (index.column() == 0)
            contact.name = value.toString();
        else if (index.column() == 1)
            contact.address = value.toString();
        else
            return false;

        contacts.replace(row, contact);
        emit dataChanged(index, index, {role});

        return true;
    }

    return false;
}
//! [6]

//! [7]
//返回tablemodel的一些属性
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
//! [7]

//! [8]
QList<Contact> TableModel::getContacts() const
{
    return contacts;
}
//! [8]

总结


AddressWidget类是一个QTabWidget子类,用于操作示例中显示的10个选项卡:9个字母组选项卡和NewAddressTab的一个实例。NewAddressTab类是QWidget的一个子类,它只在地址簿为空时使用,提示用户添加一些联系人。AddressWidget还与TableModel的实例交互,向地址簿中添加、编辑和删除条目。
TableModel是QAbstractTableModel的子类,它提供了标准的模型/视图API来访问数据。它包含已添加的联系人列表。然而,这些数据并不都在一个选项卡中可见。相反,QTableView用于根据字母组提供相同数据的9个不同视图。
QSortFilterProxyModel是负责为每组联系人筛选联系人的类。每个代理模型都使用QRegExp来过滤不属于对应字母组的联系人。AddDialog类用于从用户那里获取地址簿的信息。这个QDialog子类由NewAddressTab实例化以添加联系人,由addresswget实例化以添加和编辑联系人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值