George
George

Reputation: 630

How do i make only one QComboBox item editable?

I have QComboBox in my Qt application and i want it to have few default, non-editable items, but i also want it to have one editable item, which has default text, but is replaced once edited and some confirmation button is pressed (enter, by default).

Here's what i've tried:

QComboBox* combo_1 = new QComboBox();
combo_1->setEditable(true);
combo_1->addItems(QStringList()<<"Option_1."<<"Option_2."<<"Option_3."<<"Option_4."<<"Other...");

This way all the items are editable and once i edit any item and press enter, it stays the same, but new item with edited text is inserted to the box.

How can i achieve the behaviour i want? Any help is appreciated!

P.s. i indicated my goal as to have only one editable item, but i'd also like to know how to insert endless(conditionally) amount of new items the same way.

Upvotes: 0

Views: 1459

Answers (1)

Serhiy Kulish
Serhiy Kulish

Reputation: 1122

Solution is using model-view pattern for combobox and subclassing QComboBox.

1: Implement custom model. In my case I have editable item in row = 2 (QString m_strEditableValue) and fixed items in 0,1 rows.

class MyModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit MyModel(QObject *parent = nullptr);

    QModelIndex index(int row, int column, const QModelIndex &parent) const;
    QModelIndex parent(const QModelIndex &child) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    Qt::ItemFlags flags(const QModelIndex &index) const;

private:
    QString m_strEditableValue;

};

MyModel::MyModel(QObject *parent) : QAbstractItemModel(parent)
{
    m_strEditableValue = "default value";
}

QModelIndex MyModel::index(int row, int column, const QModelIndex &parent) const
{
    return createIndex(row, column);
}

QModelIndex MyModel::parent(const QModelIndex &child) const
{
    return QModelIndex();
}

int MyModel::rowCount(const QModelIndex &parent) const
{
    return 3;
}

int MyModel::columnCount(const QModelIndex &parent) const
{
    return 1;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        if (index.row() == 0) {
            return tr("First fixed value");
        }
        if (index.row() == 1) {
            return tr("Second fixed value");
        }

        if (index.row() == 2) {
            return m_strEditableValue;
        }
    }

    return QVariant();
}

bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole) {
        if (index.row() == 2) {
            m_strEditableValue = value.toString();
            return true;
        }
    }

    return false;
}

Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    if (index.row() == 2) {
        //mark editable only for row 2
        f = f | Qt::ItemIsEditable;
    }
    return f;
}

2: Subclass QComboBox to change standard behavior

class MyCombobox : public QComboBox
{
    Q_OBJECT
public:
    explicit MyCombobox(QWidget *parent = nullptr);

private slots:
    void OnEditTextChanged(const QString& text);
    void OnCurrentIndexChanged(int index);

public slots:
};

MyCombobox::MyCombobox(QWidget *parent) : QComboBox(parent)
{
    connect(this, &QComboBox::editTextChanged, this, &MyCombobox::OnEditTextChanged);
    connect(this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MyCombobox::OnCurrentIndexChanged);
}

void MyCombobox::OnEditTextChanged(const QString &text)
{
    if (model()) {
        //set data to model immediately 
        model()->setData(model()->index(currentIndex(), 0), text);
    }
}

void MyCombobox::OnCurrentIndexChanged(int index)
{
    if (model())
    {
        //disable editing if model disable it
        Qt::ItemFlags flags = model()->flags(model()->index(index, 0));
        if (flags & Qt::ItemIsEditable) {
            lineEdit()->setReadOnly(false);
        } else {
            lineEdit()->setReadOnly(true);
        }
    }
}

3: Using

MyModel *cbModel = new MyModel(this);
ui->cbEditable->setModel(cbModel);

Upvotes: 1

Related Questions