Seite 1 von 1

QTableView

Verfasst: 19. Juli 2011 13:27
von stephane
Hallo,
ich habe eine kurze Frage und ich hoffe jemand könnte mir dabei helfen.
Ich habe ein selbst definierte Model, dass ich mit eine QTableView verbunden habe. Mein Model enthält eine QMap<QString, QString>. In der Map befinden sich Variablen und Ihren Werte das ich per Ethernet bekomme. Das Problem ist die Werten ändern sich ständig und ich möchte der Benutzer die Möglichkeit anbieten, ein Variablewert zu ändern. dafür habe ich die funktion setData() implementiert. Außerdem habe ich eine Funktion setDataMap(), die mir ermöglicht den Map zu aktualisieren solange ich neue Datenbekommen habe. Damit wird die ganze View aktualisiert.

Code: Alles auswählen

void TableModel::setDataMap(const QMap<QString, QString> &map)
{
	beginResetModel();
        m_Data = map;
	endResetModel();
}
ich möchte aber einzelne Variablenwert ändern können. sobald ich das auf dem View machen willst klappt es nicht. das View wird automatisch aktualisiert. Hat jemand eine Idee, wie ich es am geschicktesten machen kann?

Code: Alles auswählen

TableModel::TableModel(MainWindow *win, QObject *parent): QAbstractTableModel(parent)
{
	m_Win = win;
	for(int i = 0; i < 200; i++)
		checked[i] = false;
}

int TableModel::rowCount(const QModelIndex &parent) const
{
	Q_UNUSED(parent);
    return m_Data.size();
}

int TableModel::columnCount(const QModelIndex &parent) const
{
	Q_UNUSED(parent);
    return 2;
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= m_Data.size() || index.row() < 0)
        return QVariant();

    if (role == Qt::DisplayRole) 
	{
        if (index.column() == 0)
            return dataAt(index.row());
        else if (index.column() == 1)
			if (checked[index.row()])
			{
				return QVariant();
			}
			else
				return m_Data.value(dataAt(index.row()));
    }
	if(role==Qt::CheckStateRole )
		if(index.column() == 1 )
		{
			if (checked[index.row()])
				return  Qt::Checked;
			else
				return  Qt::Unchecked;
		}
    return QVariant();
}

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 QString("Variable");

            case 1:
                return QString("Value");

            default:
                return QVariant();
        }
    }
    return QVariant();
}

void TableModel::setDataMap(const QMap<QString, QString> &map)
{
	beginResetModel();
         m_Data = map;
	endResetModel();
}

QString TableModel::dataAt(int offset) const
{
    return (m_Data.begin() + offset).key();
}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
	Qt::ItemFlags flags = QAbstractItemModel::flags(index);
	flags |= Qt::ItemIsEditable;
	if (!index.isValid())
		return Qt::ItemIsEnabled;
	if (index.column() == 1)
		flags |= Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    return flags; 
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
	if (role == Qt::CheckStateRole)
	{
		QString data = dataAt(index.row());
		if(!checked[index.row()])
		{
			checked[index.row()] = true;
			m_Win->sendCmd("stoplog " + data + "\r\n");
		}
		else
		{
			checked[index.row()] = false;
			m_Win->sendCmd("startlog " + data + "\r\n");
		}
		return true;
	}
	if (role == Qt::EditRole)
	{
		QString data = dataAt(index.row());
		QString val = value.toString();
		if(checked[index.row()])
			m_Win->sendCmd("setvalue " + data + " " + val +"\r\n");
	return true;
	}
	return false;
}
Ich bedanbke mich im Voraus.

Re: QTableView

Verfasst: 19. Juli 2011 16:49
von RHBaum
Aehm naja, schwer vorschlage zu machen, weil wir nicht wissen wie dein Model "funktioniert"
m_Data = map;
Ich geh mal davon aus, dass "deine" Daten im Model also in ner QMap<QString, QString> liegen, richtig ?
wie laesst du denn den Index erstellen ?

generell, wenn du daten aendern willst, musst DU das ueber das Model tun, klar.

Dazu aenderst du die daten in der Datenhalterklasse, und sagst dem View, das es sich die daten neu holen soll ....
Zum neuholen muss das Model einfach das
signal: void dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight );
feuern.
Wie du aber nu von deinem Key in der Map auf ein ModelIndex kommst, das ist halt die Logic deines Models und die kenn ich ned ...

Ciao ...

Re: QTableView

Verfasst: 20. Juli 2011 11:44
von stephane
Wie ich vorher erklärt habe bekomme ich Variablen und ihre Werte über die Ethernet-Schnittstelle. Manche Variable Werte ändern sich sehr schnell und sind somit nicht Schreibbar und Manche nur gelegentlich (heiß 1 mal jede 2 Stunden). Ich habe die möglichkeit durch eine Funktion sendCmd(Variable, Value) den Wert der Variable, die sich langsam ändern zu setzen.

Was ich in meine Main tue ist: Die Variable die ich bekomme, parsern und in einem QMap<QString, Qstring> speichern. Diese QMap setzte ich durch die Funktion setDataMap(const QMap<QString, QString> &map) in meinem Model und mache einen reset(), damit die View sich aktualisiere.

Code: Alles auswählen

void TableModel::setDataMap(const QMap<QString, QString> &map)
{
	beginResetModel();
        m_Data = map;
	endResetModel();
}
Dort taught mein Problem auf. Wenn ich den reset() mache aktuzalisiert die TableView die ganze Tabelle und nicht nur die Werte die sich geändert haben. Und dies widerrum lass mich kaum Zeit diese Werte zu bearbeiten. Meine Frage ist gibt es eine Möglichkeit der View zu sagen, dass er nur einzelne Zellen aktualisieren muss?

Ich bedanke mich schon für die Hilfe.

Re: QTableView

Verfasst: 20. Juli 2011 12:38
von RHBaum
Meine Frage ist gibt es eine Möglichkeit der View zu sagen, dass er nur einzelne Zellen aktualisieren muss?
signal: void dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight );
Ja, das versuch ich dir doch zu erklären, aber du hoerst ned zu !

nen Model-reset wird immer dazu fuehren, dass der View alle Daten losslaesst, und sich komplett alles neu holt, also seine Table/Baum komplett neu aufbaut.

das heisst, du darfst nicht komplett deine Datenbasis austauschen, sondern darfst selber nur die notwendigen aenderungen an deiner Datenbasis im Model anedern, und dann nicht mit ModelReset signalisieren das du neue Daten hasst sondern viel filigraner mit DataChanged ...
Ich hab hier Models laufen, da fliessen bis zu 100MBit an daten durch die sie monitoren theorethisch ...
mit nem ModelReset wurde sich der View versuchen jede microsekunde neu aufzubauen ^^ was das gibt kannst dir wohl denken ...

Und wenn du ned auf mich hoeren willst, dann lies und versteh bitte die qt Doku zu dem MVC (Model View Controller) Konzept in der Qt Doku. Dort iss das alles ziemlich detailiert beschrieben.

Ciao ...

Re: QTableView

Verfasst: 20. Juli 2011 13:18
von stephane
Ok zuerst danke für deine Antwort, allerdings ich habe versucht mit emit dataChanged(const QModelIndex&, const QModelIndex&). Das Signal wird emitiert aber der View ruft die Funktion Data() trotzdem nicht auf. hast du eine idee woran es liegen könnte?

Ich weiß dass das Signal emitiert wurde, weil ich in meinen Model zur Testzwecken einen Slot eingebaut habe und die entsprechende Connect-Anweisung. Jedesmal dass sich was in meinem Model ändert spring mein Programm in dem SlotFunktion aber die View wird nicht aktualisiert.

Code: Alles auswählen

TableModel::TableModel(MainWindow *win, QObject *parent): QAbstractTableModel(parent)
{
	m_Win = win;
	for(int i = 0; i < 200; i++)
		checked[i] = false;
	QObject::connect(this, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(modelDataHasChanged(const QModelIndex&, const QModelIndex&)));
}

void TableModel::setDataMap(const QMap<QString, QString> &map)
{
	int i = 0;
	QMapIterator<QString, QString> m_Data_iterator(m_Data);
	QMapIterator<QString, QString> map_iterator(map);
	while (map_iterator.hasNext()) 
	{
		QModelIndex index = createIndex(i,0);
		map_iterator.next();
		if(m_Data.contains(map_iterator.key()))
		{
			if (m_Data.value(map_iterator.key()) != map_iterator.value())
			{
				m_Data.insert(map_iterator.key(), map_iterator.value());
				//emit a signal to make the view reread identified data
				emit QAbstractItemModel::dataChanged(index, index);
			}
		}
		else
		{
			m_Data.insert(map_iterator.key(), map_iterator.value());
			//emit a signal to make the view reread identified data
			emit dataChanged(index, index);
		}
		i++;
	}
}

Danke nochmal.

Re: QTableView

Verfasst: 20. Juli 2011 13:44
von RHBaum
was genau geaendert wurde, "erklaerst" du dem View ja ueber die Model-Indizies ... und die muessen stimmen und valid sein, sonst macht der view nix ...
Also, wie "baust" du die QModelIndex in deinem Model ?
bzw, werden die Indizies genau so wie bei der index() methode gebaut, das ist auch wichtig.

btw. wenn ein Datensatz im Model noch nicht vorhanden ist, brauchst du auch kein DataChanged aufrufen ... das bringt nix ...
Sondern du musst beginInsert .... end Insert() aufrufen. Dann "erweitert" sich der View auch ...

Generell hasst du noch ein problem:
nen Model ist Index basierend, also 0,1,2 ...
deine Datenhalterklasse ist nen Dictonary.
Das ist eher unguenstig.
Wenn du ein element einfuegst, musst du selber die Pos bestimmen und in einen int Index umrechnen ...
Du musst also Posi und Key immer synchron irgendwie halten .
weiterhin musst du alle drauffolgenden Daten "nachruecken" lassen ...
Das geht, ist aber ziemlich komplex.
Besser :
Man haelt die Daten total ungeordnet.
und fuer die Sortierung schaltet man ein Model (Filter sort Model) dazwischen.



Ciao ...

Re: QTableView

Verfasst: 20. Juli 2011 13:50
von stephane
Ich habe einfach die Funktion QModelIndex QAbstractItemModel::createIndex ( int row, int column, void * ptr = 0 ) aufgerufen und die entsprechende Zeilen und Spalten-Nummer als parameter übergeben.
ist es Ok so oder gibt es noch etwas das ich tuen sollte?

Re: QTableView

Verfasst: 20. Juli 2011 14:06
von RHBaum
ist es Ok so oder gibt es noch etwas das ich tuen sollte?
Nee scheint genau das ist dein problem ...

- du kriegst ne Map mit 3 elementen rein ....
- du iterierst und hasst nen counter.
- das heisst beim 1 element kriegst du den ModelIndex von der Zeile 0, beim 2. zeile 1, beim 3. zeile 2 ... etc
- die elemente werden in deiner zielmap aber irgendwo eingefuegt, haben also theorethisch ganz andere Indizies.

Das heisst, du rufst das datachange zwar auf, aber aktualisiert die falschen Zellen ....

Du hasst Ne Zuordnung String-> String (Dictionary)
Du brauchst noch ne Zurdnung Index(int) -> string (fuer gezielten Datenzugriff), und du brauchst aber auch Key-> Index fuer die Aktualisierung.

Loesungen gibts mehrere Möglichkeiten, das haengt aber auch von deiner Situation ausserhalb ab:
- Bist du an die Maps Gebunden ?
- kann / soll das Model die Datenhalten, oder hasst du ausserhalb noch ne Datenhalterklasse ...

Was du machen Koenntest:
Die Map durch ne List ersetzen .... dafuer wird das einfuegen / modifizieren aber laenger dauern
Durch weitere Maps Index->key und Key->Index beziehungen bauen
Reihenfolge in der Map als Index nehmen und alles Synchron halten ...

Hat alles Vor und Nachteile:

Für nen konkreten Vorschlag braeucht ich noch paar Infos:

Wie gross wird deine Map in dem Model, also wieviel Zeilen zeigt dein Modell maximal befuellt ca. an ... Groessenordnung.
100 ? 1000? 10000000 ?
setDataMap(const QMap<QString, QString> &map)
Deine Set methode ist der Dreh und Angelpunkt an der Geschichte, den Rest kann man so abschaetzen.
- Wie oft wird da ein Update auf die daten gefahren ?
- erzeugst du die map da selber immer ... oder bekommst du die map fertig ? Kannst du die Funktion nicht auf sowas wie setData(const QString & key, const QString & Value) runterbrechen ?

Ciao ...