使用键盘在带有 QTreeView 的 QComboBox 中控制所选项目

huangapple go评论101阅读模式
英文:

controling selected item with keyboard in a QCombobox with QTreeView

问题

使用Qt 6.2.4和Ubuntu环境,我从QComboBox派生出一个控件,将QTreeView设置为其视图。

它包含一个包含多个父项和子项的树(文件夹和文件),可能有多层文件夹。

一切都正常工作,表现如我所希望的那样。

现在,当选择一个项目时,我想使用上下箭头键来选择同一父项下的前一个或后一个项目。

我尝试了几种方法,我可以获取正确的兄弟项目,但无法相应地更新comboBox视图(comboBox始终显示之前选择的项目,没有更新)。

main.cpp:

  1. #include <QApplication>
  2. #include "tree-combobox.h"
  3. int main(int argc, char **argv)
  4. {
  5. QApplication app (argc, argv);
  6. TreeComboBox combo;
  7. combo.setGeometry(0, 0, 400, 50);
  8. combo.ShowFileList("/my/path/", "*");
  9. combo.show();
  10. return app.exec();
  11. }

tree-combobox.h:

  1. #include <QComboBox>
  2. #include <QTreeView>
  3. #include <QFileSystemModel>
  4. #include <QMouseEvent>
  5. #include <QAbstractItemView>
  6. class TreeComboBox : public QComboBox
  7. {
  8. public:
  9. TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
  10. {
  11. QTreeView* tree = new QTreeView(this); // 用于comboBox的树视图
  12. setView(tree); // 将其分配给comboBox
  13. }
  14. void ShowFileList(QString path, QString filesFilter) // 用文件夹和文件填充comboBox,指定路径和通配符
  15. {
  16. // 阻止信号
  17. this->blockSignals(true); // 填充控件时不发出信号
  18. //// 创建文件模型
  19. QFileSystemModel *fileModel = new QFileSystemModel(this); // 要使用的文件系统模型
  20. // 设置文件模型选项
  21. fileModel->setReadOnly(true); // 设置为只读
  22. fileModel->setFilter(QDir::AllDirs | QDir::AllEntries | QDir::NoDotAndDotDot); // 所有文件夹,所有文件,不以点开头的文件
  23. fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons); // 不使用文件的图标
  24. fileModel->setOption(QFileSystemModel::DontWatchForChanges); // 控件不会跟踪磁盘上的更改
  25. // 设置文件模型的文件过滤器
  26. QStringList filter; // 用于文件通配符
  27. filter << filesFilter;
  28. fileModel->setNameFilters(filter);
  29. // 设置文件模型的根目录
  30. fileModel->setRootPath("");
  31. //// 创建视图
  32. // 树视图
  33. QTreeView *view = new QTreeView;
  34. this->setView(view); // 将树视图分配给comboBox
  35. // 文件模型
  36. this->setModel(fileModel); // 将文件模型分配给comboBox
  37. // 删除树视图中的列,只保留文件名
  38. QModelIndex index = fileModel->index(path);
  39. for (int i = 1; i < fileModel->columnCount(); ++i) // 除第一列外的所有列
  40. view->hideColumn(i);
  41. // 树视图选项
  42. view->setAnimated(true); // 启用动画
  43. view->setSortingEnabled(true); // 通过单击标题启用排序
  44. view->sortByColumn(0, Qt::AscendingOrder); // 排序值
  45. view->expand(index); // 展开给定路径的视图
  46. view->scrollTo(index); // 从给定路径设置视图
  47. view->setRootIndex(index); // 设置根索引为给定路径
  48. // 再次允许信号
  49. this->blockSignals(false);
  50. }
  51. QString GetFile() // 获取列表中的选定值
  52. // 每次单击项目时都会发出currentItemChanged()信号,即使是父项目
  53. // 如果单击的项目无效(即文件夹),则此函数返回空QString
  54. {
  55. QModelIndex index = view()->currentIndex(); // 从QTree获取当前值索引
  56. QString path = model()->data(index, QFileSystemModel::FilePathRole).toString(); // 获取完整的路径值
  57. QFileInfo info(path); // 用于测试此值
  58. if (info.isFile()) // 如果值确实是文件
  59. return path; // ...返回其完整路径
  60. else // 值是文件夹
  61. return QString(); // ...所以什么都不返回
  62. }
  63. private:
  64. virtual void hidePopup() // 控制弹出窗口隐藏行为
  65. // 对于comboBox,每次单击项目时,弹出窗口都会消失...但这次不是为文件夹!
  66. {
  67. if (!view()->underMouse()) { // 鼠标是否位于QTreeView上?
  68. QComboBox::hidePopup(); // 如果没有,折叠comboBox
  69. return;
  70. }
  71. QModelIndex index = view()->currentIndex(); // 获取所选项目的当前索引
  72. if (!model()->hasChildren(index)) // 如果没有子项目(因此不是文件夹)
  73. QComboBox::hidePopup(); // 折叠comboBox
  74. }
  75. virtual void keyPressEvent(QKeyEvent *keyboardEvent) // 键盘事件
  76. {
  77. if (this->hasFocus()) { // 控件必须处于活动状态才能接受键盘按键
  78. if (keyboardEvent->key() == Qt::Key_Up) { // 上
  79. //view()->setCurrentIndex(view()->currentIndex().sibling(view()->currentIndex().row() - 1, 0));
  80. //view()->selectionModel()->select(view()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
  81. QModelIndex index = view()->currentIndex();
  82. int n = index.row() - 1;
  83. QModelIndex sibling = index.siblingAtRow(n);
  84. if (sibling.isValid()) {
  85. view()->setCurrentIndex(sibling);
  86. view()->scrollTo(sibling);
  87. //view()->selectionModel()->setCurrentIndex(sibling, QItemSelectionModel::ClearAndSelect);
  88. //view()->selectionModel()->select(sibling, QItemSelectionModel::Select | QItemSelectionModel::Rows);
  89. //tree->selectionModel()->select(tree->currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
  90. //this->setCurrentIndex(sibling.row());
  91. }
  92. keyboardEvent->accept(); // 接受键盘事件
  93. <details>
  94. <summary>英文:</summary>
  95. Using Qt 6.2.4, Ubuntu environment, I derived from `QComboBox` to set a `QTreeView` as its view.
  96. It contains a tree (folders and files) with multiple parents and children, several levels of folders is possible.
  97. All is working fine and behaves as I want.
  98. Now, when one item is selected I&#39;d like to use up and down arrow keys to select the previous or next item of the same parent.
  99. I tried several things, I get the right sibling but I am not able to update the comboBox view accordingly (the comboBox always shows the item selected before, nothing is updated).
  100. **main.cpp:**
  101. #include &lt;QApplication&gt;
  102. #include &quot;tree-combobox.h&quot;
  103. int main(int argc, char **argv)
  104. {
  105. QApplication app (argc, argv);
  106. TreeComboBox combo;
  107. combo.setGeometry(0, 0, 400, 50);
  108. combo.ShowFileList(&quot;/my/path/&quot;, &quot;*&quot;);
  109. combo.show();
  110. return app.exec();
  111. }
  112. **tree-combobox.h:**
  113. #include &lt;QComboBox&gt;
  114. #include &lt;QTreeView&gt;
  115. #include &lt;QFileSystemModel&gt;
  116. #include &lt;QMouseEvent&gt;
  117. #include &lt;QAbstractItemView&gt;
  118. class TreeComboBox : public QComboBox
  119. {
  120. public:
  121. TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
  122. {
  123. QTreeView* tree = new QTreeView(this); // tree view for combobox
  124. setView(tree); // assign it to combobox
  125. }
  126. void ShowFileList(QString path, QString filesFilter) // fill combobox with folders and files, specify path and wildcard(s)
  127. {
  128. // block signals
  129. this-&gt;blockSignals(true); // no signals emitted while stuffing the widget
  130. //// create files model
  131. QFileSystemModel *fileModel = new QFileSystemModel(this); // file system model to use
  132. // set options to file model
  133. fileModel-&gt;setReadOnly(true); // set it read-only
  134. fileModel-&gt;setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot); // all folders, all files, no file beginning with a dot
  135. fileModel-&gt;setOption(QFileSystemModel::DontUseCustomDirectoryIcons); // don&#39;t use icons from the files
  136. fileModel-&gt;setOption(QFileSystemModel::DontWatchForChanges); // the widget won&#39;t track changes on disk
  137. // set file filter for files model
  138. QStringList filter; // for files wildcard
  139. filter &lt;&lt; filesFilter;
  140. fileModel-&gt;setNameFilters(filter);
  141. // set root for files model
  142. fileModel-&gt;setRootPath(&quot;&quot;);
  143. //// create view
  144. // tree view
  145. QTreeView *view = new QTreeView;
  146. this-&gt;setView(view); // assign tree view to combobox
  147. // files model
  148. this-&gt;setModel(fileModel); // assign files model to combobox
  149. // remove columns in tree view, to keep only filenames
  150. QModelIndex index = fileModel-&gt;index(path);
  151. for (int i = 1; i &lt; fileModel-&gt;columnCount(); ++i) // all columns but first one
  152. view-&gt;hideColumn(i);
  153. // tree view options
  154. view-&gt;setAnimated(true); // animated
  155. view-&gt;setSortingEnabled(true); // sorting enabled by clicking on header
  156. view-&gt;sortByColumn(0, Qt::AscendingOrder); // sort values
  157. view-&gt;expand(index); // expand the view from the given path
  158. view-&gt;scrollTo(index); // set view from given path
  159. view-&gt;setRootIndex(index); // set root index to given path
  160. // allow signals again
  161. this-&gt;blockSignals(false);
  162. }
  163. QString GetFile() // get selected value from list
  164. // currentItemChanged() is emitted each time an item is clicked, even a parent item
  165. // this function returns an empty QString if the clicked item is not valid (i.e. a folder)
  166. {
  167. QModelIndex index = view()-&gt;currentIndex(); // current value index from QTree
  168. QString path = model()-&gt;data(index, QFileSystemModel::FilePathRole).toString(); // get full path value
  169. QFileInfo info(path); // to test this value
  170. if (info.isFile()) // if the value is really a file
  171. return path; // ... return its full path
  172. else // value is a folder
  173. return QString(); // ... so return nothing
  174. }
  175. private:
  176. virtual void hidePopup() // control popup hiding behaviour
  177. // for a combobox, each time an item is clicked the popup disappears... but not for a folder this time !
  178. {
  179. if (!view()-&gt;underMouse()) { // is mouse over QTreeView ?
  180. QComboBox::hidePopup(); // if not collapse the comboBox
  181. return;
  182. }
  183. QModelIndex index = view()-&gt;currentIndex(); // get current index of selected item
  184. if (!model()-&gt;hasChildren(index)) // if it doesn&#39;t have children (so it is not a folder)
  185. QComboBox::hidePopup(); // collapse the comboBox
  186. }
  187. virtual void keyPressEvent(QKeyEvent *keyboardEvent) // keyboard event
  188. {
  189. if (this-&gt;hasFocus()) { // widget has to be active to accept keyboard keys
  190. if (keyboardEvent-&gt;key() == Qt::Key_Up) { // up
  191. //view()-&gt;setCurrentIndex(view()-&gt;currentIndex().sibling(view()-&gt;currentIndex().row() - 1, 0));
  192. //view()-&gt;selectionModel()-&gt;select(view()-&gt;currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
  193. QModelIndex index = view()-&gt;currentIndex();
  194. int n = index.row() - 1;
  195. QModelIndex sibling = index.siblingAtRow(n);
  196. if (sibling.isValid()) {
  197. view()-&gt;setCurrentIndex(sibling);
  198. view()-&gt;scrollTo(sibling);
  199. //view()-&gt;selectionModel()-&gt;setCurrentIndex(sibling, QItemSelectionModel::ClearAndSelect);
  200. //view()-&gt;selectionModel()-&gt;select(sibling, QItemSelectionModel::Select | QItemSelectionModel::Rows);
  201. //tree-&gt;selectionModel()-&gt;select(tree-&gt;currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
  202. //this-&gt;setCurrentIndex(sibling.row());
  203. }
  204. keyboardEvent-&gt;accept(); // accept keyboard event
  205. }
  206. else if (keyboardEvent-&gt;key() == Qt::Key_Down) { // down
  207. view()-&gt;setCurrentIndex(view()-&gt;currentIndex().sibling(view()-&gt;currentIndex().row() - 1, 0));
  208. keyboardEvent-&gt;accept();
  209. }
  210. }
  211. }
  212. };
  213. What&#39;s working so far in `keyPressEvent()`: the `sibling` index (`QModelIndex`) is the right one.
  214. What I tried: the lines commented out with `//`.
  215. Desired result: the comboBox selects and shows the previous or next item in the list, for the same parent (no need to go up or down to another parent).
  216. Thanks in advance !
  217. </details>
  218. # 答案1
  219. **得分**: 3
  220. 以下是翻译好的代码部分:
  221. ```cpp
  222. class TreeComboBox : public QComboBox
  223. {
  224. public:
  225. // 添加一个新的成员来保存根索引
  226. QModelIndex rootIndex;
  227. TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
  228. {
  229. QTreeView* tree = new QTreeView(this);
  230. setView(tree);
  231. }
  232. void ShowFileList(QString path, QString filesFilter)
  233. {
  234. this->blockSignals(true);
  235. QFileSystemModel *fileModel = new QFileSystemModel(this);
  236. fileModel->setReadOnly(true);
  237. fileModel->setFilter(QDir::AllDirs | QDir::AllEntries | QDir::NoDotAndDotDot);
  238. fileModel->setOption(QFileSystemModel::DontUseCustomDirectoryIcons);
  239. fileModel->setOption(QFileSystemModel::DontWatchForChanges);
  240. QStringList filter;
  241. filter << filesFilter;
  242. fileModel->setNameFilters(filter);
  243. fileModel->setRootPath("");
  244. QTreeView *view = new QTreeView;
  245. this->setView(view);
  246. this->setModel(fileModel);
  247. QModelIndex index = fileModel->index(path);
  248. for (int i = 1; i < fileModel->columnCount(); ++i)
  249. view->hideColumn(i);
  250. view->setAnimated(true);
  251. view->setSortingEnabled(true);
  252. view->sortByColumn(0, Qt::AscendingOrder);
  253. view->expand(index);
  254. view->scrollTo(index);
  255. view->setRootIndex(index);
  256. // 保存根索引
  257. rootIndex = index;
  258. // 然后设置给你的 comboBox
  259. setRootModelIndex(index);
  260. this->blockSignals(false);
  261. }
  262. QString GetFile()
  263. {
  264. QModelIndex index = view()->currentIndex();
  265. QString path = model()->data(index, QFileSystemModel::FilePathRole).toString();
  266. QFileInfo info(path);
  267. if (info.isFile())
  268. return path;
  269. else
  270. return QString();
  271. }
  272. private:
  273. virtual void hidePopup()
  274. {
  275. if (!view()->underMouse()) {
  276. QComboBox::hidePopup();
  277. return;
  278. }
  279. QModelIndex index = view()->currentIndex();
  280. if (!model()->hasChildren(index))
  281. QComboBox::hidePopup();
  282. }
  283. virtual void keyPressEvent(QKeyEvent *keyboardEvent)
  284. {
  285. if (this->hasFocus())
  286. {
  287. // 我只是用这个来避免必须通过点击来选择项目
  288. // 如果对你没有用,可以删除它
  289. if(!view()->currentIndex().isValid())
  290. {
  291. view()->setCurrentIndex(view()->indexAt(QPoint(0,0)));
  292. }
  293. if (keyboardEvent->key() == Qt::Key_Up)
  294. {
  295. QModelIndex index = view()->currentIndex();
  296. int n = index.row() - 1;
  297. QModelIndex sibling = index.siblingAtRow(n);
  298. if (sibling.isValid())
  299. {
  300. // 更新视图的当前索引
  301. view()->setCurrentIndex(sibling);
  302. // 更新组合框
  303. setRootModelIndex(sibling.parent());
  304. setCurrentIndex(sibling.row());
  305. // 这是保存根索引不丢失的地方
  306. if(rootModelIndex() != rootIndex)
  307. setRootModelIndex(rootIndex);
  308. }
  309. keyboardEvent->accept();
  310. }
  311. else if (keyboardEvent->key() == Qt::Key_Down)
  312. {
  313. QModelIndex index = view()->currentIndex();
  314. int n = index.row() + 1;
  315. QModelIndex sibling = index.siblingAtRow(n);
  316. if(sibling.isValid())
  317. {
  318. // 更新视图的当前索引
  319. view()->setCurrentIndex(sibling);
  320. // 更新组合框
  321. setRootModelIndex(sibling.parent());
  322. setCurrentIndex(sibling.row());
  323. if(rootModelIndex() != rootIndex)
  324. setRootModelIndex(rootIndex);
  325. }
  326. keyboardEvent->accept();
  327. }
  328. }
  329. }
  330. };

希望这对你有所帮助。

英文:

Both the tree view and combobox need to be updated on key press, in order for the items to be visibly updated on the combobox.

And since it appears a rootModelIndex needs to be set, and the initial root getting lost on subfolders, I added a QModelIndex as a member to store it in.

Modified class with explanatory comments:

  1. class TreeComboBox : public QComboBox
  2. {
  3. public:
  4. //add a new memeber to save the root index
  5. QModelIndex rootIndex;
  6. TreeComboBox(QWidget* parent = 0) : QComboBox(parent)
  7. {
  8. QTreeView* tree = new QTreeView(this);
  9. setView(tree);
  10. }
  11. void ShowFileList(QString path, QString filesFilter)
  12. {
  13. this-&gt;blockSignals(true);
  14. QFileSystemModel *fileModel = new QFileSystemModel(this);
  15. fileModel-&gt;setReadOnly(true);
  16. fileModel-&gt;setFilter(QDir::AllDirs | QDir::AllEntries |QDir::NoDotAndDotDot);
  17. fileModel-&gt;setOption(QFileSystemModel::DontUseCustomDirectoryIcons);
  18. fileModel-&gt;setOption(QFileSystemModel::DontWatchForChanges);
  19. QStringList filter;
  20. filter &lt;&lt; filesFilter;
  21. fileModel-&gt;setNameFilters(filter);
  22. fileModel-&gt;setRootPath(&quot;&quot;);
  23. QTreeView *view = new QTreeView;
  24. this-&gt;setView(view);
  25. this-&gt;setModel(fileModel);
  26. QModelIndex index = fileModel-&gt;index(path);
  27. for (int i = 1; i &lt; fileModel-&gt;columnCount(); ++i)
  28. view-&gt;hideColumn(i);
  29. view-&gt;setAnimated(true);
  30. view-&gt;setSortingEnabled(true);
  31. view-&gt;sortByColumn(0, Qt::AscendingOrder);
  32. view-&gt;expand(index);
  33. view-&gt;scrollTo(index);
  34. view-&gt;setRootIndex(index);
  35. //save the root index
  36. rootIndex=index;
  37. //then set to your comboBox
  38. setRootModelIndex(index);
  39. this-&gt;blockSignals(false);
  40. }
  41. QString GetFile()
  42. {
  43. QModelIndex index = view()-&gt;currentIndex();
  44. QString path = model()-&gt;data(index, QFileSystemModel::FilePathRole).toString();
  45. QFileInfo info(path);
  46. if (info.isFile())
  47. return path;
  48. else
  49. return QString();
  50. }
  51. private:
  52. virtual void hidePopup()
  53. {
  54. if (!view()-&gt;underMouse()) {
  55. QComboBox::hidePopup();
  56. return;
  57. }
  58. QModelIndex index = view()-&gt;currentIndex();
  59. if (!model()-&gt;hasChildren(index))
  60. QComboBox::hidePopup();
  61. }
  62. virtual void keyPressEvent(QKeyEvent *keyboardEvent)
  63. {
  64. if (this-&gt;hasFocus())
  65. {
  66. //I just used this to avoid having to select an item by clicking on it
  67. //you can remove it if it&#39;s of no use to you
  68. if(!view()-&gt;currentIndex().isValid())
  69. {
  70. view()-&gt;setCurrentIndex(view()-&gt;indexAt(QPoint(0,0)));
  71. }
  72. if (keyboardEvent-&gt;key() == Qt::Key_Up)
  73. {
  74. QModelIndex index = view()-&gt;currentIndex();
  75. int n = index.row() - 1;
  76. QModelIndex sibling = index.siblingAtRow(n);
  77. if (sibling.isValid())
  78. {
  79. //update view&#39;s current index
  80. view()-&gt;setCurrentIndex(sibling);
  81. //update combobox
  82. setRootModelIndex(sibling.parent());
  83. setCurrentIndex(sibling.row());
  84. //this is where you save the root index from being lost
  85. if(rootModelIndex()!=rootIndex)
  86. setRootModelIndex(rootIndex);
  87. }
  88. keyboardEvent-&gt;accept();
  89. }
  90. else
  91. if (keyboardEvent-&gt;key() == Qt::Key_Down)
  92. {
  93. QModelIndex index = view()-&gt;currentIndex();
  94. int n = index.row() + 1;
  95. QModelIndex sibling = index.siblingAtRow(n);
  96. if(sibling.isValid())
  97. {
  98. //update view&#39;s current index
  99. view()-&gt;setCurrentIndex(sibling);
  100. //update combobox
  101. setRootModelIndex(sibling.parent());
  102. setCurrentIndex(sibling.row());
  103. if(rootModelIndex()!=rootIndex)
  104. setRootModelIndex(rootIndex);
  105. }
  106. keyboardEvent-&gt;accept();
  107. }
  108. }
  109. }
  110. };

Result:

使用键盘在带有 QTreeView 的 QComboBox 中控制所选项目

For more:

huangapple
  • 本文由 发表于 2023年7月7日 06:27:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定