Python3 tkinter lay invisible scrollbar over widget? - python-3.x

I would like to lay an invisible scrollbar over a Treeview
I am using a raspberry pi and have a small touchscreen and I thus would like to use the space efficiently
I had to size up my scrollbar as I couldn't figure out how to make "swipe"-scrolling in a treeview possible
That's why I now have very little space and the font is almost too small
Is there any way to make a scrollbar invisible, but still usable when laying on another widget with eg. the place function?

You don't need scrollbars to scroll. All scrollable widgets have an api that is used for scrolling: the xview and yview methods. The scrollbar is just a convenient way to call those methods, but it's not the only way.
I don't know what events a swipe will send, but you can bind to those events and directly call the xview and/or yview methods yourself.
For example, let's assume for the moment that a touch is the <B1> event, and a swipe is the <B1-Motion> event. You can scroll with a swiping motion like this:
class Example:
def __init__(self):
...
self.tree = ttk.Treeview(...)
self.tree.bind("<B1>", self.start_swipe)
self.tree.bind("<B1-Motion>", self.on_swipe)
...
def start_swipe(self, event):
self.last_y = event.y
def on_swipe(self, event):
# only do the scrolling if the swipe is 10 pixels or more
if abs(event.y - self.swipe_start) < 10:
return
# compute whether we are scrolling up or down
delta = -1 if event.y > self.last_y else 1
# remember this location for the next time this is called
self.last_y = event.y
# do the scroll
self.tree.yview_scroll(delta, "units")

Related

How to make PyQt5 QScrollArea horizontal scrollbar show up dynamically?

I created a QScrollArea to show a directory tree and a file tree. When directories or files are shown in this area, the vertical scrollbar appears, but the horizontal scrollbar never works. This is the code (the actual code is very large, so I'm showing only relevant portions):
class SomeWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
def DirectoryOrFileSelection(self):
layoutOne = QVBoxLayout()
self.treeview_tabs = QTabWidget()
self.directoryView = QScrollArea(widgetResizable=True) #QWidget()
self.directoryView.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.filesView = QScrollArea(widgetResizable=True) #QWidget()
self.filesView.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.treeview_tabs.addTab(self.directoryView, "Dirs")
self.treeview_tabs.addTab(self.filesView, "Files")
theHBoxLayout = QHBoxLayout()
leftSpacing = 4
theHBoxLayout.addWidget(self.treeview_tabs, leftSpacing)
layoutOne.addLayout(theHBoxLayout)
self.layout.addLayout(layoutOne)
This is what it looks like when there's no need for a scrollbar.
The vertical scrollbar promptly appears when I make the main window smaller.
However, when I expand one of the directories, even though the filenames go beyond the visible area, the horizontal scrollbar does not appear.
If I use Qt.ScrollBarAlwaysOn in place of Qt.ScrollBarAsNeeded, an inactive scrollbar shows up and never becomes active.
Could anyone help with how to make the horizontal scrollbar active when necessary? I need to be able to scroll horizontally to see the full filenames.
UPDATE: As per suggestions received, I applied the setHorizontalScrollBarPolicy directly to the QTreeView, but even though the scrollbar appears and looks like it's active, it does not seem to recognize when the content is going outside the view area. Shown in the image below.
There is no need to add the view to another scroll area, as all Qt item views are scroll areas.
Note that, unlike the headers of QTableView, the header of QTreeView automatically stretches the last section:
Note: The horizontal headers provided by QTreeView are configured with this property set to true, ensuring that the view does not waste any of the space assigned to it for its header. If this value is set to true, this property will override the resize mode set on the last section in the header.
This means that if you are only showing the first column of the tree, the names of files and directories will always be elided if their width exceeds the width of the widget, and the horizontal scroll bar will not be activated even if it's always shown.
treeview.header().setStretchLastSection(False)
treeview.header().setSectionResizeMode(QHeaderView.ResizeToContents)

Weird white Stripe appearing next to tkinter scrollbar

So while 'designing' my tkinter application i noticed this white stripe appearing next to the scrollbar there:
It is not disappearing when the scrollbar gets to an active state but it is definetly part of the Scrollbar itself because there is nothing under it in my programm which has a white background.
It seems to appear no matter if I use grid or pack. In this case I use grid - here the little extract of my code:
class App(Tk):
def __init__(self):
#other stuff
self.hvf=hvFrame(self,sticky=EW,showfocus=S,bg='white',padx=5,pady=5)
self.hvf.grid(row=1,column=0,columnspan=2,sticky=NSEW,pady=5,padx=(0,5))
sb=Scrollbar(self,orient=VERTICAL,command=self.hvf.yview,bd=0,highlightthickness=0)
sb.grid(row=1,column=2,sticky=NSEW,pady=5)
self.hvf.config(yscrollcommand=sb.set)
If you ned more, then here you go. (Should not be executable for you because you do not have the extras file but you should still be able to understand it.)
EDIT:
A little reproduceable example:
from tkinter import Tk,Frame,Scrollbar,VERTICAL,NS
root = Tk()
frame = Frame(root,height=300,width=500)
frame.grid(row=0,column=0,padx=(0,5),pady=5)
sb = Scrollbar(root,orient=VERTICAL)
sb.grid(row=0,column=1,sticky=NS,pady=5)
root.mainloop()
After testing a few things like changing the windows theme or changing the root and frame background to black the while line still comes up. I cannot be 100% sure but I believe this is due to fact that tkinter on Windows pulls the scrollbar design from Windows itself and it is simply part of that design. It may be a design choice to give the scroll bar some visual depth. That said you cannot do anything to change the design of the scrollbar within a Windows environment so you as stuck with this unless you write a custom scrollbar.
Example code:
import tkinter as tk
root = tk.Tk()
root['bg'] = 'black'
frame = tk.Frame(root, height=300, width=500, background='black')
frame.grid(row=0, column=0)
sb = tk.Scrollbar(root, orient=tk.VERTICAL)
sb.grid(row=0, column=1, sticky=tk.NS)
root.mainloop()
Results from overlay of white and black backgrounds:
Below is the code I use for a custom scrollbar (but did not write myself). You can find the post where I got the code here.

Make a Toplevel window take up the entire monitor space?

I'm trying to make a tkinter Toplevel window to go full screen and take up the entire monitor space (going over the task bar as well). I am able to do this with the Tk() using the code: window.attributes("-fullscreen", True). But I know you can't have two instances of Tk() so I need to use a Toplevel and root.attributes("-fullscreen", True) does not work with Toplevel.
The code I have in place at the moment to make the Toplevel fullscreen is this:
window = Toplevel()
w = window.winfo_screenwidth()
h = window.winfo_screenheight()
window.geometry("%dx%d+0+0" % (w,h))
But this does not even go into a proper fullscreen mode as the left side of the interface does not reach the edge of the screen.
How can I get a Toplevel window to take up the entire space of the screen like a Tk() window can?
I found a work around to this issue. What I did was I set the Tk() interface to take up the entire screen, and I placed a frame on top of it and made it fit the Tk(). That way I could put the desired widgets on the frame and when I'm done, destroy the frame and reset the size of the Tk() frame.
This is a possible way to solve fullscreen trouble:
finestra1= Toplevel()
wf1= finestra1.winfo_screenwidth()
hf1= finestra1.winfo_screenheight()
A = str(wf1)
B = str(hf1)
finestra1.geometry(A+"x"+B)

Working with pop up canvases

Trying to create a pop up canvas that will pop up when I hit the certain key combination and then it will display images that can be scrolled through using the arrow keys and then closed out with the escape key. This is all going on top of a canvas that has a map drawn on it that I can use the the arrow keys to move around the map. Since I'm having many issues with this that are all pretty much interrelated I figured I would just asking them all with one post instead of doing separate posts, it may come in very handy to have all the information in one place for other people as well.
Currently, I have tried several approaches to solve the problem, I do see one other possible option just a couple minutes ago since I finally got online this morning but it didn't seem to want to work.
Question 1: Where do you form the pop up canvas? In the original
def __init__(self, parent):
or do you normally wait until the time of the switch over to the pop up to form the pop canvas? I have tried it both ways and have seen problems with both approaches. Forming it at the start up causes the pop up to pop up on the screen right from the get go. I have seen on effbot.org that supposedly using the state='hidden' attribute exists but when I try to use it all I get is an error that says I can only use 'disabled' or 'normal'.
Question 2: So how do you hide the popup until you want to use it, if you create the pop up at the start of the program versus waiting until the pop up key combo is pushed?
I have tried sizing the pop up to 0x0 but I still see single pixel on the screen until I hit the key combo.
I have tried waiting until key combo is hit to bring up the pop up and that works fine until I get ready to change to the second image.
On effbot.org I saw and have tried several different combinations to try to 'disable' without destroying(which I don't want to do) the canvas underneath when the pop up comes up. The trouble I have is when I go and push an arrow key the underneath canvas still has the focus and it moves the map instead of change the image on the pop up. I have tried several of the effbot combinations...
self.canvas.config(state='disabled')
self.canvaspopup = Canvas(self, width=800, height=614)
self.canvaspopup.pack_propagate(0)
self.canvaspopup.place(x=284,y=52)
self.png = Label(self.canvaspopup)
self.png.pack()
self.canvaspopup.focus_set()
I have also tried
self.canvas.bind('<FocusOut>')
self.canvaspopup.bind('<FocusIn>')
And still no luck, the underneath canvas still has the focus and I'm still moving the map instead of advancing the image in the pop up. I am guessing given it was on the Events and Bindings page on effbot.org that I saw FocusIn/Out being talked about that I should put that in the .bind statement and not in the .config.
Question 3: How do I get the pop up to take the focus, pretty much should only need keyboard focus and no mouse focus?
I know I want to keep the pop up handy as I want to be able to click on the underlying map in different locations and have the program go to the internet and pull up data for those locations so the pop up is something that will be used several times throughout any one run of the program. I know from what I've seen I can use
self.canvaspopup.destroy()
But this destroys the canvas so I would have to recreate it again. So if I put the pop up creation at the beginning of the program...I'm screwed and can't recreate it. Which leads right back to Question 2.
Definitely a mess. I have most everything working fine, its just the interaction, and knowing the proper way of handling this kind of situation that has me stumped.
Since you are using place, you can use place_forget to hide the popup. For the focus, you use focus_set.
Here's an example:
import tkinter as tk
class PopupCanvas(tk.Canvas):
def __init__(self, parent, *args, **kwargs):
tk.Canvas.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.text_item=self.create_text(10, 10, anchor="nw")
self.bind("<Left>", self.handle_left)
self.bind("<Right>", self.handle_right)
self.bind("<Escape>", self.hide)
def handle_left(self, event=None):
self.itemconfigure(self.text_item, text="You clicked left")
def handle_right(self, event=None):
self.itemconfigure(self.text_item, text="You clicked right")
def show(self, event=None):
self.place(relx=.5, rely=.5, anchor="center")
self.focus_set()
def hide(self, event=None):
self.place_forget()
self.parent.focus_set()
root = tk.Tk()
label = tk.Label(root, text="Press 'p' to pop up a canvas, <escape> to hide it\n" +
"Press left and right arrows when the popup is visible")
canvas = tk.Canvas(root, width=400, height=400)
popup = PopupCanvas(canvas, width=200, height=200, background="pink")
label.pack(side="top", fill="x")
canvas.pack(side="top", fill="both", expand=True)
canvas.focus_set()
canvas.bind("<p>", popup.show)
root.mainloop()

Python Tkinter Clickable Text?

I'm wondering if there's a way to make clickable text in Tkinter. Maybe like you would see on a title screen of a game, and where you hover your mouse over the text and it changes color/hightlights itself. All I need the click to do is execute another function.
Are either of these things possible? Thanks!
you are looking for tkinter's events:
tk_widget.bind("<Button-1>",CALLBACK)
The call back needs to take an event argument which is a dictionary containing information about what triggered the event.
This can run into issues with widgets that overlap such as windows in a canvas or labels sometimes triggering the callback for the window behind it.
For hovering the mouse over a widget the event is called "<Enter>" and moving mouse out of widget region is called "<Leave>" for highlighting text effect, if you just want to capture a click anywhere on a window then on the root call root.bind_all("<Button-1>",CALLBACK)
source: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/events.html
example:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
def change_case(event=None):
new_text = str.swapcase(lab["text"])
lab.config(text=new_text)
def red_text(event=None):
lab.config(fg="red")
def black_text(event=None):
lab.config(fg="black")
root = tk.Tk()
lab = tk.Label(root,text="this is a test")
lab.bind("<Button-1>",change_case)
lab.bind("<Enter>",red_text)
lab.bind("<Leave>",black_text)
lab.grid()
root.mainloop()
hope this helps :)

Resources