-->

Python tkinter canvas flickering

2020-05-05 18:38发布

问题:

Firstly I should state that I am aware of other posts on this site with similar names. I have been through them but they do not, so far as I can tell, address my problem. I would actually say my problem is far more simple than most of those examples.

Simply put, I wanted to create a transparent rectangle that I could use to show a drag select area. When I found out that tkinter doesn't do transparency, I decided on the idea of simply drawing four lines to create the appearance of a rectangle without drawing anything between those lines. Not ideal, but for my small project it was enough (although if anyone knows a library that does transparency and colour gradients and things without much fussing around, I'm all ears).

So I created my canvas. On mouse down I saved the coordinates and also created four lines at that coordinate (effectively a one pixel rectangle), and on mouse move I used the canvas.coords method to move the lines. On mouse up I removed the lines, using canvas.delete.

The code seems to work fine, but it's (what I assume is) the updating of the canvas that's the problem. If I click and drag really slowly, the rectangle will appear as I want it to. If I move faster (and I should clarify that I consider this drag speed to be well within normal usage), the bottom and right lines just disappear until the mouse either slows down or stops. Had I been whizzing the mouse around like crazy I might have understood, but the speed at which this happens is really not that fast. So the lines very visibly either disappear, or flicker as occasionally the update is fast enough to keep track of the moving mouse.

Basically it looks pretty bad, however there is something of note. So if I drag my mouse down and to the right (to make the box larger), this effect will happen. When I drag it back to the start, to make a smaller box, it does not occur no matter how fast I try to do it. I'm sure this is some quirk of the canvas widget, but I'd like to know either how to fix it or if people have just gone on to other libraries instead of tkinter (if so, what are they?)

The code is about as simple as it comes for this kind of thing:

def OnLeftMouseDown(event):
    global InitialX, InitialY, Line1ID, Line2ID, Line3ID, Line4ID
    InitialX = event.x
    InitialY = event.y
    Line1ID = canvas.create_line(event.x, event.y, event.x, event.y, fill='green')
    Line2ID = canvas.create_line(event.x, event.y, event.x, event.y, fill='green')
    Line3ID = canvas.create_line(event.x, event.y, event.x, event.y, fill='green')
    Line4ID = canvas.create_line(event.x, event.y, event.x, event.y, fill='green')

def OnLeftMouseUp(event):
    canvas.delete(Line1ID)
    canvas.delete(Line2ID)
    canvas.delete(Line3ID)
    canvas.delete(Line4ID)

def OnLeftMouseMove(event):
    canvas.coords(Line1ID, InitialX, InitialY, InitialX, event.y)
    canvas.coords(Line2ID, InitialX, InitialY, event.x, InitialY)
    canvas.coords(Line3ID, event.x, InitialY, event.x, event.y)
    canvas.coords(Line4ID, InitialX, event.y, event.x, event.y)


root = tk.Tk()
root.geometry("1000x600")

global InitialX, InitialY
global Line1ID, Line2ID, Line3ID, Line4ID

canvas = tk.Canvas(root, width=800, height=600, bg='white')
canvas.pack()

canvas.bind("<ButtonPress-1>", OnLeftMouseDown)
canvas.bind("<B1-Motion>", OnLeftMouseMove)
canvas.bind("<ButtonRelease-1>", OnLeftMouseUp)

root.mainloop()

I've had this problem while dragging other items on a canvas too. I wanted a series of visual 'nodes' that were just frames containing information. Dragging them at even a normal speed cut off the right most and bottom most parts of the frame.

Edit: My OS is Windows 7. I have tried the code suggested below by user sciroccorics and have also tried adding to my own code the canvas method 'update_idletasks'. No change. My program has the flicker and sciroccorics' code has the same flicker. Furthermore I've used Microsoft Expression Encoder 4 to capture the desktop to show what's going on. With screen caps and that program, I get no visible flicking / disappearing lines (even when I set the recorder to record at 60fps). Although I was able to use my phone to record what I was seeing so we can see that here: Vimeo vid of flickering

回答1:

Here is the minimal version of your example, using one rectangle instead of four lines (I've added a blue circle on the canvas, to show that the rectangle is transparent). It does not show any flickering or vanishing edges on my (rather old) laptop. How is it on your device?

As noted by @stovfl, tkinter display refreshing is indeed much less efficient on Windows 10 as it used to be on Windows 7 (which was already much less efficient as it has always been on Linux/X11). I added an update_idletasks() on the mouse move callback, as it sometimes improves refreshing errors. You may comment it out, and test if it offers any difference.

import tkinter as tk

def OnLeftMouseDown(event):
    global InitialX, InitialY, RectID
    InitialX, InitialY = event.x, event.y
    RectID = canvas.create_rectangle(InitialX, InitialY, InitialX, InitialY)

def OnLeftMouseUp(event):
    canvas.delete(RectID)

def OnLeftMouseMove(event):
    canvas.update_idletasks()
    canvas.coords(RectID, InitialX, InitialY, event.x, event.y)

root = tk.Tk()

canvas = tk.Canvas(root, width=800, height=600, bg='white')
canvas.pack()
canvas.create_oval(200, 200, 400, 400, fill='blue')

canvas.bind("<ButtonPress-1>", OnLeftMouseDown)
canvas.bind("<B1-Motion>", OnLeftMouseMove)
canvas.bind("<ButtonRelease-1>", OnLeftMouseUp)

root.mainloop()