在桌面软件的UI界面开发中,经常会用到下拉框ComboBox控件,用来选择多个选项中的一项,程序员在编码的时候,经常这样做:
ui->comboBox->addItems(QStringList()<<tr("Text文件")<<tr("Word文件")<<tr("Excel表格")<<tr("二进制文件")<<tr("PDF文件"));
然后在需要响应用户操作的函数中通过comboBox的当前索引或当前文本内容判断应该执行何种操作:
if (ui->comboBox->currentIndex() == 0) // if (ui->comboBox->currentText() == tr("Text文件"))
{
// 执行某种操作(Text文件)
}
// ...
这是一种明显的硬编码方式,如果想要修改某个文本项的名称或者调整文本项的顺序,都需要修改不止一个地方。
因此,通常我们会创建一个类型值(枚举值或其它常量类型,这个类型或许本就是已经存在的),然后建立类型值与文本项之间的映射关系。
QMap<int, QString> m_valueNameMap;
然后前面的代码可以写成:
m_valueNameMap[FileType::Text] = tr("Text文件");
m_valueNameMap[FileType::Word] = tr("Word文件");
m_valueNameMap[FileType::Excel] = tr("Excel表格");
m_valueNameMap[FileType::Binary] = tr("二进制文件");
m_valueNameMap[FileType::PDF] = tr("Word文件");
ui->comboBox->addItems(m_valueNameMap.values());
于是响应函数的代码我们可以写成这样了:
if (m_valueNameMap.key(ui->comboBox->currentText()) == FileType::Text)
{
// 执行某种操作(Text文件)
}
// ...
现在,我们消除了硬编码,如果要修改某个文本项的名称,我们只需要修改一处。
但是,由于QMap是自动排序的,所以下拉框的文本项的顺序是根据类型FileType从小到大排序的。如果我们想调整文本项的顺序,例如,把二进制文件放到第一个,恐怕就得去修改这个FileType的定义。问题是,有些情况下,这个类型定义可能是第三方库或者底层库的定义,不方便或不能修改。怎么办?
我们可以用QList来代替QMap达到这个目的。当然,这样的话我们得定义一个类型:
struct ValueName
{
int enumValue;
QString uiName;
};
于是
QList<ValueName> m_valueNameMap;
这里的变量名称依然用m_valueNameMap问题到不大,反正我觉得m_valueNames并不贴切。由于QList不具备查询能力,所以每次得到文本项对应的类型值时需要遍历m_valueNameMap,这就需要新增加一个函数:
int enumValue(const QString &uiname) const
{
foreach (const ValueName &var, m_valueNameMap)
{
if (var.uiName == uiname)
{
return var.enumValue;
}
}
qWarning()<<"error! ui-name not exist! ui-name="<<uiname;
return -9999;
}
有时候可能还会需要根据类型查找文本项的函数。我们看到这些函数只用到了m_valueNameMap一个变量。且这些函数的增多会导致m_valueNameMap所在的类越来越大。于是我们可以利用“封装基本数据类型”的手法将其重构至一个单独的类QEnumValueNameMapper。
#ifndef QENUMVALUENAMEMAPPER_H
#define QENUMVALUENAMEMAPPER_H
#include <QStringList>
#include <QScopedPointer>
class QEnumValueNameMapper {
public:
QEnumValueNameMapper();
~QEnumValueNameMapper();
void appendValueName(int enumvalue, const QString &uiname);
QStringList uiNames() const;
QString uiName(int enumvalue, bool *ok = 0) const;
int enumValue(const QString &uiname, bool *ok = 0) const;
private:
struct PrivateData;
PrivateData *d_ptr;
Q_DISABLE_COPY(QEnumValueNameMapper)
};
#endif // QENUMVALUENAMEMAPPER_H
#include "qenumvaluenamemapper.h"
#include <QList>
#include <QDebug>
struct ValueName
{
ValueName() {}
ValueName(int value, const QString &name) : enumValue(value), uiName(name) {}
int enumValue;
QString uiName;
};
struct QEnumValueNameMapper::PrivateData
{
// 这里之所以使用QList而不使用QMap, 是因为:
//1)QList不会自动排序,保证了用户append的原有顺序,这对于用户而言也许是重要的!
//2)考虑到大部分情况下,枚举值的个数不会太多(<1000),遍历QList的查找效率不会成为问题!
QList<ValueName> valueNamePairList;
};
QEnumValueNameMapper::QEnumValueNameMapper() :
d_ptr(new PrivateData())
{
}
QEnumValueNameMapper::~QEnumValueNameMapper()
{
delete d_ptr;
d_ptr = 0;
}
void QEnumValueNameMapper::appendValueName(int enumvalue, const QString &uiname)
{
d_ptr->valueNamePairList.append(ValueName(enumvalue, uiname));
}
QStringList QEnumValueNameMapper::uiNames() const
{
QStringList names;
foreach (const ValueName &var, d_ptr->valueNamePairList)
{
names.append(var.uiName);
}
return names;
}
QString QEnumValueNameMapper::uiName(int enumvalue, bool *ok) const
{
foreach (const ValueName &var, d_ptr->valueNamePairList)
{
if (var.enumValue == enumvalue)
{
if (ok)
*ok = true;
return var.uiName;
}
}
qWarning()<<"error! enum-value not exist! enum-value="<<enumvalue;
if (ok)
*ok = false;
return QString::null;
}
int QEnumValueNameMapper::enumValue(const QString &uiname, bool *ok) const
{
foreach (const ValueName &var, d_ptr->valueNamePairList)
{
if (var.uiName == uiname)
{
if (ok)
*ok = true;
return var.enumValue;
}
}
qWarning()<<"error! ui-name not exist! ui-name="<<uiname;
if (ok)
*ok = false;
return -9999;
}
调用端代码:
QEnumValueNameMapper m_valueNameMap;
m_valueNameMap.appendValueName(FileType::Text, tr("Text文件"));
m_valueNameMap.appendValueName(FileType::Word, tr("Word文件"));
.appendValueName(FileType::Excel, tr("Excel表格"));
m_valueNameMap.appendValueName(FileType::Binary, tr("二进制文件"));
m_valueNameMap.appendValueName(FileType::PDF, tr("Word文件"));
ui->comboBox->addItems(m_valueNameMap.uiNames());
if (m_valueNameMap.enumValue(ui->comboBox->currentText()) == FileType::Text)
{
// 执行某种操作(Text文件)
}
// ...
这种方式消除了硬编码,让修改维护变得简单灵活。