Qt(通过Common Lisp的qtools):QLineEdit不会在点击时激活,但会在TAB上获得焦点

mob*_*eng 4 qt sbcl widget common-lisp ubuntu-16.04

问题

我遇到了相当奇怪的问题:QLineEdit通过鼠标点击它们无法访问界面中的对象,但使用Tab它们会收到焦点并响应键盘输入.但即使他们有焦点,他们也不会回应点击.令人讨厌的是,最后添加的内容QLineEdit确实会对点击做出响应.

额外的问题:鼠标不可访问的行编辑不显示工具提示.

我使用Common Lisp的qtools来构建接口,所以我将不得不解释它是如何工作的.简而言之,我在循环中逐个添加行编辑,所有这些都以相同的方式创建并添加到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)))
Run Code Online (Sandbox Code Playgroud)

要指示参数值已更改,parameter-widget将发出新信号parameter-changed(它转换为Qt的信号"parameterChanged"):

(define-signal (parameter-widget parameter-changed) ())
Run Code Online (Sandbox Code Playgroud)

行编辑的插槽尝试将文本转换为数字,如果成功,则更新基础参数并发出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)))
Run Code Online (Sandbox Code Playgroud)

此方法只是为类型对象parameter(工厂方法)自动构造窗口小部件:

(defmethod make-parameter-ui ((object parameter))
  (make-instance 'parameter-widget :parameter object))
Run Code Online (Sandbox Code Playgroud)

需要此方法来对齐多个单个参数的标签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)))
Run Code Online (Sandbox Code Playgroud)

参数容器

所以,如果我parameter-widget自己使用- 一切都很好.但大多数时候我需要以下列形式的多个参数parameter-container:

(define-widget parameter-container-widget (QWidget)
  ((parameter-container :initarg :parameter-container)))
Run Code Online (Sandbox Code Playgroud)

这个插槽捕获parameter-changed来自所有儿童的信号,parameter并为容器重新发射信号

(define-slot (parameter-container-widget parameter-changed) ()
  (format t "~&Re-emitting the signal...~%")
  (signal! parameter-container-widget (parameter-changed)))
Run Code Online (Sandbox Code Playgroud)

layoutQGridLayout把所有个人parameter-widget的所有人都parameter放在一起parameter-container.它所做的一切:

  • 它遍历所有单个参数,
  • 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)))))
Run Code Online (Sandbox Code Playgroud)

这种方法适用于parameter-widgetparameter-container:

(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
  (q+:add-widget grid widget row column 1 1))
Run Code Online (Sandbox Code Playgroud)

和通用实例化器(工厂方法):

(defmethod make-parameter-ui ((object parameter-container))
  (make-instance 'parameter-container-widget :parameter-container object))
Run Code Online (Sandbox Code Playgroud)

系统信息

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*))
Run Code Online (Sandbox Code Playgroud)

在努力的同时,我偶然发现了一个可能的解决方案.这条线

(setf (q+:parent p) parameter-container-widget) 
Run Code Online (Sandbox Code Playgroud)

设置子窗口小部件p(在子窗口小部件列表中)的父窗口parameter-container-widget.如果注释这一行,一切正常.

p后面的小部件(包括entry一个实例QLineEdit)后来被添加到网格中,但不是p自己!在某种程度上,parameter-widget它不是一个合适的小部件:它只是一个其他小部件的集合,其中包含如何将它们添加到容器中的规则.但它需要在能够接收和发送信号的意义上充当小部件.

mob*_*eng 5

在@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"))
Run Code Online (Sandbox Code Playgroud)

因为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))))
Run Code Online (Sandbox Code Playgroud)

初始化进入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))))
Run Code Online (Sandbox Code Playgroud)

其余的定义保持不变(信号通常与问题无关,但有助于理解我为什么跳过所有这些箍):

(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))))
Run Code Online (Sandbox Code Playgroud)

这是一个快速演示,一切都适用于单个参数.现在,PARAMETER-WIDGET它不再是一个小部件,因此它被添加为类槽.

(define-widget parameter-widget-window (QWidget)
  ((parameter :initarg :parameter)
   (parameter-widget)))
Run Code Online (Sandbox Code Playgroud)

窗口的网格:

(define-subwidget (parameter-widget-window grid)
    (q+:make-qgridlayout  parameter-widget-window))
Run Code Online (Sandbox Code Playgroud)

这里将PARAMETER-WIDGET组件添加到网格中已移出此定义.原因是:此时的插槽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)))
Run Code Online (Sandbox Code Playgroud)

发射器保持不变:

(defun parameter-example (parameter)
  (with-main-window
      (window (make-instance 'parameter-widget-window
                :parameter parameter))))
Run Code Online (Sandbox Code Playgroud)

跑步: (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)))
Run Code Online (Sandbox Code Playgroud)

而且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))))))
Run Code Online (Sandbox Code Playgroud)

添加到网格这个小部件是微不足道的(但不完全正确,但可修复,请参见下文):

(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
  (q+:add-widget grid widget row column))
Run Code Online (Sandbox Code Playgroud)

演示部分没有改变:

(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))))
Run Code Online (Sandbox Code Playgroud)

跑步: (example-parameter-container (list *mass* *velociy*))

它也可以像这样处理分层参数

(example-parameter-container (list *mass* *velocity* (list *temperature* *pressure*))) 
Run Code Online (Sandbox Code Playgroud)

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