Qt(通过 Common Lisp 的 qtools):QLineEdit 在点击时不激活,但在 TAB 上获得焦点
Qt (via Common Lisp's qtools): QLineEdit does not activate on click but receives focus on TAB
问题
我运行遇到了一个相当奇怪的问题:界面中的QLineEdit
object无法通过鼠标点击访问,但使用Tab
的他们接收焦点并响应键盘输入。但即使他们有焦点,他们也不会响应点击。更奇怪的是,最后添加的 QLineEdit
确实对点击有反应。
额外问题:mouse-inaccessible line-edits 不显示 tool-tips.
我正在使用 Common Lisp 的 qtools 构建接口,因此我将不得不做一些解释它是如何工作的。简而言之,我在循环中添加 line-edits one-by-one,所有这些都以相同的方式创建并添加到 QGridLayout
.
已更新:请参阅下文了解问题的可能来源
参数小部件
参数控件用于表示一个参数输入。每个参数都有一个名称、值和(度量)单位。名称和单位显示为标签,值是可编辑的并由 QLineEdit
表示
(define-widget parameter-widget (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget label) (q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-base-name parameter)))
(define-subwidget (parameter-widget entry) (q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter)))
(setf (q+:tool-tip entry) (parameter-base-description parameter)))
(define-subwidget (parameter-widget units) (q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
为了表明参数值已经改变,parameter-widget
将发出新的信号 parameter-changed
(它被转换为 Qt 的信号 "parameterChanged"):
(define-signal (parameter-widget parameter-changed) ())
行编辑的插槽尝试将文本转换为数字,如果成功,则更新基础参数并发出 parameter-changed
:
(define-slot (parameter-widget parameter-changed) ((new-text string))
(declare (connected entry (text-changed string)))
(handler-case
(let ((new-number (parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
这个方法只是自动构造parameter
类型的object的widget(工厂方法):
(defmethod make-parameter-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
parameter-container
(请参见further-below)中的多个单个参数需要此方法来对齐标签:
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)))
参数容器
因此,如果我单独使用 parameter-widget
- 一切正常。但大多数时候我需要 parameter-container
:
形式的多个参数
(define-widget parameter-container-widget (QWidget)
((parameter-container :initarg :parameter-container)))
此插槽捕获来自所有 children parameter
的信号 parameter-changed
和 re-emits 容器
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
layout
是 QGridLayout
为 parameter-container
的所有 parameter
-children 放置所有个体 parameter-widget
。它所做的一切:
- 它遍历所有单独的参数,
- 为每个
构造parameter-widget
- 添加到
layout
row-by-row
- 并将信号
parameter-changed
连接到 above-defined 插槽。
代码:
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(loop for p in (parameter-container-children parameter-container)
for row from 0
do (let ((parameter-widget (make-parameter-ui p)))
(setf (q+:parent parameter-widget) parameter-container-widget)
(add-to-grid parameter-widget layout row 0)
(connect! parameter-widget (parameter-changed)
parameter-container-widget
(parameter-changed)))))
此方法统一处理parameter-widget
和parameter-container
:
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column 1 1))
和泛型实例化器(工厂方法):
(defmethod make-parameter-ui ((object parameter-container))
(make-instance 'parameter-container-widget :parameter-container object))
系统信息
Qt4.8,Common Lisp:SBCL 1.3.7,来自 Quicklisp 的 qtools,OS:Ubuntu16.04
更新
Windows10
同样的问题
更新 2:完整的最小示例
抱歉,上面的代码使用了其他包中的许多定义:全部放上去会很长。最小的例子归结为:
(ql:quickload '(:qtools :qtcore :qtgui))
(defpackage :qtexample
(:use #:cl+qt))
(in-package qtexample)
(in-readtable :qtools)
(defclass parameter ()
((name :initarg :name :accessor parameter-name)
(value :initarg :value :accessor parameter-value)
(units :initarg :units :accessor parameter-units)))
(defun parameter (name value units)
(make-instance 'parameter
:name name
:value value
:units units))
(defvar *mass* (parameter "mass" 1d0 "kg"))
(defvar *velocity* (parameter "velocity" 0.5d0 "m/s"))
(defvar *temperature* (parameter "temperature" 300d0 "K"))
(defvar *pressure* (parameter "pressure" 1d5 "Pa"))
(define-widget parameter-widget (QWidget)
((parameter
:initarg :parameter
:accessor parameter-widget-parameter)))
(define-subwidget (parameter-widget label)
(q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-name parameter)))
(defmethod make-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
(defconstant +align-right+ 2)
(define-subwidget (parameter-widget entry)
(q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter))))
(define-subwidget (parameter-widget units)
(q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
(define-signal (parameter-widget parameter-changed) ())
(define-slot (parameter-widget entry) ((new-text string))
(declare (connected entry (text-changed string)))
(format t "~&Parameter has changed~%")
(handler-case
(let ((new-number (parse-number:parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)
(list (1+ row) (+ column 3))))
(define-widget parameter-widget-window (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget-window parameter-widget)
(make-ui parameter))
(define-subwidget (parameter-widget-window grid)
(q+:make-qgridlayout parameter-widget-window)
(add-to-grid parameter-widget grid 0 0))
(defun parameter-example (parameter)
(with-main-window
(window (make-instance 'parameter-widget-window
:parameter parameter))))
(define-widget parameter-container-widget (QWidget)
((parameter-container
:initarg :parameter-container
:accessor parameter-container-widget-parameter-container)))
(defmethod make-ui ((object list))
(make-instance 'parameter-container-widget :parameter-container object))
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(let* ((parameter-widgets (loop for p in parameter-container
collect (make-ui p))))
(loop for p in parameter-widgets
for row from 0
do (progn
(setf (q+:parent p) parameter-container-widget)
(add-to-grid p layout row 0)
(connect! p
(parameter-changed)
parameter-container-widget
(parameter-changed))))))
(define-widget parameter-container-widget-window (QWidget)
((parameter-container :initarg :parameter-container)))
(define-subwidget (parameter-container-widget-window container-widget)
(make-ui parameter-container)
(setf (q+:parent container-widget) parameter-container-widget-window))
(define-slot (parameter-container-widget-window parameter-changed) ()
(declare (connected container-widget (parameter-changed)))
(format t "~&Got parameter changed~%"))
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column))
(defun example-parameter-container (parameter-container)
(with-main-window
(window (make-instance 'parameter-container-widget-window
:parameter-container parameter-container))))
;; to run:
(example-parameter-container (list *mass* *velocity*))
在处理它时,我偶然发现了一个可能的解决方案。这一行
(setf (q+:parent p) parameter-container-widget)
将 sub-widget p
(在 sub-widget 的列表中)的 parent 设置为 parameter-container-widget
。如果注释掉这一行,一切正常。
p
的 sub-widget(包括 entry
,QLineEdit
的实例)后来被添加到网格中,但不包括 p
本身!在某种程度上,parameter-widget
不是一个合适的小部件:它只是其他小部件的集合,具有如何将它们添加到容器中的规则。但它需要充当能够接收和发送信号的小部件。
根据@KubaOber 和@Shinmera 的建议,我设法解决了这个问题。我在这里发布修复以供将来参考。
预选赛没有改变(除了,我忘了添加:PARSE-NUMBER
系统):
(ql:quickload '(:qtools :qtcore :qtgui :parse-number))
(defpackage :qtexample
(:use #:cl+qt))
(in-package qtexample)
(in-readtable :qtools)
(defclass parameter ()
((name :initarg :name :accessor parameter-name)
(value :initarg :value :accessor parameter-value)
(units :initarg :units :accessor parameter-units)))
(defun parameter (name value units)
(make-instance 'parameter
:name name
:value value
:units units))
(defvar *mass* (parameter "mass" 1d0 "kg"))
(defvar *velocity* (parameter "velocity" 0.5d0 "m/s"))
(defvar *temperature* (parameter "temperature" 300d0 "K"))
(defvar *pressure* (parameter "pressure" 1d5 "Pa"))
因为 PARAMETER-WIDGET
不是真正的小部件,而只是其他小部件的容器(并且它本身不能添加到适当的小部件容器中 - 否则标签的对齐方式会消失 - 参见ADD-TO-GRID
方法)但它需要能够接收和发送信号,它继承自QObject
而不是QWidget
。现在所有的子部件都是正常的 class-slots 而不是由 (DEFINE-SUBWIDGET ...)
定义。请注意,没有为任何子小部件提供父级:当这些小部件添加到容器小部件时,将分配父级 属性。
(define-widget parameter-widget (QObject)
((parameter
:initarg :parameter
:accessor parameter-widget-parameter)
(label :initform (q+:make-qlabel))
(entry :initform (q+:make-qlineedit))
(units :initform (q+:make-qlabel))))
初始化进入INITIALIZE-INSTANCE :AFTER
方法:
(defconstant +align-right+ 2)
(defmethod initialize-instance :after ((object parameter-widget) &key)
(with-slots (parameter label entry units) object
(setf (q+:text label) (parameter-name parameter))
(setf (q+:text entry) (format nil "~A" (parameter-value parameter)))
(setf (q+:alignment entry) +align-right+)
(setf (q+:text units) (parameter-units parameter))))
其余定义保持不变(信号大部分与问题无关,但有助于理解我为什么要跳过所有这些箍):
(defmethod make-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
(define-signal (parameter-widget parameter-changed) ())
(define-slot (parameter-widget entry) ((new-text string))
(declare (connected entry (text-changed string)))
(format t "~&Parameter has changed~%")
(handler-case
(let ((new-number (parse-number:parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)
(list (1+ row) (+ column 3))))
这是一个快速演示,一切都适用于单个参数。现在,PARAMETER-WIDGET
不再是一个小部件,因此它被添加为一个 class-slot。
(define-widget parameter-widget-window (QWidget)
((parameter :initarg :parameter)
(parameter-widget)))
window的网格:
(define-subwidget (parameter-widget-window grid)
(q+:make-qgridlayout parameter-widget-window))
此处将 PARAMETER-WIDGET
组件添加到网格中已移出此定义。原因:slot PARAMETER-WIDGET
此时未绑定。它确实在 INITIALIZE-INSTANCE :AFTER
方法中绑定并且所有组件都添加到网格中:
(defmethod initialize-instance :after ((object parameter-widget-window) &key)
(with-slots (parameter parameter-widget grid) object
(setf parameter-widget (make-ui parameter))
(setf (q+:parent parameter-widget) object)
(add-to-grid parameter-widget grid 0 0)))
启动器保持不变:
(defun parameter-example (parameter)
(with-main-window
(window (make-instance 'parameter-widget-window
:parameter parameter))))
至运行:(parameter-example *mass*)
PARAMETER-CONTAINER-WIDGET
是一个合适的小部件。它的定义没有改变:
(define-widget parameter-container-widget (QWidget)
((parameter-container
:initarg :parameter-container
:accessor parameter-container-widget-parameter-container)))
(defmethod make-ui ((object list))
(make-instance 'parameter-container-widget :parameter-container object))
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
和LAYOUT
定义也没有改变。但是现在可以安全地将 p
(PARAMETER-WIDGET
) 的父级 属性 设置为 PARAMETER-WIDGET-CONTAINER
,因此当容器被销毁时它也会被销毁。
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(let* ((parameter-widgets (loop for p in parameter-container
collect (make-ui p))))
(loop for p in parameter-widgets
for row from 0
do (progn
(add-to-grid p layout row 0)
(let ((pp p))
(setf (q+:parent pp) parameter-container-widget))
(connect! p
(parameter-changed)
parameter-container-widget
(parameter-changed))))))
将此小部件添加到网格是微不足道的(但不完全正确,但可以修复,请参见下文):
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column))
演示部分没有变化:
(define-widget parameter-container-widget-window (QWidget)
((parameter-container :initarg :parameter-container)))
(define-subwidget (parameter-container-widget-window container-widget)
(make-ui parameter-container)
(setf (q+:parent container-widget) parameter-container-widget-window))
(define-slot (parameter-container-widget-window parameter-changed) ()
(declare (connected container-widget (parameter-changed)))
(format t "~&Got parameter changed~%"))
(defun example-parameter-container (parameter-container)
(with-main-window
(window (make-instance 'parameter-container-widget-window
:parameter-container parameter-container))))
至运行:(example-parameter-container (list *mass* *velociy*))
它也可以像这样处理分层参数
(example-parameter-container (list *mass* *velocity* (list *temperature* *pressure*)))
而这个 运行 将说明为什么 PARAMETER-CONTAINER-WIDGET
的 ADD-TO-GRID
需要更复杂一点,但这是一个不同的故事。
问题
我运行遇到了一个相当奇怪的问题:界面中的QLineEdit
object无法通过鼠标点击访问,但使用Tab
的他们接收焦点并响应键盘输入。但即使他们有焦点,他们也不会响应点击。更奇怪的是,最后添加的 QLineEdit
确实对点击有反应。
额外问题:mouse-inaccessible line-edits 不显示 tool-tips.
我正在使用 Common Lisp 的 qtools 构建接口,因此我将不得不做一些解释它是如何工作的。简而言之,我在循环中添加 line-edits one-by-one,所有这些都以相同的方式创建并添加到 QGridLayout
.
已更新:请参阅下文了解问题的可能来源
参数小部件
参数控件用于表示一个参数输入。每个参数都有一个名称、值和(度量)单位。名称和单位显示为标签,值是可编辑的并由 QLineEdit
(define-widget parameter-widget (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget label) (q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-base-name parameter)))
(define-subwidget (parameter-widget entry) (q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter)))
(setf (q+:tool-tip entry) (parameter-base-description parameter)))
(define-subwidget (parameter-widget units) (q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
为了表明参数值已经改变,parameter-widget
将发出新的信号 parameter-changed
(它被转换为 Qt 的信号 "parameterChanged"):
(define-signal (parameter-widget parameter-changed) ())
行编辑的插槽尝试将文本转换为数字,如果成功,则更新基础参数并发出 parameter-changed
:
(define-slot (parameter-widget parameter-changed) ((new-text string))
(declare (connected entry (text-changed string)))
(handler-case
(let ((new-number (parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
这个方法只是自动构造parameter
类型的object的widget(工厂方法):
(defmethod make-parameter-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
parameter-container
(请参见further-below)中的多个单个参数需要此方法来对齐标签:
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)))
参数容器
因此,如果我单独使用 parameter-widget
- 一切正常。但大多数时候我需要 parameter-container
:
(define-widget parameter-container-widget (QWidget)
((parameter-container :initarg :parameter-container)))
此插槽捕获来自所有 children parameter
的信号 parameter-changed
和 re-emits 容器
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
layout
是 QGridLayout
为 parameter-container
的所有 parameter
-children 放置所有个体 parameter-widget
。它所做的一切:
- 它遍历所有单独的参数,
- 为每个 构造
- 添加到
layout
row-by-row - 并将信号
parameter-changed
连接到 above-defined 插槽。
parameter-widget
代码:
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(loop for p in (parameter-container-children parameter-container)
for row from 0
do (let ((parameter-widget (make-parameter-ui p)))
(setf (q+:parent parameter-widget) parameter-container-widget)
(add-to-grid parameter-widget layout row 0)
(connect! parameter-widget (parameter-changed)
parameter-container-widget
(parameter-changed)))))
此方法统一处理parameter-widget
和parameter-container
:
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column 1 1))
和泛型实例化器(工厂方法):
(defmethod make-parameter-ui ((object parameter-container))
(make-instance 'parameter-container-widget :parameter-container object))
系统信息
Qt4.8,Common Lisp:SBCL 1.3.7,来自 Quicklisp 的 qtools,OS:Ubuntu16.04
更新
Windows10
同样的问题更新 2:完整的最小示例
抱歉,上面的代码使用了其他包中的许多定义:全部放上去会很长。最小的例子归结为:
(ql:quickload '(:qtools :qtcore :qtgui))
(defpackage :qtexample
(:use #:cl+qt))
(in-package qtexample)
(in-readtable :qtools)
(defclass parameter ()
((name :initarg :name :accessor parameter-name)
(value :initarg :value :accessor parameter-value)
(units :initarg :units :accessor parameter-units)))
(defun parameter (name value units)
(make-instance 'parameter
:name name
:value value
:units units))
(defvar *mass* (parameter "mass" 1d0 "kg"))
(defvar *velocity* (parameter "velocity" 0.5d0 "m/s"))
(defvar *temperature* (parameter "temperature" 300d0 "K"))
(defvar *pressure* (parameter "pressure" 1d5 "Pa"))
(define-widget parameter-widget (QWidget)
((parameter
:initarg :parameter
:accessor parameter-widget-parameter)))
(define-subwidget (parameter-widget label)
(q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-name parameter)))
(defmethod make-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
(defconstant +align-right+ 2)
(define-subwidget (parameter-widget entry)
(q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter))))
(define-subwidget (parameter-widget units)
(q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
(define-signal (parameter-widget parameter-changed) ())
(define-slot (parameter-widget entry) ((new-text string))
(declare (connected entry (text-changed string)))
(format t "~&Parameter has changed~%")
(handler-case
(let ((new-number (parse-number:parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)
(list (1+ row) (+ column 3))))
(define-widget parameter-widget-window (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget-window parameter-widget)
(make-ui parameter))
(define-subwidget (parameter-widget-window grid)
(q+:make-qgridlayout parameter-widget-window)
(add-to-grid parameter-widget grid 0 0))
(defun parameter-example (parameter)
(with-main-window
(window (make-instance 'parameter-widget-window
:parameter parameter))))
(define-widget parameter-container-widget (QWidget)
((parameter-container
:initarg :parameter-container
:accessor parameter-container-widget-parameter-container)))
(defmethod make-ui ((object list))
(make-instance 'parameter-container-widget :parameter-container object))
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(let* ((parameter-widgets (loop for p in parameter-container
collect (make-ui p))))
(loop for p in parameter-widgets
for row from 0
do (progn
(setf (q+:parent p) parameter-container-widget)
(add-to-grid p layout row 0)
(connect! p
(parameter-changed)
parameter-container-widget
(parameter-changed))))))
(define-widget parameter-container-widget-window (QWidget)
((parameter-container :initarg :parameter-container)))
(define-subwidget (parameter-container-widget-window container-widget)
(make-ui parameter-container)
(setf (q+:parent container-widget) parameter-container-widget-window))
(define-slot (parameter-container-widget-window parameter-changed) ()
(declare (connected container-widget (parameter-changed)))
(format t "~&Got parameter changed~%"))
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column))
(defun example-parameter-container (parameter-container)
(with-main-window
(window (make-instance 'parameter-container-widget-window
:parameter-container parameter-container))))
;; to run:
(example-parameter-container (list *mass* *velocity*))
在处理它时,我偶然发现了一个可能的解决方案。这一行
(setf (q+:parent p) parameter-container-widget)
将 sub-widget p
(在 sub-widget 的列表中)的 parent 设置为 parameter-container-widget
。如果注释掉这一行,一切正常。
p
的 sub-widget(包括 entry
,QLineEdit
的实例)后来被添加到网格中,但不包括 p
本身!在某种程度上,parameter-widget
不是一个合适的小部件:它只是其他小部件的集合,具有如何将它们添加到容器中的规则。但它需要充当能够接收和发送信号的小部件。
根据@KubaOber 和@Shinmera 的建议,我设法解决了这个问题。我在这里发布修复以供将来参考。
预选赛没有改变(除了,我忘了添加:PARSE-NUMBER
系统):
(ql:quickload '(:qtools :qtcore :qtgui :parse-number))
(defpackage :qtexample
(:use #:cl+qt))
(in-package qtexample)
(in-readtable :qtools)
(defclass parameter ()
((name :initarg :name :accessor parameter-name)
(value :initarg :value :accessor parameter-value)
(units :initarg :units :accessor parameter-units)))
(defun parameter (name value units)
(make-instance 'parameter
:name name
:value value
:units units))
(defvar *mass* (parameter "mass" 1d0 "kg"))
(defvar *velocity* (parameter "velocity" 0.5d0 "m/s"))
(defvar *temperature* (parameter "temperature" 300d0 "K"))
(defvar *pressure* (parameter "pressure" 1d5 "Pa"))
因为 PARAMETER-WIDGET
不是真正的小部件,而只是其他小部件的容器(并且它本身不能添加到适当的小部件容器中 - 否则标签的对齐方式会消失 - 参见ADD-TO-GRID
方法)但它需要能够接收和发送信号,它继承自QObject
而不是QWidget
。现在所有的子部件都是正常的 class-slots 而不是由 (DEFINE-SUBWIDGET ...)
定义。请注意,没有为任何子小部件提供父级:当这些小部件添加到容器小部件时,将分配父级 属性。
(define-widget parameter-widget (QObject)
((parameter
:initarg :parameter
:accessor parameter-widget-parameter)
(label :initform (q+:make-qlabel))
(entry :initform (q+:make-qlineedit))
(units :initform (q+:make-qlabel))))
初始化进入INITIALIZE-INSTANCE :AFTER
方法:
(defconstant +align-right+ 2)
(defmethod initialize-instance :after ((object parameter-widget) &key)
(with-slots (parameter label entry units) object
(setf (q+:text label) (parameter-name parameter))
(setf (q+:text entry) (format nil "~A" (parameter-value parameter)))
(setf (q+:alignment entry) +align-right+)
(setf (q+:text units) (parameter-units parameter))))
其余定义保持不变(信号大部分与问题无关,但有助于理解我为什么要跳过所有这些箍):
(defmethod make-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
(define-signal (parameter-widget parameter-changed) ())
(define-slot (parameter-widget entry) ((new-text string))
(declare (connected entry (text-changed string)))
(format t "~&Parameter has changed~%")
(handler-case
(let ((new-number (parse-number:parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)
(list (1+ row) (+ column 3))))
这是一个快速演示,一切都适用于单个参数。现在,PARAMETER-WIDGET
不再是一个小部件,因此它被添加为一个 class-slot。
(define-widget parameter-widget-window (QWidget)
((parameter :initarg :parameter)
(parameter-widget)))
window的网格:
(define-subwidget (parameter-widget-window grid)
(q+:make-qgridlayout parameter-widget-window))
此处将 PARAMETER-WIDGET
组件添加到网格中已移出此定义。原因:slot PARAMETER-WIDGET
此时未绑定。它确实在 INITIALIZE-INSTANCE :AFTER
方法中绑定并且所有组件都添加到网格中:
(defmethod initialize-instance :after ((object parameter-widget-window) &key)
(with-slots (parameter parameter-widget grid) object
(setf parameter-widget (make-ui parameter))
(setf (q+:parent parameter-widget) object)
(add-to-grid parameter-widget grid 0 0)))
启动器保持不变:
(defun parameter-example (parameter)
(with-main-window
(window (make-instance 'parameter-widget-window
:parameter parameter))))
至运行:(parameter-example *mass*)
PARAMETER-CONTAINER-WIDGET
是一个合适的小部件。它的定义没有改变:
(define-widget parameter-container-widget (QWidget)
((parameter-container
:initarg :parameter-container
:accessor parameter-container-widget-parameter-container)))
(defmethod make-ui ((object list))
(make-instance 'parameter-container-widget :parameter-container object))
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
和LAYOUT
定义也没有改变。但是现在可以安全地将 p
(PARAMETER-WIDGET
) 的父级 属性 设置为 PARAMETER-WIDGET-CONTAINER
,因此当容器被销毁时它也会被销毁。
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(let* ((parameter-widgets (loop for p in parameter-container
collect (make-ui p))))
(loop for p in parameter-widgets
for row from 0
do (progn
(add-to-grid p layout row 0)
(let ((pp p))
(setf (q+:parent pp) parameter-container-widget))
(connect! p
(parameter-changed)
parameter-container-widget
(parameter-changed))))))
将此小部件添加到网格是微不足道的(但不完全正确,但可以修复,请参见下文):
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column))
演示部分没有变化:
(define-widget parameter-container-widget-window (QWidget)
((parameter-container :initarg :parameter-container)))
(define-subwidget (parameter-container-widget-window container-widget)
(make-ui parameter-container)
(setf (q+:parent container-widget) parameter-container-widget-window))
(define-slot (parameter-container-widget-window parameter-changed) ()
(declare (connected container-widget (parameter-changed)))
(format t "~&Got parameter changed~%"))
(defun example-parameter-container (parameter-container)
(with-main-window
(window (make-instance 'parameter-container-widget-window
:parameter-container parameter-container))))
至运行:(example-parameter-container (list *mass* *velociy*))
它也可以像这样处理分层参数
(example-parameter-container (list *mass* *velocity* (list *temperature* *pressure*)))
而这个 运行 将说明为什么 PARAMETER-CONTAINER-WIDGET
的 ADD-TO-GRID
需要更复杂一点,但这是一个不同的故事。