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()
Related
The below class is a test to display a pdf file and a text box ontop inside a QGraphicsView. I would like to show only the top part of the pdf. And it is supposed to fill the width of the view. So it is correct here that the view does not have the same aspect ratio as the pdf.
The GraphicsView class will later be instantiated in the MainView class. The MainView is instantiated in the Application class.
I am using the resizeEvent method to scale the QGraphicsView to the viewport width when the window size changes. But for some reason I cannot get the pdf file to fill the width when the window first gets opened and loads the pdf. So when running the code, the PDF is way smaller then the window width and only when I resize the window, it pops into the correct width.
This is what it looks like, when I run the code:
For debugging purposes I set the background color of the scene to red. So I guess the pdf does not fit the scene width?
I tried calling the _update_view function inside the load_pdf function, I also tried to scale the view inside the load_pdf function, but this does not work. Does anybody have an idea how to fix this?
import fitz
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class GraphicsView(QtWidgets.QWidget):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self._init_view()
self._init_scene()
self._init_pixmap_item()
self._init_text_box()
self._init_layout()
def _init_view(self):
self.scale_factor = 6
self.view = QtWidgets.QGraphicsView()
self._turn_off_scrollbars()
self._enable_anti_aliasing()
self._set_transform_anchor()
self._install_event_filter()
def _turn_off_scrollbars(self):
self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
def _enable_anti_aliasing(self):
self.view.setRenderHint(QtGui.QPainter.Antialiasing)
self.view.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
def _set_transform_anchor(self):
self.view.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
def _install_event_filter(self):
self.view.viewport().installEventFilter(self)
def _init_scene(self):
self.scene = QtWidgets.QGraphicsScene(self.view)
self.view.setScene(self.scene)
#red background for debugging
self.scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor("red")))
def _init_pixmap_item(self):
self.pixmap_item = QtWidgets.QGraphicsPixmapItem()
self.scene.addItem(self.pixmap_item)
def _init_text_box(self):
self.font_size = 12
self.font_name = QtGui.QFont("Helvetica", self.font_size)
self.font_color = QtGui.QColor("blue")
self.v_pos = 10
self.h_pos = 10
self.text_box = QtWidgets.QGraphicsTextItem("Hello World")
self.text_box.setDefaultTextColor(self.font_color)
self.text_box.setFont(self.font_name)
self.text_box.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
self.text_box.setTransform(QtGui.QTransform.fromScale(2.25, 2.25), True)
self.scene.addItem(self.text_box)
def _init_layout(self):
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.view)
self.setLayout(layout)
self.setGeometry(100, 100, 1400, 400)
self.setMinimumSize(800, 400)
def load_pdf(self, path):
pdf_doc = fitz.open(path)
page = pdf_doc[0]
self._transform_pdf_page(page)
self._position_text_box()
self._update_view()
def _transform_pdf_page(self, page):
matrix = fitz.Matrix(self.scale_factor, self.scale_factor)
pixmap = page.get_pixmap(matrix=matrix, alpha=False)
image = QtGui.QImage(
pixmap.samples, pixmap.width, pixmap.height, QtGui.QImage.Format_RGB888)
self._fit_image(image)
def _fit_image(self, image):
self.pixmap_item.setPixmap(QtGui.QPixmap.fromImage(image))
self.scene.setSceneRect(self.pixmap_item.boundingRect())
self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)
def _position_text_box(self):
# To Do: Test on multiple pdfs
# Maybe add an offset, so that the scale factor is the same everywhere
self.text_box.setPos(
self.view.sceneRect().width() - self.text_box.boundingRect().width() * 2.3 - self.h_pos * 2.3,
self.text_box.boundingRect().height() * 1.7 - self.h_pos * 2.3)
def resizeEvent(self, event):
self._update_view()
return super().resizeEvent(event)
def _update_view(self):
self.view.resetTransform()
aspect_ratio = self.scene.sceneRect().height() / self.scene.sceneRect().width()
view_width = self.view.viewport().width()
view_height = aspect_ratio * view_width
self.view.setTransform(
QtGui.QTransform().scale(
view_width / self.view.sceneRect().width(),
view_height / self.view.sceneRect().height()))
self.view.ensureVisible(0, 0, 0, 0)
self.view.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = GraphicsView()
window.load_pdf(
"C:/test.pdf"
)
window.show()
sys.exit(app.exec_())
To solve my problem, I had to make a change to the _update_view method. The method worked as expected when the user manually resized the window. However, it did not produce the desired outcome of scaling the QGraphicsView to the width of the window, right after opening the window and running the load_pdf method.
The line view_width = self.view.viewport().width() in the _update_view method returned a viewport width of 100 instead of (approximately) 1400, which is the initial width of the window.
Prepending the _update_view method with self.view.viewport().resize(self.width(), self.height()) instead of self.view.resetTransform() solves my problem, because it will always first resize the view to the width and height of the GraphicsView class widget. I also had to multiply the viewport width by 0.97, because the PDF was still being displayed a bit larger than the view.
def _update_view(self):
self.view.viewport().resize(self.width(), self.height())
aspect_ratio = self.scene.sceneRect().height() / self.scene.sceneRect().width()
view_width = self.view.viewport().width() * 0.97
view_height = aspect_ratio * view_width
self.view.setTransform(
QtGui.QTransform().scale(
view_width / self.view.sceneRect().width(),
view_height / self.view.sceneRect().height()))
self.view.ensureVisible(0, 0, 0, 0)
self.view.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)
I'm developing a "cell" matrix using python 3.6 and tkinter, I've modified a scrolled frame class (code below) because scrollbars let me scroll though there isn't space left in the scrollbar. The result is an undesired space (see images below) . I used the class AutoScrollbar to gray out the scrollbar when it's not needed and worked fine under Windows, but under Linux still let me scroll further than it should and doesn't gray out.
is there any way to prevent tkinter scrollbars to allow scroll further then they should?
Under Windows:
Under Linux:
import tkinter as tk
from tkinter import ttk
class AutoScrollbar(tk.Scrollbar):
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
tk.Scrollbar.set(self, 0.0, 1.0)
else:
tk.Scrollbar.set(self, lo, hi)
class ScrolledFrame(tk.Frame):
def __init__(self, parent, *args, **kw):
tk.Frame.__init__(self, parent, *args, **kw)
vscrollbar = AutoScrollbar(self, orient=tk.VERTICAL)
vscrollbar.pack(fill=tk.Y, side=tk.RIGHT, expand=False)
hscrollbar = AutoScrollbar(self, orient=tk.HORIZONTAL)
hscrollbar.pack(fill=tk.X, side=tk.BOTTOM, expand=False)
canvas = tk.Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set,
xscrollcommand=hscrollbar.set,
bg ='gray')
canvas.pack(fill=tk.BOTH, expand=True)
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = tk.Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=tk.NW)
# detect changes on interior size
def _configure_interior(event):
# adjust size of scroll region
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
#print('size interior = canvas: '+str(size))
canvas.config(scrollregion="0 0 %s %s" % size)
interior.bind('<Configure>', _configure_interior)
Attached is part of a GUI i have constructed in Tkinter using a canvas, hence makes it possible to insert an image in the background.
When I call the function Relay_1: the result is sent to the Python shell window.
What i would like is a text box in the canvas, and show the result (i.e print the result) on the canvas and not in the shell.
Any ideas would be appreciated.
import Tkinter
#Function
def Relay_1():
arduinoData.write(b'1')
print ("This is a test\n")
class ProgramGUI:
def __init__(self):
# Creates the Main window for the program
self.main = tkinter.Tk()
# Change Default ICON
self.main.iconbitmap(self,default = 'test.ico')
# Create title, window size and make it a non re sizable window
self.main.title('Test')
self.main.resizable(False, False)
self.main.geometry('680x300')
self.canvas = tkinter.Canvas(self.main, width = 680, height = 300)
self.canvas.pack()
self.logo = tkinter.PhotoImage(file = 'test.png')
self.canvas.create_image(0,0,image = self.logo, anchor = 'nw')
# Create 3 Frame to group the widgets
self.top = tkinter.Frame(self.main)
self.middle = tkinter.Frame(self.main)
self.bottom = tkinter.Frame(self.main)
etc etc
The tkinter canvas widget has a very simple and easy to use method to draw text called create_text(). You can use it this way,
self.canvas.create_text(10, 10, text='This is a test\n')
The text can be customized by passing a wide range of arguments including font,fill and justify. Check the full list of passable arguments here:http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_text-method
To add the text when the code is executed, you could create a class inside ProgramGUI() method:
def draw_text(self, text):
self.canvas.create_text(10, 10, text=text)
And use it after creating an object.
I'm having problem with adjusting the width of the label to reflect current width of the window. When the window size changes I'd like label to fill the rest of the width that is left after other widgets in row consume width they need.
Putting the label in a Frame and using grid_propagate(False) does not seem to work.
Consider following code:
import tkinter as tk
import tkinter.ttk as ttk
class PixelLabel(ttk.Frame):
def __init__(self,master, w, h=20, *args, **kwargs):
'''
creates label inside frame,
then frame is set NOT to adjust to child(label) size
and the label keeps extending inside frame to fill it all,
whatever long text inside it is
'''
ttk.Frame.__init__(self, master, width=w, height=h,borderwidth=1)
#self.config(highlightbackground="blue")
self.grid_propagate(False) # don't shrink
self.label = ttk.Label(*args, **kwargs)
self.label.grid(sticky='nswe')
def resize(self,parent,*other_lenghts):
'''
resizes label to take rest of the width from parent
that other childs are not using
'''
parent.update()
new_width = parent.winfo_width()
print(new_width)
for lenght in other_lenghts:
new_width -= lenght
print(new_width)
self.configure(width = new_width)
root = tk.Tk()
master = ttk.Frame(root)
master.grid()
label = ttk.Label(master,text='aaa',borderwidth=1, relief='sunken')
label.grid(row=0,column=0)
label1_width = 7
label1 = ttk.Label(master,text='bbbb',borderwidth=1, relief='sunken',width=label1_width)
label1.grid(row=0,column=1)
label2 = ttk.Label(master,text='ccccccccccccccccccccccccccccccccccccc',borderwidth=1, relief='sunken')
label2.grid(row=0,column=2)
label3_width = 9
label2 = ttk.Label(master,text='ddddd',borderwidth=1, relief='sunken',width=label2_width)
label2.grid(row=0,column=3)
label4 = ttk.Label(master,text='ee',borderwidth=1, relief='sunken')
label4.grid(row=1,column=0)
label5 = ttk.Label(master,text='f',borderwidth=1, relief='sunken')
label5.grid(row=1,column=1,sticky='we')
nest_frame = ttk.Frame(master)
nest_frame.grid(row=2,columnspan=4)
label8_width = 9
label8 = ttk.Label(nest_frame,text='xxxxx',borderwidth=1, relief='sunken',width=label8_width)
label8.grid(row=0,column=0)
label9 = PixelLabel(nest_frame, 5, text='should be next to xxxxx but is not?',borderwidth=1, relief='sunken')
label9.grid(row=0,column=1)
label9.resize(root,label2_width)
root.mainloop()
Why label9 does not appear next to label8
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
It's not clear why you are using a label in a frame. I suspect this is an XY problem. You can get labels to consume extra space without resorting to putting labels inside frames. However, since you posted some very specific code with very specific questions, that's what I'll address.
Why label9 does not appear next to label8
Because you are creating the label as a child of the root window rather than a child of the frame. You need to create the label as a child of self inside PixelLabel:
class PixelLabel(...):
def __init__(...):
...
self.label = ttk.Label(self, ...)
...
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
There are a couple more problems. First, you need to give column zero of the frame inside PixelFrame a non-zero weight so that it uses all available space (or, switch to pack).
class PixelLabel(...):
def __init__(...):
...
self.grid_columnconfigure(0, weight=1)
...
Second, you need to use the sticky attribute when placing nest_frame in the window so that it expands to fill its space:
nest_frame.grid(..., sticky="ew")