是否可以在另一个模型视图的委托中拥有模型视图(例如 ListView 或 GridView)?
我正在使用一个 ListView (LV1),它的委托也有一个 ListView (LV2)。LV2 之前和之后的所有内容都正确显示(并且在 LV1 的委托中)。但是,LV1 中没有显示任何内容。
我的模型来自:http: //qt-project.org/wiki/How_to_use_a_QSqlQueryModel_in_QML。我认为 SQL 查询是正确的,因为它在 Qt 外部运行时返回正确的信息。根据调试消息,所有查询都已明确执行。
我有两个查询,一个正在与另一个一起更新。效果很好。
图 #1 显示了调试消息,它指出 QML 确实调用了数据函数并且信息确实是从数据库返回的。
图 #2 显示了图 #1 时的 QML 窗口:我们可以清楚地看到布局混乱,什么都没有显示。
Verbatim #1 是 QML 代码。
Verbatim #2 是 main 的摘录,它表明我通过一个名为 updateBindings 的信号将主模型连接到辅助模型。
Verbatim #3 是我更新的 SqlQueryModel。与教程的主要区别在于信号和插槽。
Verbatim #4 供参考,因为这个类在 verbatim #3 中使用。
任何输入都是有价值的,我已经为此苦苦挣扎了几天。谢谢。
逐字记录#1(QML):
import QtQuick 1.1
import "Styles"
Rectangle {
id : window;
width : 750
height : 500
signal searchSignal(string msg)
signal resetSignal()
property variant subtypes: {"S":"Status (digital)", "V":"Value (analog)"}
property variant phases: {'66': 'Final Mass Properties', '67': 'Final Thruster Aignment/Solar Array Inst.', '60': 'Final Functional Performance', '114': 'Maneuver (Wheel Mode) Overlay (LTO)', '88': 'Troubleshooting Test Phase 9', '89': 'Troubleshooting Test Phase 10', '111': 'Battery 1 Reconditioning Overlay (LTO)', '110': 'Battery Recharge Overlay (LTO)', '113': 'Maneuver (SK Mode) Overlay (LTO)', '112': 'Battery 2 Reconditioning Overlay (LTO)', '68': 'Flight Battery Functional', '83': 'Troubleshooting Test Phase 4', '80': 'Troubleshooting Test Phase 1', '81': 'Troubleshooting Test Phase 2', '86': 'Troubleshooting Test Phase 7', '87': 'Troubleshooting Test Phase 8', '84': 'Troubleshooting Test Phase 5', '85': 'Troubleshooting Test Phase 6', '24': 'Post T/V Performance ( Ambient)', '26': 'Alignments - Pre Dynamics', '27': 'Antenna Installation', '20': 'Cold (Equinox) Functional Plateau', '21': 'Transition Monitor (cold to hot)', '22': 'Hot ( Winter Solstice)', '82': 'Troubleshooting Test Phase 3', '28': 'Solar Array Installation', '40': 'CATR', '41': 'ESD (PFM Only)', '1': 'North Panel Electrical integration', '3': 'SSM/Bus Module Electrical Integration', '2': 'South Panel Electrical integration', '5': 'South and bus Electrical Integration', '4': 'North and Bus Electrical integration', '6': 'S/C Electrical Integration', '75': 'Hazardous Processing Facility Operations', '39': 'Final Alignment Verif. CATR Preps', '77': 'Launch Complex Performance', '76': 'HPF Payload testing', '108': 'Lunar Eclipse Event Overlay (LTO)', '109': 'Battery Discharge Overlay (LTO)', '70': 'Launch Base Functional Performance', '102': 'On Orbit', '103': 'Station Keeping', '100': 'Prime Shift Orbit Raising', '101': 'Off Shift Orbit Raising', '106': 'Quiescent Non-Eclipse State of Health (LTO)', '107': 'Earth Eclipse Season Overlay (LTO)', '104': 'Eclipse', '105': 'Post Eclipse', '10': 'Panel RF Testing', '13': 'Integrated System Reference Performance', '12': 'End to End Satting', '15': 'Transition Monitoring (Ambient to Hot Solstice)', '14': 'Pre Thermal Vacuum performance (Ambient)', '16': 'Summer Solstice Functional Plateau', '18': 'Transition Monitoring (Hot to Cold)', '31': 'Sine Vibration', '30': 'Acoustic Vibration', '36': 'Post Dynamics Performance', '35': 'Deployments', '34': 'Launch Vehicle Adapter Fit Check', '65': 'Propulsion Global Helium'}
ListView {
anchors.fill: parent
focus: true
highlightRangeMode: ListView.StrictlyEnforceRange
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
model: tcModel
delegate: Component {
Item {
id: item
width: window.width; height: window.height
Flickable {
id: mainScrollView
contentHeight: parent.height
contentWidth: parent.width
anchors.fill: parent
clip: true
focus: true
interactive: true
Column{
id: dataCol
spacing: 10
/* Buttons */
Row{
width: window.width
Button{
text: "Go"
action.onClicked: searchSignal("TLM_NO LIKE '" + num.text + "%'")
bgColor: "lightgreen"
width: window.width/10; height: window.width/30
}
Button{
text: "Reset"
action.onClicked: resetSignal()
bgColor: "lightgrey"
width: window.width/10; height: window.width/30
}
}
/* */
Grid{
columns: 5
spacing: 10
Text {
text: "Mnemonic"
font.bold: true
}
Text {
text: "Name"
font.bold: true
}
Text {
text: "Type"
font.bold: true
}
Text {
text: "Subtype"
font.bold: true
}
Text {
text: "Category"
font.bold: true
}
/* NEW LINE */
TextEdit {
id: num
text: TLM_NO
}
Text {
text: TLM_NAME
}
Text {
text: TLM_TYPE
}
Text {
text: (SUBTYPE ? subtypes[SUBTYPE] : "Unknown")
}
Text {
text: TLM_CATEGO
}
} /* End grid */
Separator{}
Text{
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "<u>Limits</u>"
id: limitLabel
}
/* Limits */
ListView {
//anchors.top: limitLabel.bottom
header:
Row{
height: 30
clip: true
anchors.margins: 4
Text {
text: "Phase name"
font.bold: true
}
Text {
text: "Red low"
font.bold: true
}
Text {
text: "Yellow low"
font.bold: true
}
Text {
text: "Yellow high"
font.bold: true
}
Text {
text: "Red high"
font.bold: true
}
}
delegate: Item {
id: delegate
width: delegate.ListView.view.width;
height: 30
clip: true
anchors.margins: 4
Row {
anchors.margins: 4
anchors.fill: parent
spacing: 4;
Text {
text: PHASE_NO?phases[PHASE_NO]:"N/A"
}
Text {
text: CRIT_LO?CRIT_LO:"N/A"
}
Text {
text: NOM_LO?NOM_LO:"N/A"
}
Text {
text: NOM_HI?NOM_HI:"N/A"
}
Text {
text: CRIT_HI?CRIT_HI:"N/A"
}
}
}
model: limitModel
height: window.height / 3
} /* End limits grid view*/
Separator{}
Text{
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "end limits"
}
}/* End column*/
}
}
}
}
}
逐字记录#2(main.cpp,摘录):
/* Let's create all the models associated with this given user form.*/
/* We need the viewer to connect all the signals and to set context properties.*/
QmlApplicationViewer viewer;
SqlQueryModel *mainModel;
QList<SqlQueryModel*>::iterator umIt;
QHash<QString, SqlQueryModel*> modelCnx = QHash<QString, SqlQueryModel*>();
for(umIt = selectedUF.userModels.begin(); umIt != selectedUF.userModels.end(); umIt++){
SqlQueryModel *model = *umIt;
/* Let's go through each binding of the UserModel and connect create the bindings in the model. */
model->exec();
viewer.rootContext()->setContextProperty(model->modelName, model);
/* If this is the selected search model, let's save it to connect the signals later. */
if (model->modelName == selectedUF.searchModel){
mainModel = model;
}else{
QObject::connect(mainModel, SIGNAL(bindedValueChanged(QString, QString, QVariant)),
model, SLOT(updateBindings(QString,QString,QVariant)));
}
}
viewer.setMainQmlFile(QString("qml/" + selectedUF.qml));
QObject::connect((QObject*)viewer.rootObject(), SIGNAL(searchSignal(QString)),
mainModel, SLOT(search(QString)));
QObject::connect((QObject*)viewer.rootObject(), SIGNAL(resetSignal()),
mainModel, SLOT(reset()));
viewer.showExpanded();
return app->exec();
逐字记录#3(sqlquerymodel.cpp):
#include "sqlquerymodel.h"
SqlQueryModel::SqlQueryModel(QObject *parent) :
QSqlQueryModel(parent)
{
this->modelName = "";
this->query = "";
bindings = QHash<QString, QList<ModelBinding> >();
}
void SqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
{
if(query.length() == 0){
this->query = query;
}
QSqlQueryModel::setQuery(query, db);
generateRoleNames();
QSqlError le = this->lastError();
if (le.isValid()){
QString errMsg("Query was:\n%1\n\nError:\n%2");
QMessageBox::critical(0, "Query error", errMsg.arg(query).arg(le.text()));
return;
}
}
void SqlQueryModel::setQuery(const QSqlQuery &query)
{
QSqlQueryModel::setQuery(query);
generateRoleNames();
QSqlError le = this->lastError();
if (le.isValid()){
QString errMsg("Query was:\n%1\n\nError:\n%2");
qDebug() << errMsg.arg(query.lastQuery()).arg(le.text());
/* We're not using a MessageBox because it causes a segfault for some reason. */
//QMessageBox::critical(0, "Query error", errMsg.arg(query.lastQuery()).arg(le.text()));
return;
}
}
/**
* @brief SqlQueryModel::exec This function prepares and executes the query.
*/
void SqlQueryModel::exec()
{
qDebug() << "Executing query on model" << this->modelName;
/* Let's create a QSqlQuery. It will store the query and we'll bind values to it.*/
QSqlQuery sQuery;
/* If we initialize the query with the string, then we CANNOT use bind (won't work and won't show any error).*/
sQuery.prepare(this->query);
/** Now, let's go through all the models associated to this instance.
* For each of them, we'll bind its value. Note that we're avoiding making a copy by using the adresses
* cf. : http://stackoverflow.com/questions/17106243/qt-iterator-not-accessing-the-correct-object
**/
QHash<QString, QList<ModelBinding> >::iterator bindingsIt;
for (bindingsIt = bindings.begin(); bindingsIt != bindings.end(); bindingsIt++){
QList<ModelBinding>::iterator eachBindingIt;
QList<ModelBinding>& curBinding = *bindingsIt;
for(eachBindingIt = curBinding.begin(); eachBindingIt != curBinding.end(); eachBindingIt++){
ModelBinding& binding = *eachBindingIt;
binding.bindToQuery(&sQuery);
}
}
/* Let's not forget to execute this query, or nothing will be displayed in the QML. */
sQuery.exec();
qDebug() << "----------------";
qDebug() << sQuery.lastQuery();
QMapIterator<QString, QVariant> i(sQuery.boundValues());
while (i.hasNext()) {
i.next();
qDebug() << i.key().toAscii().data() << "="
<< i.value().toString().toAscii().data();
}
qDebug() << "----------------";
this->setQuery(sQuery);
}
void SqlQueryModel::generateRoleNames()
{
QHash<int, QByteArray> roleNames;
for( int i = 0; i < record().count(); i++) {
roleNames[Qt::UserRole + i + 1] = record().fieldName(i).toAscii();
}
qDebug() << "Generating role names for" << modelName;
setRoleNames(roleNames);
}
void SqlQueryModel::search(QString str){
QString nQuery (query);
nQuery.append(" WHERE ").append(str);
qDebug() << "Set query to: " << nQuery;
this->setQuery(nQuery);
QSqlError le = this->lastError();
if (le.isValid()){
QString errMsg("An error occurred while loading the file.\n\nQuery was:\n%1\n\nError:\n%2");
QMessageBox::critical(0, "Database error", errMsg.arg(nQuery).arg(le.text()));
}
}
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
QVariant value = QSqlQueryModel::data(index, role);
if(role < Qt::UserRole){
value = QSqlQueryModel::data(index, role);
}else{
int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
qDebug() << modelName << ":" << record().fieldName(columnIdx) << "=" << value;
emit bindedValueChanged(modelName, record().fieldName(columnIdx), value);
}
return value;
}
void SqlQueryModel::reset()
{
qDebug() << "Resetting original SQL query to: " << query;
this->setQuery(query);
}
void SqlQueryModel::updateBindings(QString modelName, QString col, QVariant val)
{
/** Now, let's go through all the models associated to this instance.
* We're going to see if the new signal we got is used for this model (for model name and column name).
* If so, we'll assigned it and then we'll execute this query by calling exec().
**/
bool anyValueChanged = false;
QHash<QString, QList<ModelBinding> >::iterator bindingsIt;
for (bindingsIt = bindings.begin(); bindingsIt != bindings.end(); bindingsIt++){
QList<ModelBinding>::iterator eachBindingIt;
QList<ModelBinding>& curBinding = *bindingsIt;
for(eachBindingIt = curBinding.begin(); eachBindingIt != curBinding.end(); eachBindingIt++){
ModelBinding& binding = *eachBindingIt;
if(bindingsIt.key() == modelName && binding.column == col){
binding.value = val;
anyValueChanged = true;
}
}
}
if (anyValueChanged){
this->exec();
}
}
逐字记录#4(modelbinding.cpp):
#include "modelbinding.h"
ModelBinding::ModelBinding(QString placeholder, QString column, QVariant value)
{
this->placeholder = placeholder;
this->column = column;
this->value = value;
}
void ModelBinding::bindToQuery(QSqlQuery *sQuery)
{
sQuery->bindValue(placeholder, value);
}