問題描述
我有一個(gè)類用于永久存儲(chǔ)一些以表格方式組織的項(xiàng)目.這個(gè)類與 Qt 完全無關(guān),來自不同的庫(kù).對(duì)于這個(gè)問題的其余部分,讓我們調(diào)用這個(gè)類 DataContainer
.它提供了與 std-c++ 兼容的迭代器來訪問和操作內(nèi)容.
I have a class used for permanent storage of some item that are organized in a table-like manner. This class is totally unrelated to Qt and comes from a different library. Lets call this class DataContainer
for the rest of this question. It provides std-c++ compatible iterators to access and manipulate the content.
我需要通過 Qt GUI 顯示和修改該數(shù)據(jù).我的想法是創(chuàng)建一個(gè)類 DataContainerQtAdaptor
,它繼承自 QAbstractTableModel
并存儲(chǔ)一個(gè)指向 DataContainer
對(duì)象的指針.DataContainerQtAdaptor
用作 DataContainer
對(duì)象的適配器,我的 Qt 應(yīng)用程序內(nèi)部的所有操作都通過此適配器完成.然后我使用 QTableView
小部件來顯示信息.
I need to display and modify that data through a Qt GUI. My idea was to create a class DataContainerQtAdaptor
that inherits from QAbstractTableModel
and stores a pointer to the DataContainer
object. The DataContainerQtAdaptor
serves as an adaptor to the DataContainer
object and all manipulation from inside of my Qt app is done through this adaptor. Then I use a QTableView
widget to display the information.
不幸的是,DataContainer
可能會(huì)被線程/進(jìn)程更改.(例如,將 DataContainer
視為封裝數(shù)據(jù)庫(kù)連接的某個(gè) C++ 類,并且該數(shù)據(jù)庫(kù)可能會(huì)被其他人更改.)
Unfortunately the DataContainer
might be changed by threads/processes. (For example think of DataContainer
as some C++ class that encapsulates a database connection and that database might be changed by someone else.)
問題:
1) 假設(shè)我有一個(gè)函數(shù),每次 DataContainer
對(duì)象的內(nèi)部結(jié)構(gòu)發(fā)生變化時(shí)都會(huì)調(diào)用該函數(shù).必須調(diào)用 QAbstractTableModel
來通知模型底層更改的正確函數(shù)是什么?我需要類似親愛的模型,您的持久存儲(chǔ)后端已更改.請(qǐng)更新自己并向每個(gè)附加視圖發(fā)出信號(hào)以反映此更改".
1) Assume I have a function that is called everytime the internal structur of the DataContainer
object has been changed. What is the correct function of the QAbstractTableModel
that must be called to inform the model of the underlying change? I need something like "Dear Model, your persistent storage backend changed. Please, update yourself and emit a signal to every attached view in order to reflect this change".
2) 假設(shè) 1) 已解決.如果更改是通過 GUI 觸發(fā)的,那么避免雙重"GUI 更新的最佳方法是什么?例如:用戶單擊表格小部件中的單元格 -> 表格小部件調(diào)用模型的 setData
-> 模型將更改推送到后端 -> 后端觸發(fā)其自己的onUpdate"功能 -> 模型重新讀取完整的后端(盡管它已經(jīng)知道更改)-> 第二次更新 GUI
2) Lets say 1) is solved. What is the best way to avoid a "double" GUI update in case the change was triggered through the GUI? E.g: User clicks on a cell in the table widget -> table widget calls setData
of the model -> model pushes change to backend -> backend triggers its own "onUpdate" function -> model re-reads complete backend (although it already knows the change) -> GUI is updated a second time
3) 用戶應(yīng)該能夠通過 GUI 插入新的行/列并將數(shù)據(jù)放入其中.但是位置是由這個(gè)數(shù)據(jù)決定的,因?yàn)楹蠖吮3謹(jǐn)?shù)據(jù)的排序.因此,我有以下問題:用戶決定在最后創(chuàng)建一個(gè)新行,并將新數(shù)據(jù)推送到后端.當(dāng)重新讀取后端/模型時(shí),此數(shù)據(jù)通常不在最后位置,而是已插入中間某處,并且所有其他數(shù)據(jù)已向前移動(dòng).我如何保持表格視圖小部件的所有屬性(如選擇單元格")同步?
3) The user should be able to insert new rows/columns through the GUI and put data into it. But the position is detemined by this data, because the backend keeps the data sorted. Hence, I have the following problem: The user decides to create a new row at the end and the new data is pushed to the backend. When the backend/model is re-read this data is normally not at the last position, but has been inserted somewhere in the middle and all other data has been moved forward. Ho do I keep all the properties of the the table view widget like "selection of a cell" in sync?
我相信,對(duì)于所有這些問題,一定有一些簡(jiǎn)單的標(biāo)準(zhǔn)解決方案,因?yàn)樗c QFileSystemModel
的工作方式相同.用戶選擇一個(gè)文件,其他一些進(jìn)程創(chuàng)建一個(gè)新文件.新文件顯示在視圖中,所有后續(xù)行向前移動(dòng).選擇也向前推進(jìn).
I believe, there must be some simple standard solution to all these question, because it is the same way as QFileSystemModel
works. The user selects a file and some other process creates a new file. The new file is displayed in the view and all subsequent rows move forward. The selection moves forward, too.
馬蒂亞斯
推薦答案
模型語(yǔ)義
首先要保證QAbstractItemModel
不能處于不一致的狀態(tài).這意味著必須在模型上觸發(fā)一些信號(hào), 在對(duì)基礎(chǔ)數(shù)據(jù)進(jìn)行某些更改之前.
Model Semantics
First of all, you must ensure that the QAbstractItemModel
cannot be in an inconsistent state. This means that there are some signals that must be fired on the model before certain changes to the underlying data are done.
結(jié)構(gòu)更改和數(shù)據(jù)更改之間存在根本區(qū)別.結(jié)構(gòu)更改是添加或刪除模型的行/列.數(shù)據(jù)更改僅影響現(xiàn)有數(shù)據(jù)項(xiàng)的值.
There is a fundamental difference between changes to structure and changes to data. Structure changes are the rows/columns of the model being added or removed. Data changes affect the value of existing data items only.
結(jié)構(gòu)更改需要在修改前后調(diào)用
beginXxx
和endXxx
.在調(diào)用beginXxx
之前,您不能修改任何結(jié)構(gòu).完成結(jié)構(gòu)更改后,調(diào)用endXxx
.Xxx
是以下之一:InsertColumns
、MoveColumns
、RemoveColumns
、InsertRows
、MoveRows
、RemoveRows
、ResetModel
.
Structural changes require calling
beginXxx
andendXxx
around the modification. You cannot modify any structure before callingbeginXxx
. When you're done changing the structure, callendXxx
.Xxx
is one of:InsertColumns
,MoveColumns
,RemoveColumns
,InsertRows
,MoveRows
,RemoveRows
,ResetModel
.
如果更改影響許多不連續(xù)的行/列,則指示模型重置會(huì)更便宜 - 但要注意視圖上的選擇可能無法生存.
If the changes affect many discontiguous rows/columns, it's cheaper to indicate a model reset - but be wary that selections on the views might not survive it.
保持結(jié)構(gòu)完整的數(shù)據(jù)更改只需要在修改基礎(chǔ)數(shù)據(jù)之后發(fā)送dataChanged
.這意味著在查詢模型的對(duì)象接收到 dataChanged
之前,對(duì) data
的調(diào)用可能會(huì)返回一個(gè)新值.
Data changes that keep the structure intact merely require that dataChanged
is sent after the underlying data was modified. This means that there is a window of time when a call to data
might return a new value before dataChanged
is received by the object that queries the model.
這也意味著非QObject
類中的非常量模型幾乎沒有用,除非您當(dāng)然使用觀察者或類似模式實(shí)現(xiàn)橋接功能.
This also implies that non-constant models are almost useless from non-QObject
classes, unless of course you implement bridge functionality using observer or similar patterns.
處理模型更新循環(huán)的 Qt 慣用方法是利用項(xiàng)目角色.模型如何解釋角色完全取決于您.QStringListModel
實(shí)現(xiàn)的一個(gè)簡(jiǎn)單而有用的行為是將角色從 setData
調(diào)用轉(zhuǎn)發(fā)到 dataChanged
,否則忽略該角色.
The Qt-idiomatic way of dealing with update loops on the model is by leveraging the item roles. It's entirely up to you how your model interprets the roles. A simple and useful behavior implemented by QStringListModel
is simply to forward the role from the setData
call to dataChanged
, otherwise ignoring the role.
股票視圖小部件僅對(duì)帶有 DisplayRole
的 dataChanged
做出反應(yīng).然而,當(dāng)他們編輯數(shù)據(jù)時(shí),他們使用 EditRole
調(diào)用 setData
.這打破了循環(huán).該方法既適用于查看小部件,也適用于 Qt 快速查看項(xiàng)目.
The stock view widgets react only to dataChanged
with the DisplayRole
. Yet, when they edit the data, they call setData
with the EditRole
. This breaks the loop. The approach is applicable both to view widgets and to Qt Quick view items.
只要模型在排序完成時(shí)正確地發(fā)出變化信號(hào),你就會(huì)沒事.
As long as the model properly emits the change signals when the sorting is done, you'll be fine.
操作順序?yàn)?
視圖添加一行并調(diào)用模型的
insertRow
方法.該模型可以將此空行添加到底層容器中,也可以不添加.關(guān)鍵是必須暫時(shí)保留空行索引.
The view adds a row and calls model's
insertRow
method. The model can either add this empty row to the underlying container or not. The key is that the empty row index must be kept for now.
編輯從行中的一個(gè)項(xiàng)目開始.視圖狀態(tài)更改為 Editing
.
The editing starts on an item in the row. The view state changes to Editing
.
對(duì)項(xiàng)目進(jìn)行編輯.視圖退出編輯狀態(tài),并在模型上設(shè)置數(shù)據(jù).
Editing is done on the item. The view exits the editing state, and sets the data on the model.
模型根據(jù)項(xiàng)目的內(nèi)容確定項(xiàng)目的最終位置.
The model determines the final position of the item, based on its contents.
模型調(diào)用beginMoveRows
.
模型通過在正確的位置插入項(xiàng)目來改變?nèi)萜?
The model changes the container by inserting the item at the correct location.
模型調(diào)用endMoveRows
.
此時(shí),一切都如您所愿.如果在移動(dòng)之前獲得焦點(diǎn),則視圖可以自動(dòng)跟隨移動(dòng)的項(xiàng)目.默認(rèn)情況下,編輯的項(xiàng)目具有焦點(diǎn),因此效果很好.
At this point, everything is as you expect it to be. The views can automatically follow the moved item if it was focused prior to being moved. The edited items are focused by default, so that works fine.
您的DataContainer
沒有足夠的功能使其工作除非對(duì)它的所有訪問都通過模型完成.如果你想直接訪問容器,要么讓容器顯式繼承QAbstractXxxxModel
,要么你必須給容器添加一個(gè)通知系統(tǒng).前者是更簡(jiǎn)單的選擇.
Your DataContainer
doesn't have enough functionality to make it work unless all access to it were to be done through the model. If you want to access the container directly, either make the container explicitly inherit QAbstractXxxxModel
, or you'll have to add a notification system to the container. The former is an easier option.
您的核心問題簡(jiǎn)化為:我能否在不實(shí)現(xiàn)模型通知 API 的某些變體的情況下?lián)碛心P凸δ?顯而易見的答案是:不,對(duì)不起,你不能——根據(jù)定義.要么功能存在,要么不存在.如果您不希望容器成為 QObject
,您可以使用觀察者模式實(shí)現(xiàn)通知 API - 那么您將需要您的模型填充類.真的沒有辦法.
Your core question reduces to: can I have model functionality without implementing some variant of the model notification API. The obvious answer is: no, sorry, you can't - by definition. Either the functionality is there, or it isn't. You can implement the notification API using an observer pattern if you don't want the container to be a QObject
- then you'll need your model shim class. There's really no way around it.
QFileSystemModel
由文件系統(tǒng)通知有關(guān)已更改的單個(gè)目錄條目.你的容器也必須這樣做——這相當(dāng)于以某種形狀或形式提供一個(gè) dataChanged
信號(hào).如果模型有被移動(dòng)或添加/刪除的項(xiàng)目 - 它的結(jié)構(gòu)發(fā)生了變化 - 它必須通過調(diào)用相關(guān)的 beginZzz 來發(fā)出
和 xxxAboutToBeYyy
和 xxxYyy
信號(hào)endZzz
方法.
The QFileSystemModel
gets notified by the filesystem about individual directory entries that have changed. Your container has to do the same - and this amounts to providing a dataChanged
signal, in some shape or form. If the model has items that get moved around or added/removed - its structure changes - it has to emit the xxxAboutToBeYyy
and xxxYyy
signals, by calling the relevant beginZzz
and endZzz
methods.
QModelIndex
最重要的未記錄方面是:它的實(shí)例僅在模型結(jié)構(gòu)沒有改變時(shí)才有效.如果您的模型傳遞了在結(jié)構(gòu)更改之前生成的索引,您就可以自由地以未定義的方式行事(崩潰、發(fā)動(dòng)核打擊等).
The most important underdocumented aspect of QModelIndex
is: its instances are only valid for as long as the model's structure hasn't changed. If your model is passed an index that was generated prior to a structure change, you're free to behave in an undefined way (crash, launch a nuclear strike, whatever).
QModelIndex::internalPointer()
存在的全部原因是您擁有一個(gè)基礎(chǔ)的、復(fù)雜索引的數(shù)據(jù)容器的用例.模型的 createIndex
方法的實(shí)現(xiàn)必須生成索引實(shí)例,以某種形式存儲(chǔ)對(duì) DataContainer
索引的引用.如果這些索引適合指針,則不需要在堆上分配數(shù)據(jù).如果需要在堆上分配容器索引存儲(chǔ),則必須保留指向此數(shù)據(jù)的指針,并在容器結(jié)構(gòu)發(fā)生變化時(shí)將其刪除.您可以隨意執(zhí)行此操作,因?yàn)樵诮Y(jié)構(gòu)更改后沒有人應(yīng)該使用索引實(shí)例.
The whole reason for the existence of QModelIndex::internalPointer()
is your use case of having an underlying, complex-indexed data container. Your implementation of the model's createIndex
method must generate index instances that store references to the DataContainer
's indices in some form. If those indices fit in a pointer, you don't need to allocate the data on the heap. If you need to allocate the container index storage on the heap, you must retain a pointer to this data and delete it any time the container's structure changes. You're free to do it, since nobody is supposed to use the index instance after a structure change.
這篇關(guān)于Qt 5.2 Model-View-Pattern:如何通知模型對(duì)象底層數(shù)據(jù)結(jié)構(gòu)的變化的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!