You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Tkinter/Pillow实现可框选区域的屏幕截图功能?

问题描述

我开发了一个用于存储错误信息的错误日志程序,希望为其添加截图存储功能。尝试用Pillow实现截图后,当前代码无法完成点击按钮后启动截图、框选区域并保存的完整流程。现有代码如下:

import csv
import datetime
import tkinter as tk
from tkinter import messagebox
import os
import configparser
from PIL import ImageGrab

config = configparser.ConfigParser()
config.read("C:/Users/Public/Documents/Intel/Strata/CfgFiles/Stationdata.ini")  #Locationdata for PD dataframe.

for key, value in config["StationData"].items():
    print(value)

snip_count = 0    
    
config.read("C:/Users/Public/Documents/Intel/Strata/CfgFiles/Appinfo.ini")

for key, version in config["About"].items():
    print(version)

def log_error(category, error):
    with open("//datagrovera.ra.intel.com/DeviceLabUser/Shared/Device lab Error Logger/error_log.txt", 'a', newline='')as error_log:
        writer = csv.writer(error_log, delimiter=',')
        writer.writerow([datetime.datetime.now(), error, category, value, version])
        

def main():
    root = tk.Tk()
    root.geometry('300x515')
    root.title(value)
    category_var = tk.StringVar(root)
    category_var.set('Strata')
    category_dropdown = tk.OptionMenu(root, category_var, 'Strata', 'Prober', 'General')
    category_dropdown.pack(pady=10)

    error_label = tk.Label(root, text='Error:')
    error_label.pack()
    v=tk.Scrollbar(root, orient='vertical')
    v.pack(side=tk.RIGHT, fill=tk.Y)
    error_entry = tk.Text(root, yscrollcommand=v.set)
    error_entry.pack(expand = True, fill = tk.BOTH )

    def log_error_callback():
        category = category_var.get()
        error = error_entry.get('1.0','end')
        if not error:
            messagebox.showerror('Error', 'Please enter an error message')
            return
        log_error(category, error)
        messagebox.showinfo('Success', f'Error logged for {category} category')

    def delete():
        error_entry.delete("1.0","end")

    log_button = tk.Button(root, text='Log Error', command=lambda:[log_error_callback(), delete()])
    log_button.pack()

    # Define a function to capture the screen snip
    def capture_snip():
    # Use the global variable
        global snip_count
    # Get the coordinates of the selected region
        x1 = root.winfo_rootx() + canvas.winfo_x()
        y1 = root.winfo_rooty() + canvas.winfo_y()
        x2 = x1 + canvas.winfo_width()
        y2 = y1 + canvas.winfo_height()
    # Grab the image of the region and save it to a folder with a unique name
        image = ImageGrab.grab().crop((x1,y1,x2,y2))
        image.save(f"snip_{snip_count}.png")
    # Increment the snip count
        snip_count += 1

# Create a canvas to draw the region
    canvas = tk.Canvas(root, width=300, height=200, bg='white')
    canvas.pack()

# Create a button to capture the screen snip
    button = tk.Button(root, text='Capture', command=capture_snip)
    button.pack()

    root.mainloop()

if __name__ == '__main__':
    main()

现有代码的问题:仅固定截取程序内canvas区域,未实现自由框选屏幕区域的交互逻辑。


实现自由区域截图的完整方案

可以通过Tkinter结合Pillow实现完整截图流程,核心逻辑为:点击截图按钮后创建全屏半透明遮罩窗口,监听鼠标事件记录框选的起点与终点,完成框选后截取对应区域并保存。

修改后的完整代码如下:

import csv
import datetime
import tkinter as tk
from tkinter import messagebox, Toplevel
import os
import configparser
from PIL import ImageGrab

config = configparser.ConfigParser()
config.read("C:/Users/Public/Documents/Intel/Strata/CfgFiles/Stationdata.ini")

for key, value in config["StationData"].items():
    print(value)

snip_count = 0

config.read("C:/Users/Public/Documents/Intel/Strata/CfgFiles/Appinfo.ini")

for key, version in config["About"].items():
    print(version)

def log_error(category, error):
    with open("//datagrovera.ra.intel.com/DeviceLabUser/Shared/Device lab Error Logger/error_log.txt", 'a', newline='') as error_log:
        writer = csv.writer(error_log, delimiter=',')
        writer.writerow([datetime.datetime.now(), error, category, value, version])

def main():
    root = tk.Tk()
    root.geometry('300x515')
    root.title(value)
    category_var = tk.StringVar(root)
    category_var.set('Strata')
    category_dropdown = tk.OptionMenu(root, category_var, 'Strata', 'Prober', 'General')
    category_dropdown.pack(pady=10)

    error_label = tk.Label(root, text='Error:')
    error_label.pack()
    v = tk.Scrollbar(root, orient='vertical')
    v.pack(side=tk.RIGHT, fill=tk.Y)
    error_entry = tk.Text(root, yscrollcommand=v.set)
    error_entry.pack(expand=True, fill=tk.BOTH)

    def log_error_callback():
        category = category_var.get()
        error = error_entry.get('1.0', 'end')
        if not error.strip():
            messagebox.showerror('Error', 'Please enter an error message')
            return
        log_error(category, error)
        messagebox.showinfo('Success', f'Error logged for {category} category')

    def delete():
        error_entry.delete("1.0", "end")

    log_button = tk.Button(root, text='Log Error', command=lambda: [log_error_callback(), delete()])
    log_button.pack()

    # 区域截图核心逻辑
    def capture_snip():
        global snip_count
        # 创建全屏半透明遮罩窗口
        snip_win = Toplevel(root)
        snip_win.attributes('-fullscreen', True)
        snip_win.attributes('-alpha', 0.3)  # 半透明效果
        snip_win.attributes('-topmost', True)

        # 记录框选坐标
        start_x = start_y = end_x = end_y = 0
        selection_rect = None

        # 鼠标按下事件:记录起点
        def on_mouse_down(event):
            nonlocal start_x, start_y, selection_rect
            start_x = event.x_root
            start_y = event.y_root
            # 创建矩形选框
            selection_rect = snip_win.canvas.create_rectangle(start_x, start_y, start_x, start_y, outline='red', width=2)

        # 鼠标拖动事件:更新选框大小
        def on_mouse_drag(event):
            nonlocal end_x, end_y, selection_rect
            end_x = event.x_root
            end_y = event.y_root
            snip_win.canvas.coords(selection_rect, start_x, start_y, end_x, end_y)

        # 鼠标释放事件:完成截图
        def on_mouse_up(event):
            nonlocal start_x, start_y, end_x, end_y
            snip_win.destroy()  # 关闭遮罩窗口
            # 修正坐标顺序(确保x1<x2,y1<y2)
            x1 = min(start_x, end_x)
            y1 = min(start_y, end_y)
            x2 = max(start_x, end_x)
            y2 = max(start_y, end_y)
            # 截取区域并保存
            if x2 > x1 and y2 > y1:  # 确保选框有效
                image = ImageGrab.grab(bbox=(x1, y1, x2, y2))
                save_path = f"snip_{snip_count}.png"
                image.save(save_path)
                global snip_count
                snip_count += 1
                messagebox.showinfo('Success', f"Screenshot saved as {save_path}")
            else:
                messagebox.showwarning('Warning', 'Invalid selection, no screenshot saved')

        # 在遮罩窗口添加画布用于绘制选框
        snip_win.canvas = tk.Canvas(snip_win, cursor='cross')
        snip_win.canvas.pack(fill=tk.BOTH, expand=True)

        # 绑定鼠标事件
        snip_win.canvas.bind('<ButtonPress-1>', on_mouse_down)
        snip_win.canvas.bind('<B1-Motion>', on_mouse_drag)
        snip_win.canvas.bind('<ButtonRelease-1>', on_mouse_up)

        # 按ESC取消截图
        snip_win.bind('<Escape>', lambda e: snip_win.destroy())

    # 创建截图按钮
    snip_button = tk.Button(root, text='Capture Screenshot', command=capture_snip)
    snip_button.pack(pady=10)

    root.mainloop()

if __name__ == '__main__':
    main()

关键功能说明

  • 全屏遮罩窗口:点击截图按钮后生成全屏半透明窗口,让用户清晰定位截取区域,同时设置为置顶窗口避免被遮挡。
  • 鼠标交互逻辑:
    • 鼠标按下时记录框选起点,生成红色矩形选框
    • 鼠标拖动时实时更新选框大小
    • 鼠标释放时关闭遮罩并截取选框内区域
  • 坐标修正:自动调整坐标顺序,确保截取区域的有效性
  • 取消机制:按ESC键可取消当前截图操作
  • 截图保存:用递增的snip_count保证文件名唯一,保存为PNG格式图片

内容的提问来源于stack exchange,提问作者Marshmellow Physics

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:35:01