问题

我遇到了一个很奇怪的问题:QLineEdit界面中的对象不能通过鼠标点击访问,而是使用Tab ' 他们接收焦点并响应键盘输入。但即使他们有焦点,他们也不会响应点击。更奇怪的是最后添加的 QLineEdit确实响应点击。

额外的问题:鼠标无法访问的行编辑不显示工具提示。

我正在使用 Common Lisp 的 qtools构建接口(interface),所以我将不得不做一些解释它是如何工作的。简而言之,我在循环中逐个添加行编辑,所有这些都以相同的方式创建并添加到 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 类型的对象自动构造小部件。 (工厂方法):
(defmethod make-parameter-ui ((object parameter)) 
  (make-instance 'parameter-widget :parameter object)) 

需要此方法来对齐 parameter-container 中多个单个参数的标签(见下文):
(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))) 

此插槽捕获信号 parameter-changed来自所有 child parameter 's 并为容器重新发出它
(define-slot (parameter-container-widget parameter-changed) () 
  (format t "~&Re-emitting the signal...~%") 
  (signal! parameter-container-widget (parameter-changed))) 
layoutQGridLayout把所有个人 parameter-widget的所有 parameter - parameter-container 的 child .它所做的一切:
  • 它循环遍历所有单个参数,
  • 构造 parameter-widget对于每个
  • 添加到 layout逐行
  • 并连接信号parameter-changed到上面定义的插槽。

  • 代码:
    (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,操作系统:Ubuntu 16.04

    更新

    Windows 10 上的同样问题

    更新 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)  
    

    设置子小部件的父级 p (在子小部件列表中)为 parameter-container-widget .如果注释了此行,则一切正常。
    p的子小部件(包括 entryQLineEdit 的一个实例)稍后被添加到网格中,但不是 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 .现在所有子小部件都只是其中的普通类槽,而不是由 (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不再是一个小部件,所以它被添加为一个类槽。
    (define-widget parameter-widget-window (QWidget) 
      ((parameter :initarg :parameter) 
       (parameter-widget))) 
    

    窗口的网格:
    (define-subwidget (parameter-widget-window grid) 
        (q+:make-qgridlayout  parameter-widget-window)) 
    

    这里添加 PARAMETER-WIDGET网格中的组件已从此定义中移出。原因:插槽 PARAMETER-WIDGET在这一点上是不受约束的。它确实被绑定(bind)到 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*)))  
    

    这次运行将说明为什么 ADD-TO-GRID对于 PARAMETER-CONTAINER-WIDGET需要更复杂一些,但这是一个不同的故事。


    评论关闭
    IT干货网

    微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!