QDataWidgetMapper и изменение foregn key |
Здравствуйте, гость ( Вход | Регистрация )
QDataWidgetMapper и изменение foregn key |
Begemot |
28.5.2010, 17:44
Сообщение
#1
|
Студент Группа: Новичок Сообщений: 13 Регистрация: 15.5.2010 Пользователь №: 1718 Спасибо сказали: 0 раз(а) Репутация: 0 |
Случилось тут проблема, и уже больше суток не могу решить, пробовал разные подходы все или вообще не работает или работает но через раз (не шучу).
Есть MySqlRelationalTableModel, у нее одно из полей внешний ключ (fkey) ну и соответвенно связанная таблица ID, Name. Все поля таблицы привязаны к виджетам на форме через QDataWidgetMapper, fkey привязан к QLineEdit. Отображается все отлично, вместо значения ключа я вижу соответвующий Name из связанной таблицы. Вопрос как правильно програмно изменить это значение. Для юзера это выглядит так - QLineEdit read only, по щелчку на нему - открывается диалог, в котором список Name - пользователь выбирает нужное или _вводит новое_, я получаю строку которую он выбрал\ввел. Как мне теперь правильно изменить значение в главной модели и все это сохранить в бд? |
|
|
Litkevich Yuriy |
28.5.2010, 18:59
Сообщение
#2
|
разработчик РЭА Группа: Сомодератор Сообщений: 9669 Регистрация: 9.1.2008 Из: Тюмень Пользователь №: 64 Спасибо сказали: 807 раз(а) Репутация: 94 |
setModelData и submitAll
|
|
|
Begemot |
29.5.2010, 7:39
Сообщение
#3
|
Студент Группа: Новичок Сообщений: 13 Регистрация: 15.5.2010 Пользователь №: 1718 Спасибо сказали: 0 раз(а) Репутация: 0 |
setModelData и submitAll Я так полагаю что речь идет о делегате, потому как setModelData есть только у наследников QAbstractItemDelegate. Но из контрола (наследник QLineEdit) к делегату доступ получить вроде нельзя (не передавая его руками)? да это и не важно, в принципе, setModelData вызовется автоматически при потере контролом фокуса, использую setEditStrategy(QSqlTableModel::OnFieldChange); Сейчас работа идет так, в классе наследнике QEditLine перехватывается щелчек мыши, показывается диалог - пользователь выбирает строку, эта строка записывается в наш QLineEdit, дальше ждем потери фокуса, для автоматического сохранения. У меня есть свой делегат (изначально писался для обслуживания комбобоксов), для этого поля добавляю туда такой код CODE void DogDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { switch(index.column()) { ...... case Animals_Breed: QString text = editor->property("text").toString(); qDebug()<<"save Animals_Breed " << text; // --- get relation table and try to find our string QSqlTableModel * rm = ((QSqlRelationalTableModel *)model)->relationModel(Animals_Breed); QModelIndexList indexList = rm->match(rm->index(0, Breeds_Breed), Qt::DisplayRole, text, 1, Qt::MatchExactly); int id = 0; if (!indexList.isEmpty()) id = rm->data(rm->index(indexList.first().row(), Breeds_Id)).toInt(); else { // there is no such string - we should add it QSqlRecord record(rm->record()); record.setValue(Breeds_Breed, text); rm->insertRecord(0, record); rm->submit(); // --- Get id for new added string QModelIndexList indexList = rm->match(rm->index(0, Breeds_Breed), Qt::DisplayRole, text, 1, Qt::MatchExactly); if (!indexList.isEmpty()) id = rm->data(rm->index(indexList.first().row(), Breeds_Id)).toInt(); else Q_ASSERT(0); } model->setData(index, id); ((QSqlRelationalTableModel*) model)->submitAll(); return; } QItemDelegate::setModelData(editor, model, index); } Если маппер пытается сабмитить это поле, я получаю в rm таблицу связанную с внешним ключем, и пытаюсь в соответвуюшей колонке найти значение которое у нас в лайнэдите. Если значение есть - все отлично, беру соответсвующий id из внешней таблицы и пишу его в главную таблицу. Если там такого значения нет, значит пользователь ввел новую породу - я добавляю ее в связанную таблицу, сабмичу ее, дальше получаю id и пишу в главную. Этот подход работает если пользователь просто выбирает из списка строку (которая уже есть в таблице). Если же он вводит новую строку - я получаю проблемы 1. Строка добавляется в бд. 2. В едит бокс попадает соответвующее значение (уже прочитанное из бд, а не то что я руками поставил) 3. дальше пользователь ставит фокус в другой контрол, и потом когда фокус переходит в третий контрол - уходит из второго, значение в едит боксе и в базе данных сбрасывается в предыдущее, и так в 66% случаях. submitAll не помогает видимо проблема в том что при изменении\сабмите связаной таблицы, то ли главная модель не обновляется то ли еще что - но оно "забывает" изменения в 2 их 3 случаях. Что делать ума не приложу, как-то ресетить модель? заново перезагружать? что-то еще? Или это просто еще 1 баг |
|
|
Litkevich Yuriy |
29.5.2010, 11:25
Сообщение
#4
|
разработчик РЭА Группа: Сомодератор Сообщений: 9669 Регистрация: 9.1.2008 Из: Тюмень Пользователь №: 64 Спасибо сказали: 807 раз(а) Репутация: 94 |
|
|
|
Begemot |
30.5.2010, 12:41
Сообщение
#5
|
Студент Группа: Новичок Сообщений: 13 Регистрация: 15.5.2010 Пользователь №: 1718 Спасибо сказали: 0 раз(а) Репутация: 0 |
Ни прямой вызов setData, ни вызов его в делегате, ни вариант заставить маппер засабмитить у меня не работает так как надо.
Написал тестовое приложение, в котором явно видна, проблема, может кто-может посмотреть? Ниже тексты трех файлов, свой делегат нужен просто для того что бы выводить лог сохранения \ чтения данных, в h файле вверху можно изменить define отвечающий за родителя делегата, я проверял оба варианта - они работают одинаково похоже. Код сохранения сейчас в void RelationLineEdit::mousePressEvent(QMouseEvent* e) main.cpp CODE #include <QApplication> #include "window.h" int main(int argc, char **argv) { QApplication app(argc, argv); Window window; window.show(); return app.exec(); } window.h CODE #ifndef WINDOW_H #define WINDOW_H //#define MyDelegateParent QSqlRelationalDelegate #define MyDelegateParent QItemDelegate #include <QtGui> #include <QtSql> class QComboBox; class QDataWidgetMapper; class QItemSelectionModel; class QLabel; class QLineEdit; class QPushButton; class QSqlRelationalTableModel; class QStandardItemModel; class QStringListModel; class QTextEdit; class RelationLineEdit; class MySqlRelationalTableModel; class Window : public QWidget { Q_OBJECT public: Window(QWidget *parent = 0); private slots: void updateButtons(int row); private: void setupModel(); QLabel *nameLabel; QLabel *addressLabel; QLabel *typeLabel; QLineEdit *nameEdit; QTextEdit *addressEdit; RelationLineEdit * typeEdit; QPushButton *nextButton; QPushButton *previousButton; MySqlRelationalTableModel *model; QItemSelectionModel *selectionModel; QDataWidgetMapper *mapper; int typeIndex; /*QModelIndex */ int indexFixQTBUG1086; private slots: void FixQTBUG1086Before(); void FixQTBUG1086After(); }; //------------------------------------------------------------------------------ class RelationLineEdit : public QLineEdit { Q_OBJECT public: RelationLineEdit(QWidget *parent = 0); ~RelationLineEdit(); void SetData(QSqlRelationalTableModel * _model, QDataWidgetMapper * _mapper, const QString _title) { model = _model; mapper = _mapper; title = _title; } protected: virtual void mousePressEvent(QMouseEvent* e); private: QSqlRelationalTableModel * model; QDataWidgetMapper * mapper; QString title; }; //------------------------------------------------------------------------------ class SelectRelationValueDlg : public QDialog { Q_OBJECT public: SelectRelationValueDlg(QWidget * parent, QSqlTableModel * model, const QString & current, const QString title); ~SelectRelationValueDlg(); QString GetText() const { return pEdit->text(); } private slots: void CurrentRowChanged(const QModelIndex & current); void TextChanged(const QString & text); private: QVBoxLayout *verticalLayout; QLineEdit *pEdit; QListView *pList; QDialogButtonBox *buttonBox; }; /* Subclassed model as workaround for bug http://bugreports.qt.nokia.com/browse/QTBUG-1086 http://begemotov.net/wxwidgets/qt/qt-te-zh...tolko-v-profil/ Похоже каждый раз когда маппер сохраняет данные, делается select, мы испускаем сигналы, который вызывающий код должен привязать к процедурам сохранения\востановления индекса connect(modelname, SIGNAL(BeforeSelected()), this, SLOT(FixQTBUG1086Before()));\ connect(modelname, SIGNAL(AfterSelected()), this, SLOT(FixQTBUG1086After()));\ */ //------------------------------------------------------------------------------ class MySqlRelationalTableModel : public QSqlRelationalTableModel { Q_OBJECT public: MySqlRelationalTableModel(QWidget *parent = 0) : QSqlRelationalTableModel(parent) {} virtual bool select() { emit BeforeSelected(); // without this it doesn't work const bool ret = QSqlRelationalTableModel::select(); emit AfterSelected(); // without this it doesn't work return ret; } signals: void BeforeSelected(); void AfterSelected(); }; #endif //------------------------------------------------------------------------------ class MyDelegate : public MyDelegateParent { Q_OBJECT public: MyDelegate(QObject *parent = 0) {}; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; }; window.cpp CODE #include "window.h" #define Person_TypeId 3 #define Addresstype_Id 0 #define Addresstype_Place 1 Window::Window(QWidget *parent) : QWidget(parent), mapper(0) { setupModel(); indexFixQTBUG1086 = -1; nameLabel = new QLabel(tr("Na&me:")); nameEdit = new QLineEdit(); addressLabel = new QLabel(tr("&Address:")); addressEdit = new QTextEdit(); typeLabel = new QLabel(tr("&Type:")); typeEdit = new RelationLineEdit(); nextButton = new QPushButton(tr("&Next")); previousButton = new QPushButton(tr("&Previous")); nameLabel->setBuddy(nameEdit); addressLabel->setBuddy(addressEdit); typeLabel->setBuddy(typeEdit); mapper = new QDataWidgetMapper(this); mapper->setModel(model); mapper->setItemDelegate(new MyDelegate(this)); mapper->addMapping(nameEdit, model->fieldIndex("name")); mapper->addMapping(addressEdit, model->fieldIndex("address")); mapper->addMapping(typeEdit, typeIndex); typeEdit->SetData(model, mapper, tr("Select place")); connect(previousButton, SIGNAL(clicked()), mapper, SLOT(toPrevious())); connect(nextButton, SIGNAL(clicked()), mapper, SLOT(toNext())); connect(mapper, SIGNAL(currentIndexChanged(int)), this, SLOT(updateButtons(int))); QGridLayout *layout = new QGridLayout(); layout->addWidget(nameLabel, 0, 0, 1, 1); layout->addWidget(nameEdit, 0, 1, 1, 1); layout->addWidget(previousButton, 0, 2, 1, 1); layout->addWidget(addressLabel, 1, 0, 1, 1); layout->addWidget(addressEdit, 1, 1, 2, 1); layout->addWidget(nextButton, 1, 2, 1, 1); layout->addWidget(typeLabel, 3, 0, 1, 1); layout->addWidget(typeEdit, 3, 1, 1, 1); setLayout(layout); setWindowTitle(tr("SQL Widget Mapper")); mapper->toFirst(); } void Window::setupModel() { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(":memory:"); if (!db.open()) { QMessageBox::critical(0, tr("Cannot open database"), tr("Unable to establish a database connection.\n" "This example needs SQLite support. Please read " "the Qt SQL driver documentation for information how " "to build it."), QMessageBox::Cancel); return; } QSqlQuery query; query.exec("create table person (id int primary key, name varchar(20), address varchar(200), typeid int)"); query.exec("insert into person values(1, 'Alice', '<qt>123 Main Street<br/>Market Town</qt>', 101)"); query.exec("insert into person values(2, 'Bob', '<qt>PO Box 32<br/>Mail Handling Service<br/>Service City</qt>', 102)"); query.exec("insert into person values(3, 'Carol', '<qt>The Lighthouse<br/>Remote Island</qt>', 103)"); query.exec("insert into person values(4, 'Donald', '<qt>47338 Park Avenue<br/>Big City</qt>', 101)"); query.exec("insert into person values(5, 'Emma', '<qt>Research Station<br/>Base Camp<br/> Big Mountain</qt>', 103)"); query.exec("create table addresstype (id int, description varchar(20))"); query.exec("insert into addresstype values(101, 'Home')"); query.exec("insert into addresstype values(102, 'Work')"); query.exec("insert into addresstype values(103, 'Other')"); model = new MySqlRelationalTableModel(this); connect(model, SIGNAL(BeforeSelected()), this, SLOT(FixQTBUG1086Before())); connect(model, SIGNAL(AfterSelected()), this, SLOT(FixQTBUG1086After())); model->setTable("person"); model->setEditStrategy(QSqlTableModel::OnFieldChange); typeIndex = model->fieldIndex("typeid"); model->setRelation(typeIndex, QSqlRelation("addresstype", "id", "description")); model->select(); } void Window::updateButtons(int row) { previousButton->setEnabled(row > 0); nextButton->setEnabled(row < model->rowCount() - 1); } void Window::FixQTBUG1086Before() { if (!mapper) return; indexFixQTBUG1086 = mapper->currentIndex(); } void Window::FixQTBUG1086After() { if (!mapper) return; if (indexFixQTBUG1086 > -1) mapper->setCurrentIndex(indexFixQTBUG1086); } //------------------------------------------------------------------------------ SelectRelationValueDlg::SelectRelationValueDlg(QWidget * parent, QSqlTableModel * model, const QString & current, const QString title) : QDialog(parent) { if (objectName().isEmpty()) setObjectName(QString::fromUtf8("SelectRelationValueDlg")); resize(400, 300); verticalLayout = new QVBoxLayout(this); verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); pEdit = new QLineEdit(this); pEdit->setObjectName(QString::fromUtf8("pEdit")); verticalLayout->addWidget(pEdit); pList = new QListView(this); pList->setObjectName(QString::fromUtf8("pList")); verticalLayout->addWidget(pList); buttonBox = new QDialogButtonBox(this); buttonBox->setObjectName(QString::fromUtf8("buttonBox")); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); verticalLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); pList->setModel(model); pList->setModelColumn(1); setWindowTitle(title); connect(pList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(CurrentRowChanged(QModelIndex))); connect(pList, SIGNAL(clicked(QModelIndex)), this, SLOT(CurrentRowChanged(QModelIndex))); // Надо - для того что бы менять текст при клике на текущую выделенную строку connect(pEdit, SIGNAL(textChanged(const QString &)), this, SLOT(TextChanged(const QString &))); pEdit->setText(current); } SelectRelationValueDlg::~SelectRelationValueDlg() { } void SelectRelationValueDlg::CurrentRowChanged(const QModelIndex & current) { pEdit->setText(current.data().toString()); } void SelectRelationValueDlg::TextChanged(const QString & text) { QSqlTableModel * model = (QSqlTableModel *)pList->model(); QModelIndexList indexList = model->match(model->index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchExactly); if (!indexList.isEmpty()) pList->selectionModel()->setCurrentIndex(indexList.first(), QItemSelectionModel::ClearAndSelect); else pList->clearSelection(); //selectionModel()->setCurrentIndex(ui->pList->selectionModel()->currentIndex(), QItemSelectionModel::Clear); } //------------------------------------------------------------------------------ void MyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { switch(index.column()) { case Person_TypeId: qDebug() << "read Person_TypeId"; break; } MyDelegateParent::setEditorData(editor, index); } void MyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { switch(index.column()) { case Person_TypeId: { QString text = editor->property("text").toString(); qDebug()<<"save Person_TypeId : " << text; } break; } MyDelegateParent::setModelData(editor, model, index); } //------------------------------------------------------------------------------ RelationLineEdit::RelationLineEdit(QWidget *parent) : QLineEdit(parent)//, model(0) { setReadOnly(true); setCursor(Qt::PointingHandCursor); } RelationLineEdit::~RelationLineEdit() { } void RelationLineEdit::mousePressEvent(QMouseEvent* e) { QSqlTableModel * relModel = model->relationModel(Person_TypeId); SelectRelationValueDlg dlg(this, relModel, text(), title); if (dlg.exec() != QDialog::Accepted) return; QString text = dlg.GetText(); qDebug()<<"change place :" << text; // --- get relation table and try to find our string QModelIndexList indexList = relModel->match(relModel->index(0, Addresstype_Place), Qt::DisplayRole, text, 1, Qt::MatchExactly); int id = 0; if (!indexList.isEmpty()) id = relModel->data(relModel->index(indexList.first().row(), Addresstype_Id)).toInt(); else { // there is no such string - we should add it QSqlRecord record(relModel->record()); record.setValue(Addresstype_Place, text); relModel->insertRecord(0, record); relModel->submitAll(); // --- Get id for new added string QModelIndexList indexList = relModel->match(relModel->index(0, Addresstype_Place), Qt::DisplayRole, text, 1, Qt::MatchExactly); if (!indexList.isEmpty()) id = relModel->data(relModel->index(indexList.first().row(), Addresstype_Id)).toInt(); else Q_ASSERT(0); } setText(text); QModelIndex index = model->index(mapper->currentIndex(), Person_TypeId); model->setData(index, id); model->submitAll(); } pro файла у меня нету, крейтор у меня не хочет работать, пишу в студии, но думаю что этот код должно быть легко попдправить под мой пример. CODE HEADERS = window.h SOURCES = main.cpp \ window.cpp QT += sql # install target.path = $$[QT_INSTALL_EXAMPLES]/sql/sqlwidgetmapper sources.files = $$SOURCES $$HEADERS *.pro sources.path = $$[QT_INSTALL_EXAMPLES]/sql/sqlwidgetmapper INSTALLS += target sources wince*: DEPLOYMENT_PLUGIN += qsqlite Помогите разобратся, я что-то не так делаю или все-так я опять нарвался на баг:( Да, забыл самое главное. Алгоритм такой 1. Кликаем на нижнем поле 2. Выбираем из списка любою строку, например other 3. ок - так все хорошо, а вот если попробовать добавить новую строку - облом 1. Кликаем на нижнем поле 2. к текущей строке добавляем 2ку (other2) ну или пишем что угодно другое. 3. ок 4. ставим курсор в верхний эдит 5. ставим курсор в средний эдит В этот момент строка которую мы поменяли и которая уже сохранена в базу судя по логам делегата, возвращяется к первоначальному значению. |
|
|
Begemot |
30.5.2010, 13:13
Сообщение
#6
|
Студент Группа: Новичок Сообщений: 13 Регистрация: 15.5.2010 Пользователь №: 1718 Спасибо сказали: 0 раз(а) Репутация: 0 |
Допустил ошибку, надо поправить код создания базы данных сделав id автоинкрементным столбцами, надо заменить две соответвующие строчки на эти
query.exec("create table person (id integer primary key, name varchar(20), address varchar(200), typeid int)"); query.exec("create table addresstype (id integer primary key, description varchar(20))"); |
|
|
Begemot |
30.5.2010, 19:26
Сообщение
#7
|
Студент Группа: Новичок Сообщений: 13 Регистрация: 15.5.2010 Пользователь №: 1718 Спасибо сказали: 0 раз(а) Репутация: 0 |
Вообщем похоже все плохо, Now I do not use QSqlRelationalTable model at all. Useless class. To many errors... and not so good design also. (с) Qt Bugtracker, полностью согласен. Вот еще по теме - статус поражает, и еще.
В принципе сейчас перейдя на стратегию ручного сабмита и дико извратившись, я сумел добится нужного поведения, но такой изврат оставлять нельзя. Так что буду наверно отказыватся от QSqlRelationalTable , брать QSqlTable и делать все ручками. блин почти 3 дня коту под хвости из за багов QSqlRelationalTable. |
|
|
Litkevich Yuriy |
30.5.2010, 23:05
Сообщение
#8
|
разработчик РЭА Группа: Сомодератор Сообщений: 9669 Регистрация: 9.1.2008 Из: Тюмень Пользователь №: 64 Спасибо сказали: 807 раз(а) Репутация: 94 |
Факт: Qt 4.3.5 — наименее глючная версия в плане Модель/представление.
|
|
|
Гость_kkmspb_* |
24.8.2012, 10:41
Сообщение
#9
|
Гости |
У похожая ситуация :
setData( .. меняет значение в поле у QSqlRelationalTableModel делаем submitAll( mapper связанный с этим полем неправильно отображает ... делаем _mapper->setCurrentIndex(0); и теперь все правильно |
|
|
Текстовая версия | Сейчас: 29.11.2024, 4:33 |