問題描述
我有一個類用于永久存儲一些以表格方式組織的項目.這個類與 Qt 完全無關,來自不同的庫.對于這個問題的其余部分,讓我們調用這個類 DataContainer
.它提供了與 std-c++ 兼容的迭代器來訪問和操作內容.
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 顯示和修改該數據.我的想法是創建一個類 DataContainerQtAdaptor
,它繼承自 QAbstractTableModel
并存儲一個指向 DataContainer
對象的指針.DataContainerQtAdaptor
用作 DataContainer
對象的適配器,我的 Qt 應用程序內部的所有操作都通過此適配器完成.然后我使用 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
可能會被線程/進程更改.(例如,將 DataContainer
視為封裝數據庫連接的某個 C++ 類,并且該數據庫可能會被其他人更改.)
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) 假設我有一個函數,每次 DataContainer
對象的內部結構發生變化時都會調用該函數.必須調用 QAbstractTableModel
來通知模型底層更改的正確函數是什么?我需要類似親愛的模型,您的持久存儲后端已更改.請更新自己并向每個附加視圖發出信號以反映此更改".
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) 假設 1) 已解決.如果更改是通過 GUI 觸發的,那么避免雙重"GUI 更新的最佳方法是什么?例如:用戶單擊表格小部件中的單元格 -> 表格小部件調用模型的 setData
-> 模型將更改推送到后端 -> 后端觸發其自己的onUpdate"功能 -> 模型重新讀取完整的后端(盡管它已經知道更改)-> 第二次更新 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) 用戶應該能夠通過 GUI 插入新的行/列并將數據放入其中.但是位置是由這個數據決定的,因為后端保持數據的排序.因此,我有以下問題:用戶決定在最后創建一個新行,并將新數據推送到后端.當重新讀取后端/模型時,此數據通常不在最后位置,而是已插入中間某處,并且所有其他數據已向前移動.我如何保持表格視圖小部件的所有屬性(如選擇單元格")同步?
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?
我相信,對于所有這些問題,一定有一些簡單的標準解決方案,因為它與 QFileSystemModel
的工作方式相同.用戶選擇一個文件,其他一些進程創建一個新文件.新文件顯示在視圖中,所有后續行向前移動.選擇也向前推進.
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.
馬蒂亞斯
推薦答案
模型語義
首先要保證QAbstractItemModel
不能處于不一致的狀態.這意味著必須在模型上觸發一些信號, 在對基礎數據進行某些更改之前.
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.
結構更改和數據更改之間存在根本區別.結構更改是添加或刪除模型的行/列.數據更改僅影響現有數據項的值.
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.
結構更改需要在修改前后調用
beginXxx
和endXxx
.在調用beginXxx
之前,您不能修改任何結構.完成結構更改后,調用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
.
如果更改影響許多不連續的行/列,則指示模型重置會更便宜 - 但要注意視圖上的選擇可能無法生存.
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.
保持結構完整的數據更改只需要在修改基礎數據之后發送dataChanged
.這意味著在查詢模型的對象接收到 dataChanged
之前,對 data
的調用可能會返回一個新值.
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
類中的非常量模型幾乎沒有用,除非您當然使用觀察者或類似模式實現橋接功能.
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.
處理模型更新循環的 Qt 慣用方法是利用項目角色.模型如何解釋角色完全取決于您.QStringListModel
實現的一個簡單而有用的行為是將角色從 setData
調用轉發到 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.
股票視圖小部件僅對帶有 DisplayRole
的 dataChanged
做出反應.然而,當他們編輯數據時,他們使用 EditRole
調用 setData
.這打破了循環.該方法既適用于查看小部件,也適用于 Qt 快速查看項目.
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.
只要模型在排序完成時正確地發出變化信號,你就會沒事.
As long as the model properly emits the change signals when the sorting is done, you'll be fine.
操作順序為:
視圖添加一行并調用模型的
insertRow
方法.該模型可以將此空行添加到底層容器中,也可以不添加.關鍵是必須暫時保留空行索引.
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.
編輯從行中的一個項目開始.視圖狀態更改為 Editing
.
The editing starts on an item in the row. The view state changes to Editing
.
對項目進行編輯.視圖退出編輯狀態,并在模型上設置數據.
Editing is done on the item. The view exits the editing state, and sets the data on the model.
模型根據項目的內容確定項目的最終位置.
The model determines the final position of the item, based on its contents.
模型調用beginMoveRows
.
模型通過在正確的位置插入項目來改變容器.
The model changes the container by inserting the item at the correct location.
模型調用endMoveRows
.
此時,一切都如您所愿.如果在移動之前獲得焦點,則視圖可以自動跟隨移動的項目.默認情況下,編輯的項目具有焦點,因此效果很好.
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
沒有足夠的功能使其工作除非對它的所有訪問都通過模型完成.如果你想直接訪問容器,要么讓容器顯式繼承QAbstractXxxxModel
,要么你必須給容器添加一個通知系統.前者是更簡單的選擇.
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.
您的核心問題簡化為:我能否在不實現模型通知 API 的某些變體的情況下擁有模型功能.顯而易見的答案是:不,對不起,你不能——根據定義.要么功能存在,要么不存在.如果您不希望容器成為 QObject
,您可以使用觀察者模式實現通知 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
由文件系統通知有關已更改的單個目錄條目.你的容器也必須這樣做——這相當于以某種形狀或形式提供一個 dataChanged
信號.如果模型有被移動或添加/刪除的項目 - 它的結構發生了變化 - 它必須通過調用相關的 beginZzz 來發出
和 xxxAboutToBeYyy
和 xxxYyy
信號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
最重要的未記錄方面是:它的實例僅在模型結構沒有改變時才有效.如果您的模型傳遞了在結構更改之前生成的索引,您就可以自由地以未定義的方式行事(崩潰、發動核打擊等).
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()
存在的全部原因是您擁有一個基礎的、復雜索引的數據容器的用例.模型的 createIndex
方法的實現必須生成索引實例,以某種形式存儲對 DataContainer
索引的引用.如果這些索引適合指針,則不需要在堆上分配數據.如果需要在堆上分配容器索引存儲,則必須保留指向此數據的指針,并在容器結構發生變化時將其刪除.您可以隨意執行此操作,因為在結構更改后沒有人應該使用索引實例.
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.
這篇關于Qt 5.2 Model-View-Pattern:如何通知模型對象底層數據結構的變化的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!