主窗口+子窗口 实现复杂GUI模式

 

 

复制代码
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from PIL import Image, ImageTk
import math

from tkinter import scrolledtext
import json

# 对话页面 说明如何设置robotic griper

# 需要 from tkinter import scrolledtext
class ManualDialog:
    def __init__(self, parent, title="吸盘夹具",txt=('line1','line2')):
        self.parent = parent
        self.top = tk.Toplevel(parent)
        self.top.title(title)
        self.top.geometry("300x300")  # 增加了窗口的宽度和高度

        #显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
        # 创建用于输出多行文本的 Text 小部件
        # 使用 scrolledtext.ScrolledText 提供内置的滚动条
        root = self.top
        self.text_output = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=40, height=15)

        self.text_output.pack(pady=10)

        def add_text_output(text):
            # 在输出区域的末尾插入文本
            self.text_output.insert(tk.END, text + "\n")  # 添加换行符以分隔不同的输出

        # 添加一些示例文本输出
        # add_text_output("这是第一行输出文本。")
        # add_text_output("这是第二行输出文本,它可能更长一些,以展示文本换行效果。")
        # add_text_output("这是第三行输出文本。")
        for each in txt:
            add_text_output(each)
        # 你可以选择将 state 设置为 DISABLED 以防止用户编辑
        self.text_output.config(state=tk.DISABLED)

# 对话窗口 输入绝对点坐标
class Point2DInputDialog:
    def __init__(self, parent, title="Enter absolute 2D point"):
        self.parent = parent
        self.top = tk.Toplevel(parent)
        self.top.title(title)
        self.top.geometry("300x300")  # 增加了窗口的宽度和高度

        #显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
        self.label1 = tk.Label(self.top, text="Enter X Pixels:")
        self.label1.pack(pady=10)

        self.entry1 = tk.Entry(self.top)
        self.entry1.pack(pady=5)

        self.label2 = tk.Label(self.top, text="Enter Y Pixels:")
        self.label2.pack(pady=5)

        self.entry2 = tk.Entry(self.top)
        self.entry2.pack(pady=10)

        self.label3 = tk.Label(self.top, text="Enter Radius Pixels:")
        self.label3.pack(pady=5)

        self.entry3 = tk.Entry(self.top)
        self.entry3.pack(pady=10)

        # 使用 fill=X 和 expand=True 来让按钮水平扩展并填充剩余空间
        self.done_button = tk.Button(self.top, text="Done", command=self.done)
        self.done_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)

        self.clear_button = tk.Button(self.top, text="Clear", command=self.clear)
        self.clear_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)

        self.cancel_button = tk.Button(self.top, text="Cancel", command=self.cancel)
        self.cancel_button.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.X, expand=True)

        self.numbers = [None, None]

    def done(self):
        num1_str = self.entry1.get().strip()
        num2_str = self.entry2.get().strip()
        num3_str = self.entry3.get().strip()

        num1 = None
        num2 = None
        num3 = None

        try:
            num1 = float(num1_str)
        except ValueError:
            messagebox.showerror("Invalid Input", f"Invalid number for the first entry: {num1_str}")

        try:
            num2 = float(num2_str)
        except ValueError:
            messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")

        try:
            num3 = float(num3_str)
        except ValueError:
            messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")

        if num1 is not None and num2 is not None and num3 is not None:
            self.numbers = [num1, num2, num3]

        # 对话框关闭后重新显示主窗口
        # root.withdraw()  # 隐藏主窗口
        #self.parent.deiconify()
        self.top.destroy()

    def clear(self):
        self.numbers = [None, None]
        self.entry1.delete(0,tk.END)
        self.entry2.delete(0,tk.END)

    def cancel(self):
        self.numbers = [None, None]
        self.top.destroy()

    def get_numbers(self):
        return self.numbers
# 对话窗口 输入相对点坐标
class AngleDistanceInputDialog:
    def __init__(self, parent, title="Enter relative 2D point"):
        self.parent = parent
        self.top = tk.Toplevel(parent)
        self.top.title(title)
        self.top.geometry("300x200")  # 增加了窗口的宽度和高度

        #显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
        self.label1 = tk.Label(self.top, text="Enter Angle Degrees:")
        self.label1.pack(pady=10)

        self.entry1 = tk.Entry(self.top)
        self.entry1.pack(pady=5)

        self.label2 = tk.Label(self.top, text="Enter Distance Pixels:")
        self.label2.pack(pady=5)

        self.entry2 = tk.Entry(self.top)
        self.entry2.pack(pady=10)

        # 使用 fill=X 和 expand=True 来让按钮水平扩展并填充剩余空间
        self.done_button = tk.Button(self.top, text="Done", command=self.done)
        self.done_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)

        self.clear_button = tk.Button(self.top, text="Clear", command=self.clear)
        self.clear_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)

        self.cancel_button = tk.Button(self.top, text="Cancel", command=self.cancel)
        self.cancel_button.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.X, expand=True)

        self.numbers = [None, None]

    def done(self):
        num1_str = self.entry1.get().strip()
        num2_str = self.entry2.get().strip()

        num1 = None
        num2 = None
        try:
            num1 = float(num1_str)
        except ValueError:
            messagebox.showerror("Invalid Input", f"Invalid number for the first entry: {num1_str}")

        try:
            num2 = float(num2_str)
        except ValueError:
            messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")

        if num1 is not None and num2 is not None:
            self.numbers = [num1, num2]

        # 对话框关闭后重新显示主窗口
        # root.withdraw()  # 隐藏主窗口
        #self.parent.deiconify()

        self.top.destroy()


    def clear(self):
        self.numbers = [None, None]
        self.entry1.delete(0,tk.END)
        self.entry2.delete(0,tk.END)

    def cancel(self):
        self.numbers = [None, None]
        self.top.destroy()

    def get_numbers(self):
        return self.numbers

# 用来机械爪抓取方式

class GripperDialog(tk.Toplevel):
    def __init__(self, parent, title="Robotic Gripper Dialog"):
        super().__init__(parent)

        self.parent = parent
        # 设置对话框的标题
        self.title(title)

        # 设置对话框的大小(可选)
        self.geometry("640x500")

        # 绑定关闭事件(可选,确保对话框可以通过窗口管理器关闭按钮正确关闭)
        self.protocol("WM_DELETE_WINDOW", self.on_close)

        # 设定爪方式值
        self.json_dict = dict()
        # 属性
        self.image_path = None # 路径
        self.image = None      # 图片
        self.tk_image = None

        self.start_x = None
        self.start_y = None

        self.angle = None # 与x轴夹角
        self.distance = None

        # 绘图板控件 放图片
        self.canvas = tk.Canvas(self, cursor="cross")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        self.canvas.bind("<Motion>", self.show_pixel_info)#绑定鼠标移动事件,显示像素信息

        # 菜单容器
        self.menu = tk.Menu(self)  #一级菜单容器

        self.config(menu=self.menu)# 放入Toplevel容器中

        self.file_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
        # 吸盘:Suction Cup
        # 两爪夹具:Two-Jaw Clamp(或者 Two-Finger Clamp,具体取决于上下文和使用的领域)
        # 三抓夹具:Three-Jaw Chuck(在机床或工具制造中常用)或者 Three-Point Clamp(在更一般的夹持应用中)
        self.suction_cup_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
        self.two_jaw_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
        self.three_jaw_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
        #一级菜单
        self.menu.add_cascade(label="打开图片", menu=self.file_menu)
        #一级菜单
        self.menu.add_cascade(label="吸盘夹具", menu=self.suction_cup_menu)
        #一级菜单
        self.menu.add_cascade(label="两爪夹具", menu=self.two_jaw_menu)
        #一级菜单
        self.menu.add_cascade(label="三爪夹具", menu=self.three_jaw_menu)

        #二级菜单 file
        self.file_menu.add_command(label="读入图片", command=self.open_image)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="Exit", command=self.on_close)

        #二级菜单 吸盘夹具
        self.suction_cup_menu.add_command(label="设置吸盘夹持中心", command=self.suction_cup_center)
        self.suction_cup_menu.add_separator()
        self.suction_cup_menu.add_command(label="设置零件夹持方向", command=self.suction_cup_direction)
        self.suction_cup_menu.add_separator()
        self.suction_cup_menu.add_command(label="设置吸盘夹具说明", command=self.suction_cup_manual)

        # 二级菜单 两爪夹具
        self.two_jaw_menu.add_command(label="设置夹持中心", command=self.two_jaw_center)
        self.two_jaw_menu.add_separator()
        self.two_jaw_menu.add_command(label="设置第一个夹持点", command=self.two_jaw_1point)
        self.two_jaw_menu.add_separator()
        self.two_jaw_menu.add_command(label="设置第二个夹持点", command=self.two_jaw_2point)
        self.two_jaw_menu.add_separator()
        self.two_jaw_menu.add_command(label="设置两爪夹具说明", command=self.two_jaw_manual)

        # 二级菜单 三爪夹具
        self.three_jaw_menu.add_command(label="设置夹持中心", command=self.three_jaw_center)
        self.three_jaw_menu.add_separator()
        self.three_jaw_menu.add_command(label="设置第一个夹持点", command=self.three_jaw_1point)
        self.three_jaw_menu.add_separator()
        self.three_jaw_menu.add_command(label="设置第二个夹持点", command=self.three_jaw_2point)
        self.three_jaw_menu.add_separator()
        self.three_jaw_menu.add_command(label="设置第三个夹持点", command=self.three_jaw_3point)
        self.three_jaw_menu.add_separator()
        self.three_jaw_menu.add_command(label="设置三爪夹具说明", command=self.three_jaw_manual)

        # 提示标签 底部
        self.label_var = tk.StringVar()
        self.label = tk.Label(self, textvariable=self.label_var, anchor="w", bg="black", fg="white")
        self.label.pack(side=tk.BOTTOM,fill=tk.X)

    def on_ok(self):

        # 对话框关闭后重新显示主窗口
        # root.withdraw()  # 隐藏主窗口
        self.parent.deiconify()
        # 关闭对话框
        self.destroy()

    def on_close(self):
        # 如果没有其他操作需要执行,可以直接调用destroy方法关闭对话框
        # 这里只是为了演示如何绑定关闭事件,所以直接调用on_ok方法
        # 在实际应用中,你可能需要在这里添加额外的清理代码或确认对话框
        self.destroy()
        self.parent.deiconify()  # 重新显示主窗口

    # 将字典转为字符串并输出
    def gripper_dict_label(self):
        # 将字典转换为 JSON 字符串
        #json_string = json.dumps(self.json_dict, indent=4)  # indent 参数用于美化输出,使其更易读
        json_string = json.dumps(self.json_dict, indent=None, separators=(', ', ':'))
        self.label_var.set(json_string[1:-1])

    # 根据圆心点,半径绘制圆
    def draw_circle(self,center_x = 0,center_y = 0, radius = 0):
        # 绘制圆形(椭圆)
        self.canvas.create_oval(
            center_x - radius,  # 左上角x坐标
            center_y - radius,  # 左上角y坐标
            center_x + radius,  # 右下角x坐标
            center_y + radius,  # 右下角y坐标
            tags="circle",
            outline="blue",  # 边框颜色
            width=2  # 边框宽度
        )
        print("绘制夹持中心圆")

    # 根据点,绘制直线
    def draw_line(self):

        if self.start_x is not None and self.start_y is not None:
            if  self.angle is not None and self.distance is not None:
                # 将30度转换为弧度
                angle_in_degrees = self.angle
                angle_in_radians = math.radians(angle_in_degrees)
                # 计算余弦值
                cosine_value = math.cos(angle_in_radians)
                sine_value   = math.sin(angle_in_radians)
                end_x = self.distance * cosine_value +self.start_x
                end_y = self.distance * sine_value +self.start_y
                self.canvas.create_line(self.start_x, self.start_y, end_x, end_y, tags="line",
                                        width=2, capstyle=tk.ROUND, smooth=tk.TRUE,fill="blue")

                self.angle = None  # 与x轴夹角
                self.distance = None
                print("绘制夹持点和中心连线")


    # 鼠标移动时,显示像素信息
    def show_pixel_info(self, event):
        if self.tk_image:
            x, y = event.x, event.y
            try:
                pixel = self.image.getpixel((x, y))
                self.label_var.set(f"Pixel at ({x}, {y}):RGB {pixel}")
            except IndexError:
                self.label_var.set(f"Pixel out of bounds at ({x}, {y})")

    # 打开图像文件 #二级菜单 file
    def open_image(self):
        self.image_path = filedialog.askopenfilename(filetypes=[("PNG files", "*.png")])
        if self.image_path:
            self.image = Image.open(self.image_path)
            self.tk_image = ImageTk.PhotoImage(self.image)
            self.canvas.config(width=self.image.width, height=self.image.height)
            self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image)

    #二级菜单 吸盘夹具
    def suction_cup_center(self):
        # 对话窗口 输入绝对点坐标
        dialog = Point2DInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
            print("Input numbers is None")
            return

        # 设置为直线段的起点
        self.start_x,self.start_y, radius = input_numbers

        # 保存 机械爪设置值
        self.json_dict.clear()
        self.json_dict["gripper_type"]   = "suction_cup"
        self.json_dict["gripper_center"] = input_numbers

        self.gripper_dict_label() # 打印机器爪设置值
        # 绘制 夹持中心圆
        self.draw_circle( center_x=self.start_x, center_y=self.start_y, radius=radius)

    #二级菜单 吸盘夹具
    def suction_cup_direction(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()
        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_firstPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角

        self.gripper_dict_label() # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()

    # 二级菜单 吸盘夹具
    def suction_cup_manual(self):
        txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
             "2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
             "3)设置夹持方向,通过设置一个相对夹持中心点的点的距离和角度表示方向,角度以X轴为准。",
             "4)观察零件图片,检查是否设置成功。")
        # 对话窗口 输入绝对点坐标
        dialog = ManualDialog(self, title="吸盘夹具", txt=txt)

        self.wait_window(dialog.top)  # 等待对话框关闭

    # 二级菜单 两爪夹具
    def two_jaw_center(self):
        # 对话窗口 输入绝对点坐标
        dialog = Point2DInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
            print("Input numbers is None")
            return


        # 保存 机械爪设置值
        self.json_dict.clear()
        self.json_dict["gripper_type"]   = "two_jaw"
        self.json_dict["gripper_center"] = input_numbers

        # 设置为直线段的起点
        self.start_x, self.start_y, radius = input_numbers
        self.gripper_dict_label()  # 打印机器爪设置值

        # 绘制 夹持中心圆
        self.draw_circle(center_x=self.start_x, center_y=self.start_y, radius=radius)

    #二级菜单 两爪夹具
    def two_jaw_1point(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_firstPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角

        self.gripper_dict_label()  # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()

    #二级菜单 两爪夹具
    def two_jaw_2point(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_secondPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角
        self.gripper_dict_label()  # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()
    # 二级菜单 两爪夹具
    def two_jaw_manual(self):
        txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
             "2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
             "3)设置第1夹持点,即设置第1夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
             "4)设置第2夹持点,即设置第2夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
             "5)观察零件图片,检查是否设置成功。")
        # 对话窗口 输入绝对点坐标
        dialog = ManualDialog(self, title="两爪夹具", txt=txt)

        self.wait_window(dialog.top)  # 等待对话框关闭

    # 二级菜单 三爪夹具
    def three_jaw_center(self):
        # 对话窗口 输入绝对点坐标
        dialog = Point2DInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭

        input_numbers = dialog.get_numbers()
        if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict.clear()
        self.json_dict["gripper_type"]   = "three_jaw"
        self.json_dict["gripper_center"] = input_numbers

        # 设置为直线段的起点
        self.start_x, self.start_y, radius = input_numbers
        self.gripper_dict_label()  # 打印机器爪设置值
        # 绘制 夹持中心圆
        self.draw_circle(center_x=self.start_x, center_y=self.start_y, radius=radius)

    #二级菜单 三爪夹具
    def three_jaw_1point(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_firstPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角
        self.gripper_dict_label()  # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()

    #二级菜单 三爪夹具
    def three_jaw_2point(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_secondPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角
        self.gripper_dict_label()  # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()

    #二级菜单 三爪夹具
    def three_jaw_3point(self):
        # 对话窗口 输入相对点坐标
        dialog = AngleDistanceInputDialog(self)
        self.wait_window(dialog.top)  # 等待对话框关闭
        input_numbers = dialog.get_numbers()

        if input_numbers[0] is None or input_numbers[1] is None:
            print("Input numbers is None")
            return

        # 保存 机械爪设置值
        self.json_dict["gripper_thirdPoint"] = input_numbers

        # 设置为直线段的相对起点的终点
        self.angle, self.distance  = input_numbers# 与x轴夹角
        self.gripper_dict_label()  # 打印机器爪设置值
        # 根据点,绘制直线
        self.draw_line()

    # 二级菜单 三爪夹具
    def three_jaw_manual(self):
        txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
             "2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
             "3)设置第1夹持点,即设置第1夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
             "4)设置第2夹持点,即设置第2夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
             "5)设置第3夹持点,即设置第3夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
             "6)观察零件图片,检查是否设置成功。")
        # 对话窗口 输入绝对点坐标
        dialog = ManualDialog(self, title="三爪夹具", txt=txt)

        self.wait_window(dialog.top)  # 等待对话框关闭

# 因为使用了菜单,容器只能是 Toplevel 容器 或 主窗口
# 因为使用了菜单,容器只能是 Toplevel 容器 或 主窗口


class MainWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.child_window = None
        self.title("主窗口")
        self.geometry("300x200")

        self.button = tk.Button(self, text="打开子窗口", command=self.open_child_window)
        self.button.pack(pady=20)

        self.withdraw()  # 隐藏主窗口,直到点击按钮
        self.after(100, self.deiconify)  # 延时100毫秒后显示主窗口

    def open_child_window(self):
        self.withdraw()  # 隐藏主窗口
        self.child_window = GripperDialog(self)


if __name__ == "__main__":
    app = MainWindow()
    app.mainloop()
复制代码

 

posted @   辛河  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示