I'm creating a game launcher specially in Python 3.7 tkInter, and I want to make my own styled Scrollbar (in Windows 10 (version 1903)).
I've tried adding a hidden Scrollbar, and hiding works but i can't simulate it:
def scroll(self, i, reqHeight, vbarValue):
print("vbarValue", vbarValue)
value = -i / 1.4
a1 = int(self.canvass.coords(self.scroll2)[1]) == 5
a2 = value > 0
a = not(a1 ^ a2)
b1 = ((self.canvass.coords(self.scroll2)[3] > self.cHeight))
b2 = value < 0
b = not(b1 ^ b2)
print(value, value < 0)
print(a1, 5)
print("====")
print(a1, a2)
print(a)
print("----")
print(b1, b2)
print(b)
print("====\n\n")
print("OK")
x1, y1, x2, y2 = self.canvass.coords(self.scroll2)
_y1, _y2 = vbarValue
print("1:",y1, y2)
print("2:",_y1, _y2)
print("3:",(_y2 - _y1) / 2 - y2)
print("4:",(_y1 + (_y2 - _y1) / 120) * self.cHeight)
print("5:",(_y1 + (_y2 - _y1) / 120) * self.cHeight - (y2 / y1))
print("6:",((_y2 - _y1) / 120) * self.cHeight - y2* -i)
print("7:",(_y1 + (_y2 - _y1) / 120))
value = (_y1 + (_y2 - _y1) / 120) * self.cHeight / (y1 / y2)
print("8:",(y2 / y1))
# value = value - (y1 / y2)
print("Dynamic Canvas Region Height:")
print("DCRH:", self.cHeight)
print("Value: %s", value)
self.canvass.move(self.scroll2, 0, -y2)
self.canvass.move(self.scroll2, 0, value)
print("coords: %s" % self.canvass.coords(self.scroll2))
print("reqHeight: %s" % reqHeight)
Event:
def _bound_to_mousewheel(self, event): # <Enter> Event
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event): # <Leave> Event
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event): # <Configure> Event
self.canv.yview_scroll(int(-1 * (event.delta / 120)), "units")
self.scrollCommand(int(-1 * (event.delta / 120)), self.scrollwindow.winfo_reqheight(), self.vbar.get())
def _configure_window(self, event):
# update the scrollbars to match the size of the inner frame
size = (self.scrollwindow.winfo_reqwidth(), self.scrollwindow.winfo_reqheight()+1)
self.canv.config(scrollregion='0 0 %s %s' % size)
# if self.scrollwindow.winfo_reqwidth() != self.canv.winfo_width():
# # update the canvas's width to fit the inner frame
# # self.canv.config(width=self.scrollwindow.winfo_reqwidth())
# if self.scrollwindow.winfo_reqheight() != self.canv.winfo_height():
# # update the canvas's width to fit the inner frame
# # self.canv.config(height=self.scrollwindow.winfo_reqheight())
By the way, self.scrollCommand(...) is the same as scroll on the first code.
I expect to get some x and y output for the canvas.move method.
How do i simulate a Scrollbar in Tkinter Canvas
The scrollbar has a well defined interface. To simulate a scrollbar all you need to do is implement this interface. This is most easily done by creating a class that has the following attributes:
you need to define the set method, which is called whenever the widget being scrolled wants to update the scrollbar
you need to add mouse bindings to call the yview method of the widget being controlled by the scrollbar (or xview if creating a horizontal widget).
If you do those two things, your scrollbar can be used exactly like a built-in scrollbar.
For the rest of this answer, I'm going to assume you want to simulate a vertical scrollbar. Simulating a horizontal scrollbar works identically, but instead of 'top' and 'bottom', you are dealing with 'left' and right'.
Defining the set method
The set method will be called with two fractions. The canonical documentation describes it like this:
This command is invoked by the scrollbar's associated widget to tell the scrollbar about the current view in the widget. The command takes two arguments, each of which is a real fraction between 0 and 1. The fractions describe the range of the document that is visible in the associated widget. For example, if first is 0.2 and last is 0.4, it means that the first part of the document visible in the window is 20% of the way through the document, and the last visible part is 40% of the way through.
Defining the bindings
The other half of the equation is when the user interacts with the scrollbar to scroll another widget. The way this happens is that the scrollbar should call the yview command of the widget to be controlled (eg: canvas, text, listbox, etc).
The first argument you must pass to the command is either the string "moveto" or "scroll".
If "moveto", the second argument is a fraction which represents the amount that has been scrolled off of the top. This is typically called when clicking on the scrollbar, to immediately move the scrollbar to a new position
if "scroll", the second argument is an integer representing an amount, and the third argument is either the string "units" or "pages". The definition of "units" refers to the value of the yscrollincrement option. "pages" represents 9/10ths of the window height. This is typically called when dragging the mouse over the scrollbar.
The options are spelled out in the man pages for each scrollable widget.
Example
The following is an example of a uses a text widget, so that you can see that when you type, the scrollbar properly grows and shrinks. If you click anywhere in the scrollbar, it will scroll to that point in the documentation.
To keep the example short, this code doesn't handle dragging the scrollbar, and it hard-codes a lot of values that probably ought to be configurable. The point is to show that all you need to do to simulate a scrollbar is create a class that has a set method and which calls the yview or xview method of the connected widget.
First, the scrollbar class
import tkinter as tk
class CustomScrollbar(tk.Canvas):
def __init__(self, parent, **kwargs):
self.command = kwargs.pop("command", None)
tk.Canvas.__init__(self, parent, **kwargs)
# coordinates are irrelevant; they will be recomputed
# in the 'set' method
self.create_rectangle(0,0,1,1, fill="red", tags=("thumb",))
self.bind("<ButtonPress-1>", self.on_click)
def set(self, first, last):
first = float(first)
last = float(last)
height = self.winfo_height()
x0 = 2
x1 = self.winfo_width()-2
y0 = max(int(height * first), 0)
y1 = min(int(height * last), height)
self.coords("thumb", x0, y0, x1, y1)
def on_click(self, event):
y = event.y / self.winfo_height()
self.command("moveto", y)
Using the class in a program
You would use this class exactly like you would a native scrollbar: instantiate it, and set the command to be the yview command of a scrollable widget.
This example uses a text widget so you can see the scrollbar updating as you type, but the exact same code would work with a Canvas, or any other scrollable window.
root = tk.Tk()
text = tk.Text(root)
sb = CustomScrollbar(root, width=20, command=text.yview)
text.configure(yscrollcommand=sb.set)
sb.pack(side="right", fill="y")
text.pack(side="left", fill="both", expand=True)
with open(__file__, "r") as f:
text.insert("end", f.read())
root.mainloop()
Related
I found the following piece of code here.
from tkinter import *
canvas_width = 500
canvas_height = 150
def paint( event ):
python_green = "#476042"
x1, y1 = ( event.x - 1 ), ( event.y - 1 )
x2, y2 = ( event.x + 1 ), ( event.y + 1 )
w.create_oval( x1, y1, x2, y2, fill = python_green )
master = Tk()
master.title( "Painting using Ovals" )
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack(expand = YES, fill = BOTH)
w.bind( "<B1-Motion>", paint )
message = Label( master, text = "Press and Drag the mouse to draw" )
message.pack( side = BOTTOM )
master.mainloop()
It lets you draw on a Tkinter canvas by dragging your mouse around. It works just fine when I move my mouse very very slowly but the moment I move it a bit faster, the lines end up broken. Here's what I mean:
Screenshot of the Tkinter window
The line on the top was drawn slowly.
The one on the bottom, rather quickly.
As you can see, the bottom one is missing points in many places. How do I fix this?
If you want to draw a line, you need to register the coordinates of where the mouse moves and create line based on that using create_line method because it connects those points. So append positions of mouse to a list and draw a line and use the coordinates from that list. When you start drawing (click mouse), append the starting coordinates. Then on movement append the coordinates, delete the previous line (basically updating) and draw a new one. When mouse button is released clear the list of points and set the current line id to None to indicate that currently no line is drawn.
import tkinter as tk
line_id = None
line_points = []
line_options = {}
def draw_line(event):
global line_id
line_points.extend((event.x, event.y))
if line_id is not None:
canvas.delete(line_id)
line_id = canvas.create_line(line_points, **line_options)
def set_start(event):
line_points.extend((event.x, event.y))
def end_line(event=None):
global line_id
line_points.clear()
line_id = None
root = tk.Tk()
canvas = tk.Canvas()
canvas.pack()
canvas.bind('<Button-1>', set_start)
canvas.bind('<B1-Motion>', draw_line)
canvas.bind('<ButtonRelease-1>', end_line)
root.mainloop()
Also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
So, I was recently checking out VsCode, and I noticed an interesting feature. Although there was a taskbar icon, there was no titlebar; instead, VsCode implements its own. I looked at some other programs from Microsoft, and they do the same thing. I think this is a very cool feature.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps. Unfortunately, the standard way to get rid of the titlebar in Tkinter is to disable the Window Manager (using overridedirect(1)). This also gets rid of the taskbar icon, which I want to keep.
In other words, what I am trying to get is
while still keeping this: .
* For reference I am using Python 3.8 and TkInter 8.6.
You can create your own buttons titlebar using frames. Here take a look at this. I also worked on a tkinter based app and created this along with using what #Hruthik Reddy has given.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps.
Assuming that you may have used classes at some point in those apps, I have created this inherited Tk subclass, and explained in comments:
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
class TestApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# set overrideredirect to True to remove the windows default decorators
self.overrideredirect(True)
self.geometry('700x500+10+10') # you may or may not want to initialize the geometry of the window
self.minsize(193, 109)
# (x, y) coordinates from top left corner of the window
self.x = None
self.y = None
# Create a frame that will contain the title label of the window
self.frame = tk.Frame(self, bg='gray38')
self.frame.pack(side=tk.TOP, fill=tk.X)
# Label `name` for the window
# Since buttons are on the right side and the name of the window is on the left side, the label will be packed towards LEFT side
self.name = tk.Label(self.frame, text='Simple Text Box', font='Consolas 11',
bg=self.frame.cget('background'), fg='white')
self.name.pack(side=tk.LEFT, fill=tk.X, anchor=tk.CENTER)
# Pack the close button to the right-most side
self.close = tk.Button(self.frame, text='✕', bd=0, width=3, font='Consolas 13',
command=self.destroy, bg=self.frame.cget('background'))
self.close.pack(side=tk.RIGHT)
# Pack the maximize button second from the right
# The unicode string as the value of the keyword `text` below, is taken from the internet, it contains the maximize icon as unicode character
self.maximize = tk.Button(self.frame, text=u"\U0001F5D6", bd=0, width=3, font='Consolas',
command=self.maximize_win, bg=self.frame.cget('background'))
self.maximize.pack(side=tk.RIGHT)
# Pack the minimize button third from the right
self.minimize = tk.Button(self.frame, text='—', bd=0, width=3, font='Consolas 13',
command=self.minimize_win, bg=self.frame.cget('background'))
self.minimize.pack(side=tk.RIGHT)
# -------------------
# NOW YOU CAN PUT WHATEVER WIDGETS YOU WANT AFTER THIS BUT FOR THIS EXAMPLE I
# HAVE TAKEN A TEXTBOX WITH HORIZONTAL AND VERTICAL SCROLLBARS AND A SIZEGRIP
# -------------------
# The frame below contains the vertical scrollbar and the sizegrip (sizegrip helps in resizing the window
self.scroll_frame = tk.Frame(self)
v_scroll = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL)
h_scroll = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.grip = ttk.Sizegrip(self.scroll_frame)
# I am directly putting the textbox in the window, you may add frames and other stuff
self.text = tk.Text(self, wrap=tk.NONE, yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set,
font='Consolas 14', width=1, height=1)
# set the scrollbar for y and x views of the textbox respectively
v_scroll.config(command=self.text.yview)
h_scroll.config(command=self.text.xview)
# Packing scrollbar frame, the scrollbars and the grip according to the arrangement I want
self.scroll_frame.pack(side=tk.RIGHT, fill=tk.Y)
v_scroll.pack(side=tk.TOP, fill=tk.Y, expand=tk.Y)
self.grip.pack(side=tk.BOTTOM)
self.text.pack(side=tk.TOP, expand=tk.TRUE, fill=tk.BOTH)
h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
self.grip.bind("<B1-Motion>", self.onmotion)
# Bind the motion of mouse after mouse click to the onmotion function for window resizing
self.call('encoding', 'system', 'utf-8')
# Binding `<Enter>` and `<Leave>` mouse event to their respective functions
# `<Enter>` event is called when the mouse pointer enters any widget
# `<Leave>` event is called when the mouse pointer leaves any widget
# Here when the mouse pointer enters or leaves the buttons their color will change
self.close.bind('<Enter>', lambda _: self.close.config(bg='red'))
self.close.bind('<Leave>', lambda _: self.close.config(bg=self.frame.cget('background')))
self.minimize.bind('<Enter>', lambda _: self.minimize.config(bg='gray58'))
self.minimize.bind('<Leave>', lambda _: self.minimize.config(bg=self.frame.cget('background')))
self.maximize.bind('<Enter>', lambda _: self.maximize.config(bg='gray58'))
self.maximize.bind('<Leave>', lambda _: self.maximize.config(bg=self.frame.cget('background')))
# Now you may want to move your window (obviously), so the respective events are bound to the functions
self.frame.bind("<ButtonPress-1>", self.start_move)
self.frame.bind("<ButtonRelease-1>", self.stop_move)
self.frame.bind("<B1-Motion>", self.do_move)
self.frame.bind('<Double-1>', self.maximize_win)
self.name.bind("<ButtonPress-1>", self.start_move)
self.name.bind("<ButtonRelease-1>", self.stop_move)
self.name.bind("<B1-Motion>", self.do_move)
self.name.bind('<Double-1>', self.maximize_win)
def start_move(self, event):
""" change the (x, y) coordinate on mousebutton press and hold motion """
self.x = event.x
self.y = event.y
def stop_move(self, event):
""" when mouse button is released set the (x, y) coordinates to None """
self.x = None
self.y = None
def do_move(self, event):
""" function to move the window """
self.wm_state('normal') # if window is maximized, set it to normal (or resizable)
self.maximize.config(text=u"\U0001F5D6") # set the maximize button text to the square character of maximizing window
deltax = event.x - self.x
deltay = event.y - self.y
x = self.winfo_x() + deltax
y = self.winfo_y() + deltay
self.geometry(f"+{x}+{y}")
def onmotion(self, event):
""" function to change window size """
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
x1 = self.winfo_pointerx()
y1 = self.winfo_pointery()
x0 = self.winfo_rootx()
y0 = self.winfo_rooty()
self.geometry("%sx%s" % ((x1-x0), (y1-y0)))
return
def minimize_win(self, event=None):
""" function to iconify or minimize window as an icon """
self.overrideredirect(False)
self.wm_iconify()
self.bind('<FocusIn>', self.on_deiconify)
def maximize_win(self, event=None):
""" function to maximize window or make it normal (exit maximize) """
if self.maximize.cget('text') == u"\U0001F5D7":
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
return
self.wm_state('zoomed')
self.maximize.config(text=u"\U0001F5D7")
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style)
# re-assert the new window style
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
if __name__ == '__main__':
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
app = TestApp()
# print(app.tk.call('tk', 'windowingsystem'))
# # Here root.tk.call('tk', 'windowingsystem') calls tk windowingsystem in Tcl, and that returns 'win32',
# # 'aqua' or 'x11' as documented in tk
app.after(10, lambda: set_appwindow(root=app))
app.text.insert(1.0, 'Drag the window using the title or the empty area to the right of the\ntitle.'
' Try maximizing / minimizing.\n\n-- YOU MAY HAVE A PROBLEM WITH RESIZING --\n'
'-- ALSO IF YOU REMOVE `height` AND `width` KEYWORDS FROM THE TEXTBOX DECLARATION'
' AND FONT SIZE IS TOO BIG THE SCROLLBAR MAY DISAPPEAR --\nSO KEEP THOSE KEYWORDS THERE!')
app.mainloop()
When window is maximized you may not be able to see the taskbar. But you can still resize it using sizegrip. I don't yet know how to make window resizing possible from window borders with overrideredirect but Sizegrip works just fine.
Now regarding the set_appwindow function, this is what MSDN says:
The Shell creates a button on the taskbar whenever an application
creates a window that isn't owned. To ensure that the window button is
placed on the taskbar, create an unowned window with the
WS_EX_APPWINDOW extended style. To prevent the window button from
being placed on the taskbar, create the unowned window with the
WS_EX_TOOLWINDOW extended style. As an alternative, you can create a
hidden window and make this hidden window the owner of your visible
window.
Complete reference here
This may seem like a very long answer but I hope it covers all what you need and helps you.
Check out the following code it worked for me :-
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
def main():
root = tk.Tk()
root.wm_title("AppWindow Test")
button = ttk.Button(root, text='Exit', command=lambda: root.destroy())
button.place(x=10,y=10)
root.overrideredirect(True)
root.after(10, lambda: set_appwindow(root))
root.mainloop()
if __name__ == '__main__':
main()
In the function on_deiconify() we need to unbind the previous event <FocusIn> to avoid from blitting of the window we write like this
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
self.unbind("<FocusIn>")
I'm writing a Phyton app using tkinter for the GUI, and want to plot real time sensor data in a never ending X-Y graph that scrolls the old data off the left hand side of the canvas. It's ok for the old data to be lost.
I'd prefer not to use matplotlib as my needs are simple and will be targeting a RPi 3 so not a lot of resources. I'm thinking of just using tkinter "create_line" for each new sample. It's just the scrolling that I'm unsure about.
Example keeps values y on list data and uses after to run function move every 500ms (0.5s) which removes first value from list and add new value at the end and then remove all lines from canvas to draw new ones.
It could keep IDs of lines on list, and moves lines but current method seems simpler.
#!/usr/bin/env python3
import tkinter as tk
import random
# --- constants --- (UPPER_CASE names)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 200
DISTANCE = 50
NUMBER = (SCREEN_WIDTH // DISTANCE) + 1
# --- functions --- (lower_case names)
def move():
# remove first
data.pop(0)
# add last
data.append(random.randint(0,SCREEN_HEIGHT))
# remove all lines
canvas.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(data, data[1:])):
x1 = x * DISTANCE
x2 = (x+1) * DISTANCE # x1 + DISTANCE
canvas.create_line([x1, y1, x2, y2])
# run again after 500ms (0.5s)
root.after(500, move)
# --- main --- (lower_case names)
# data at start
data = [random.randint(0, SCREEN_HEIGHT) for _ in range(NUMBER)]
root = tk.Tk()
root.geometry('{}x{}'.format(SCREEN_WIDTH, SCREEN_HEIGHT))
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT)
canvas.pack()
# start animation
move()
root.mainloop()
EDIT: DISTANCE = 2
EDIT:
This code use two little different classes to draw graph. Now they need only append new element to move line
def move():
graph.append(random.randint(0,SCREEN_HEIGHT))
graph_canvas.append(random.randint(0,SCREEN_HEIGHT))
# run again after 100ms (0.1s)
root.after(100, move)
Class Graph gets canvas as argument so teoreticly you can add graph to existing canvas. But it remove all object so it would need changes to resolve this problem.
Class GraphCanvas is widget which resize Canvas and doesn't need existing canvas. Teoreticly you can add other elements like to normal canvas but again problem is that it removes all object and it would need changes in code.
It would use canvas.move(offset_x, offset_y) to move lines and then it wouldn't need to delete lines. Or maybe it would use canvas.create_polygon but it would have to first create list with pairs (x,y)
Both change "distance" between point so they use all space when canvas change size horizontally. Vertically it would need recalculations.
I added colors to see if canvases change size in window.
import tkinter as tk
import random
# --- constants --- (UPPER_CASE_NAMES)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 200
DISTANCE = 20
NUMBER = (SCREEN_WIDTH // DISTANCE) + 1
# --- classes --- (CamelCaseNames)
class Graph(object):
def __init__(self, canvas, data, *args, **kwargs):
super().__init__()
self.canvas = canvas
self.data = data
self.draw()
def append(self, item, remove_first=True):
if remove_first:
self.data.pop(0)
self.data.append(item)
self.draw()
def draw(self):
# new distance if size changed
self.distance = int(self.canvas.winfo_width())/len(self.data)+1
# remove all lines
self.canvas.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(self.data, self.data[1:])):
x1 = x * self.distance
x2 = (x+1) * self.distance # x1 + self.distance
self.canvas.create_line([x1, y1, x2, y2])
class GraphCanvas(tk.Canvas):
def __init__(self, master, data, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.data = data
self.draw()
def append(self, item, remove_first=True):
if remove_first:
self.data.pop(0)
self.data.append(item)
self.draw()
def draw(self):
# new distance if size changed
self.distance = int(self.winfo_width())/len(self.data)+1
# remove all lines
self.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(self.data, self.data[1:])):
x1 = x * self.distance
x2 = (x+1) * self.distance # x1 + self.distance
self.create_line([x1, y1, x2, y2])
# --- functions --- (lower_case_names)
def move():
graph.append(random.randint(0,SCREEN_HEIGHT))
graph_canvas.append(random.randint(0,SCREEN_HEIGHT))
# run again after 100ms (0.1s)
root.after(100, move)
# --- main --- (lower_case names)
# data at start
data = [random.randint(0, SCREEN_HEIGHT) for _ in range(NUMBER)]
root = tk.Tk()
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='#ccffcc')
canvas.pack(fill='both', expand=True)
graph = Graph(canvas, data)
graph_canvas = GraphCanvas(root, data, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='#ffcccc')
graph_canvas.pack(fill='both', expand=True)
# start animation
move()
root.mainloop()
Using class you can easily add more graphs
Is there a way to use appJar itself to get the screen height and width.
Alternativley since appJar is a wrapper for tkinter is there a way for me to create a Tk() instance to utilise the below code I have seen used everywhere whilst research:
import tkinter
root = tkinter.Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
I would like to do this so I can use these sizes for setting window sizes later with the .setGeometry() method, e.g.
# Fullscreen
app.setGeometry(width, height)
or:
# Horizontal halfscreen
app.setGeometry(int(width / 2), height)
or:
# Vertical halfscren
app.setGeometry(width, int(height / 2))
Since appJar is just a wrapper over tkinter, you need a reference to root/master instance of Tk(), which stored as self.topLevel in gui.
Alternatively, you can take a reference to a prettier self.appWindow, which is "child" canvas of self.topLevel.
To make all things clear - just add some "shortcuts" to desired methods of an inherited class!
import appJar as aJ
class App(aJ.gui):
def __init__(self, *args, **kwargs):
aJ.gui.__init__(self, *args, **kwargs)
def winfo_screenheight(self):
# shortcut to height
# alternatively return self.topLevel.winfo_screenheight() since topLevel is Tk (root) instance!
return self.appWindow.winfo_screenheight()
def winfo_screenwidth(self):
# shortcut to width
# alternatively return self.topLevel.winfo_screenwidth() since topLevel is Tk (root) instance!
return self.appWindow.winfo_screenwidth()
app = App('winfo')
height, width = app.winfo_screenheight(), app.winfo_screenwidth()
app.setGeometry(int(width / 2), int(height / 2))
app.addLabel('winfo_height', 'height: %d' % height, 0, 0)
app.addLabel('winfo_width', 'width: %d' % width, 1, 0)
app.go()
Fortunately, appJar does allow you to create Tk() instance. So I was able to create an instance using the functions to retrieve the dimensions and destroy the then unneeded instance.
# import appjar
from appJar import appjar
# Create an app instance to get the screen dimensions
root = appjar.Tk()
# Save the screen dimensions
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
# Destroy the app instance after retrieving the screen dimensions
root.destroy()
I am trying to create a simple GUI in python using tkinter. What I am trying to do is
Place my entry element in the center of the upper half of the GUI window
Place a button right next to it
On clicking button, open up an interface to choose a file
Display the file name along with its path in the entry element
def center_window(width, height):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
def OnButtonClick(self):
self.entryVariable.set( tkinter.filedialog.askopenfilename() )
self.entry.focus_set()
self.entry.selection_range(0, tkinter.END)
root = tkinter.Tk()
center_window(400, 300)
root.title("A simple GUI")
root.entryVariable = tkinter.StringVar()
root.entry = tkinter.Entry(root,textvariable=root.entryVariable)
root.entry.grid(column=10,row=5,columnspan=20)
B = tkinter.Button(root, text ="Choose", command=OnButtonClick(root))
B.grid(column=30,row=5, columnspan=2)
Could anybody guide me how can I move entry element and button in the center of the upper half of the GUI window. Also, how can I make tkinter.filedialog.askopenfilename() function to be invoked on clicking the button. It gets invoked as soon as the GUI window opens when I run the above code. Thanks.
Here is the revised code. Basically, you need to pass a function object to the command argument of a Button, which means you can either pass a function without the trailing parenthesis (if it doesn't take any argument) or use lambda. In your original code, your function was executed immediately after the python interpreter reaches that line. Also, you need to call root.mainloop at the end of the program.
import tkinter
import tkinter.filedialog
def center_window(width, height):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
def OnButtonClick(self):
self.entryVariable.set( tkinter.filedialog.askopenfilename() )
self.entry.focus_set()
self.entry.selection_range(0, tkinter.END)
root = tkinter.Tk()
center_window(400, 300)
root.title("A simple GUI")
root.entryVariable = tkinter.StringVar()
frame=tkinter.Frame(root)
root.entry = tkinter.Entry(frame,textvariable=root.entryVariable)
B = tkinter.Button(frame, text ="Choose", command=lambda: OnButtonClick(root))
root.entry.grid(column=0,row=0)
B.grid(column=1,row=0)
frame.pack(pady=100) #Change this number to move the frame up or down
root.mainloop()