FreeCAD Dialog ---> python

Dialog creation

In this page we will show how to build a simple Qt Dialog with Qt Designer, Qt's official tool for designing interfaces, then convert it to python code, then use it inside FreeCAD. I'll assume in the example that you know how to edit and run python scripts already, and that you can do simple things in a terminal window such as navigate, etc. You must also have, of course, pyqt installed.

Designing the dialog

In CAD applications, designing a good UI (User Interface) is very important. About everything the user will do will be through some piece of interface: reading dialog boxes, pressing buttons, choosing between icons, etc. So it is very important to think carefully to what you want to do, how you want the user to behave, and how will be the workflow of your action.

There are a couple of concepts to know when designing interface:

  • Modal/non-modal dialogs: A modal dialog appears in front of your screen, stopping the action of the main window, forcing the user to respond to the dialog, while a non-modal dialog doesn't stop you from working on the main window. In some case the first is better, in other cases not.
  • Identifying what is required and what is optional: Make sure the user knows what he must do. Label everything with proper description, use tooltips, etc.
  • Separating commands from parameters: This is usually done with buttons and text input fields. The user knows that clicking a button will produce an action while changing a value inside a text field will change a parameter somewhere. Nowadays, though, users usually know well what is a button, what is an input field, etc. The interface toolkit we are using, Qt, is a state-of-the-art toolkit, and we won't have to worry much about making things clear, since they will already be very clear by themselves.

So, now that we have well defined what we will do, it's time to open the qt designer. Let's design a very simple dialog, like this:

Image:Qttestdialog.jpg

We will then use this dialog in FreeCAD to produce a nice rectangular plane. You might find it not very useful to produce nice rectangular planes, but it will be easy to change it later to do more complex things. When you open it, Qt Designer looks like this:

Image:Qtdesigner-screenshot.jpg

It is very simple to use. On the left bar you have elements that can be dragged on your widget. On the right side you have properties panels displaying all kinds of editable properties of selected elements. So, begin with creating a new widget. Select "Dialog without buttons", since we don't want the default Ok/Cancel buttons. Then, drag on your widget 3 labels, one for the title, one for writing "Height" and one for writing "Width". Labels are simple texts that appear on your widget, just to inform the user. If you select a label, on the right side will appear several properties that you can change if you want, such as font style, height, etc.

Then, add 2 LineEdits, which are text fields that the user can fill in, one for the height and one for the width. Here too, we can edit properties. For example, why not set a default value? For example 1.00 for each. This way, when the user will see the dialog, both values will be filled already and if he is satisfied he can directly press the button, saving precious time. Then, add a PushButton, which is the button the user will need to press after he filled the 2 fields.

Note that I choosed here very simple controls, but Qt has many more options, for example you could use Spinboxes instead of LineEdits, etc... Have a look at what is available, you will surely have other ideas.

That's about all we need to do in Qt Designer. One last thing, though, let's rename all our elements with easier names, so it will be easier to identify them in our scripts:

Image:Qtpropeditor.jpg


Converting our dialog to python

Now, let's save our widget somewhere. It will be saved as an .ui file, that we will easily convert to python script with pyuic. On windows, the pyuic program is bundled with pyqt (to be verified), on linux you probably will need to install it separately from your package manager (on debian-based systems, it is part of the pyqt4-dev-tools package). To do the conversion, you'll need to open a terminal window (or a command prompt window on windows), navigate to where you saved your .ui file, and issue:

pyuic mywidget.ui > mywidget.py

On some systems the program is called pyuic4 instead of pyuic. This will simply convert the .ui file into a python script. If we open the mywidget.py file, its contents are very easy to understand:

from PyQt4 import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(187, 178)
        self.title = QtGui.QLabel(Dialog)
        self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
        self.title.setObjectName("title")
        self.label_width = QtGui.QLabel(Dialog)
        ...

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

   def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.title.setText(QtGui.QApplication.translate("Dialog", "Plane-O-Matic", None, QtGui.QApplication.UnicodeUTF8))
        ...

As you see it has a very simple structure: a class named Ui_Dialog is created, that stores the interface elements of our widget. That class has two methods, one for setting up the widget, and one for translating its contents, that is part of the general Qt mechanism for translating interface elements. The setup method simply creates, one by one, the widgets as we defined them in Qt Designer, and sets their options as we decided earlier. Then, the whole interface gets translated, and finally, the slots get connected (we'll talk about that later).

We can now create a new widget, and use this class to create its interface. We can already see our widget in action, by putting our mywidget.py file in a place where FreeCAD will find it (in the FreeCAD bin directory, or in any of the Mod subdirectories), and, in the FreeCAD python interpreter, issue:

from PyQt4 import QtGui
import mywidget
d = QtGui.QWidget()
d.ui = mywidget.Ui_Dialog()
d.ui.setupUi(d)
d.show()

And our dialog will appear! Note that our python interpreter is still working, we have a non-modal dialog. So, to close it, we can (apart from clicking its close icon, of course) issue:

d.hide()

Making our dialog do something

Now that we can show and hide our dialog, we just need to add one last part: To make it do something! If you play a bit with Qt designer, you'll quickly discover a whole section called "signals and slots". Basically, it works like this: elements on your widgets (in Qt terminology, those elements are themselves widgets) can send signals. Those signals differ according to the widget type. For example, a button can send a signal when it is pressed and when it is released. Those signals can be connected to slots, which can be special functionality of other widgets (for example a dialog has a "close" slot to which you can connect the signal from a close button), or can be custom functions. The PyQt Reference Documentation lists all the qt widgets, what they can do, what signals they can send, etc...

What we will do here, is create a new function that will create a plane based on height and width, and connect that function to the pressed signal emitted by our "Create!" button. So, let's begin with importing our FreeCAD modules, by putting the following line at the top of the script, where we already import QtCore and QtGui:

import FreeCAD, Part

Then, let's add a new function to our Ui_Dialog class:

def createPlane(self):
    try:
        # first we check if valid numbers have been entered
        w = float(self.width.text())
        h = float(self.height.text())
    except ValueError:
        print "Error! Width and Height values must be valid numbers!"
    else:
        # create a face from 4 points
        p1 = FreeCAD.Vector(0,0,0)
        p2 = FreeCAD.Vector(w,0,0)
        p3 = FreeCAD.Vector(w,h,0)
        p4 = FreeCAD.Vector(0,h,0)
        pointslist = [p1,p2,p3,p4,p1]
        mywire = Part.makePolygon(pointslist)
        myface = Part.Face(mywire)
        Part.show(myface)
        self.hide()

Then, we need to inform Qt to connect the button to the function, by placing the following line just before QtCore.QMetaObject.connectSlotsByName(Dialog):

QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)

This, as you see, connects the pressed() signal of our create object (the "Create!" button), to a slot named createPlane, which we just defined. That's it! Now, as a final touch, we can add a little function to create the dialog, it will be easier to call. Outside the Ui_Dialog class, let's add this code:

class plane():
    d = QtGui.QWidget()
    d.ui = Ui_Dialog()
    d.ui.setupUi(d)
    d.show()

Then, from FreeCAD, we only need to do:

import mywidget
mywidget.plane()

That's all Folks... Now you can try all kinds of things, like for example inserting your widget in the FreeCAD interface (see the Code snippets page), or making much more advanced custom tools, by using other elements on your widget.

The complete script

This is the complete script, for reference:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mywidget.ui'
#
# Created: Mon Jun  1 19:09:10 2009
#      by: PyQt4 UI code generator 4.4.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui
import FreeCAD, Part 

class Ui_Dialog(object):
   def setupUi(self, Dialog):
       Dialog.setObjectName("Dialog")
       Dialog.resize(187, 178)
       self.title = QtGui.QLabel(Dialog)
       self.title.setGeometry(QtCore.QRect(10, 10, 271, 16))
       self.title.setObjectName("title")
       self.label_width = QtGui.QLabel(Dialog)
       self.label_width.setGeometry(QtCore.QRect(10, 50, 57, 16))
       self.label_width.setObjectName("label_width")
       self.label_height = QtGui.QLabel(Dialog)
       self.label_height.setGeometry(QtCore.QRect(10, 90, 57, 16))
       self.label_height.setObjectName("label_height")
       self.width = QtGui.QLineEdit(Dialog)
       self.width.setGeometry(QtCore.QRect(60, 40, 111, 26))
       self.width.setObjectName("width")
       self.height = QtGui.QLineEdit(Dialog)
       self.height.setGeometry(QtCore.QRect(60, 80, 111, 26))
       self.height.setObjectName("height")
       self.create = QtGui.QPushButton(Dialog)
       self.create.setGeometry(QtCore.QRect(50, 140, 83, 26))
       self.create.setObjectName("create")

       self.retranslateUi(Dialog)
       QtCore.QObject.connect(self.create,QtCore.SIGNAL("pressed()"),self.createPlane)
       QtCore.QMetaObject.connectSlotsByName(Dialog)

   def retranslateUi(self, Dialog):
       Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
       self.title.setText(QtGui.QApplication.translate("Dialog", "Plane-O-Matic", None, QtGui.QApplication.UnicodeUTF8))
       self.label_width.setText(QtGui.QApplication.translate("Dialog", "Width", None, QtGui.QApplication.UnicodeUTF8))
       self.label_height.setText(QtGui.QApplication.translate("Dialog", "Height", None, QtGui.QApplication.UnicodeUTF8))
       self.create.setText(QtGui.QApplication.translate("Dialog", "Create!", None, QtGui.QApplication.UnicodeUTF8))

   def createPlane(self):
       try:
           # first we check if valid numbers have been entered
           w = float(self.width.text())
           h = float(self.height.text())
       except ValueError:
           print "Error! Width and Height values must be valid numbers!"
       else:
           # create a face from 4 points
           p1 = FreeCAD.Vector(0,0,0)
           p2 = FreeCAD.Vector(w,0,0)
           p3 = FreeCAD.Vector(w,h,0)
           p4 = FreeCAD.Vector(0,h,0)
           pointslist = [p1,p2,p3,p4,p1]
           mywire = Part.makePolygon(pointslist)
           myface = Part.Face(mywire)
           Part.show(myface)

class plane():
    d = QtGui.QWidget()
    d.ui = Ui_Dialog()
    d.ui.setupUi(d)
    d.show()

<think>好的,我现在要解决用户的问题,关于如何在Qt中使用dialog->move将对话框移动到指定坐标。用户之前已经询问过关于对话框位置设置的代码,现在想进一步了解具体的实现方法。 首先,我需要回顾之前的对话,确保信息的一致性。用户提供的引用内容涉及Qt的坐标系统、父子窗口关系以及模态对话框的设置。根据引用[1]和[3],Qt的坐标系统对于父窗口和子窗口是不同的。父窗口的坐标是相对于屏幕的左上角,而子窗口的坐标是相对于父窗口的客户区。这意味着如果对话框有父窗口,move的坐标是相对于父窗口的;否则,是相对于屏幕的。 接下来,用户的问题是如何使用dialog->move移动到指定坐标。根据之前的回答,已经解释了如何通过计算主窗口右侧的位置来定位对话框。现在需要详细说明不同情况下的坐标设置方法。 首先,需要考虑对话框是否有父对象。如果有父对象,坐标是相对于父窗口的;如果没有,则使用屏幕坐标。例如,如果对话框是主窗口的子对象,那么move(100,100)会相对于主窗口的左上角移动。如果对话框没有父对象,则坐标是屏幕的绝对位置。 然后,需要举例说明不同的应用场景,比如绝对定位、相对父窗口定位、动态跟随主窗口等。同时,可能需要涉及如何获取屏幕的尺寸,以确保对话框在可见范围内,避免移动到屏幕外。 此外,引用[4]提到模态对话框的设置,比如setAttribute(Qt::WA_DeleteOnClose)和setWindowFlags,这些可能影响对话框的行为,但用户当前的问题主要关注位置设置,所以暂时不需要深入这部分,但可以作为补充信息。 在代码示例中,需要展示如何创建对话框,设置父对象(或不设置),然后调用move方法。例如,设置对话框到屏幕的(200,300)位置,或者相对于主窗口的(50,50)位置。同时,可以提到如何结合resize来调整对话框大小,确保其正确显示。 另外,用户可能关心如何处理多屏幕的情况,比如获取可用屏幕的几何信息,使用QDesktopWidget或QScreen类来获取正确的坐标范围。例如,使用QApplication::desktop()->availableGeometry()来确保对话框位于可见区域。 最后,需要检查是否有常见的错误,比如忘记设置父对象导致坐标错误,或者没有考虑窗口边框和标题栏的尺寸,这些可能会影响实际显示的位置。例如,父窗口的move(100,100)是相对于屏幕,但子窗口的move是相对于父窗口的客户区,不包括边框。 总结来说,回答需要涵盖: 1. Qt坐标系统的基本原理,父子窗口的区别。 2. 使用dialog->move的几种情况(绝对坐标、相对父窗口坐标)。 3. 代码示例和注意事项,如多屏幕处理、窗口边框的影响。 4. 可能的相关问题,如动态跟随、坐标转换等。</think>### Qt中设置对话框位置的完整指南 在Qt中,使用`dialog->move(x, y)`设置对话框位置时,需结合**坐标系统类型**和**父子关系**进行操作。以下是具体实现方法和注意事项: --- #### 一、坐标系统核心规则(参考[1][3]) 1. **无父对象时** 对话框坐标以**屏幕左上角**为原点$(0,0)$ ```cpp QDialog dialog; dialog.move(200, 300); // 距离屏幕左200px,顶部300px ``` 2. **有父对象时** 坐标原点变为**父窗口客户区左上角**(不包含边框) ```cpp QDialog *dialog = new QDialog(this); // 指定父对象为主窗口 dialog->move(50, 50); // 相对于主窗口左上角偏移50px ``` --- #### 二、具体实现场景 ##### 场景1:绝对定位(屏幕坐标) ```cpp // 创建独立对话框(无父对象) QDialog *dialog = new QDialog; dialog->setWindowTitle("绝对定位弹窗"); dialog->resize(300, 200); // 设置窗口尺寸 dialog->move(500, 200); // 屏幕坐标(500,200) dialog->show(); ``` *注意:多屏环境下需用`QDesktopWidget`获取屏幕边界[^1]* ##### 场景2:相对父窗口定位 ```cpp // 创建子对话框(父对象为主窗口) QDialog *dialog = new QDialog(this); dialog->setWindowTitle("相对定位弹窗"); dialog->resize(300, 200); // 定位到主窗口中心右侧 int x = this->width() + 20; // 主窗口宽度+20px偏移 int y = (this->height() - 200)/2; // 垂直居中 dialog->move(x, y); ``` ##### 场景3:动态跟随主窗口 通过事件过滤器实现实时位置更新: ```cpp // 主窗口类中重写moveEvent void MainWindow::moveEvent(QMoveEvent *event) { QPoint mainPos = this->pos(); dialog->move(mainPos.x() + this->width(), mainPos.y()); } ``` --- #### 三、关键注意事项 1. **窗口装饰影响** 使用`setWindowFlags(Qt::FramelessWindowHint)`去除边框后,坐标计算方式不变[^4] 2. **多显示器适配** 获取当前屏幕可用区域: ```cpp QRect screenGeo = QGuiApplication::primaryScreen()->availableGeometry(); dialog->move(screenGeo.center().x() - 150, screenGeo.top() + 100); ``` 3. **坐标转换方法** 使用`mapToGlobal()`转换相对坐标为屏幕坐标: ```cpp QPoint globalPos = button->mapToGlobal(QPoint(0,0)); dialog->move(globalPos.x() + 50, globalPos.y()); ``` --- #### 四、调试技巧 1. 打印坐标信息验证定位: ```cpp qDebug() << "屏幕坐标:" << dialog->pos(); qDebug() << "相对坐标:" << dialog->pos() - parentWidget()->pos(); ``` 2. 可视化辅助线: ```cpp // 在父窗口绘制参考线 void MainWindow::paintEvent(QPaintEvent *) { QPainter painter(this); painter.drawLine(width(), 0, width(), height()); // 右侧边界线 } ``` --- ### 常见问题解决方案 | 问题现象 | 原因分析 | 修复方法 | |---------|----------|----------| | 对话框位置偏移 | 未考虑窗口边框厚度 | 使用`geometry()`替代`pos()`获取完整坐标 | | 多显示器显示不全 | 未检测屏幕边界 | 调用`QDesktopWidget::availableGeometry()` | | 子对话框位置错误 | 父子关系未正确设置 | 显式指定父对象构造函数参数 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值