I am currently trying to implement a DHT22 temperature and humidity sensor with Tkinter GUI using python on a Raspberry Pi 4. I've tried simply adding the code for the DHT22 to the GUI code, however this will cause the GUI to lag whenever the sensor refreshes every 2 seconds. Hence, I've resorted to multithreading to allow both the sensor and GUI to run simultaneously.
Here is what I have come up with so far:
import RPi.GPIO as GPIO
import spidev
import time
import tkinter
from tkinter import *
import tkinter.font as tkFont
from PIL import ImageTk, Image
import smbus
import serial
import os
import sys
import argparse
import Adafruit_DHT
import threading
SENSOR = Adafruit_DHT.DHT22
# GPIO4 on the Raspberry Pi
SENSOR_PIN = 4
address = 0b01
ssPin = 8
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = 976000
spi.mode = 0b11
#spi.bit_order = msbfirst
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(ssPin,GPIO.OUT)
GPIO.setup(4,GPIO.OUT)
GPIO.setup(22,GPIO.OUT)
GPIO.setup(26,GPIO.OUT)
GPIO.output(ssPin,GPIO.LOW)
GPIO.output(4,GPIO.HIGH)
GPIO.output(22,GPIO.HIGH)
GPIO.output(26,GPIO.HIGH)
print(Adafruit_DHT.read_retry(SENSOR, SENSOR_PIN))
global powerBool
global directionBool
def GUI():
class App:
def __init__(self, master):
def SendScaleReading(self):
S = scale.get() #retrieve value from slider
if S>0:
S+=155
print(S)
spi.xfer([address ,S]) #write data to slave address
frame = Frame(master)
frame.pack()
scale = tkinter.Scale(root,from_=0,to=100, width=69, length=700,bg='black',fg='#FFFFFF', highlightthickness = 0, bd = 0, command=SendScaleReading) #set parameters of slider
scale.place(relx=0.75, rely=0.05)
#scale.place(relx = 0, rely = 0) # set position of slider
fontstyle = tkFont.Font(family='Arial', size=50) #initialise font
scale['font'] = fontstyle
def PowerFn():
global powerBool
# print(ledBool)
#powerBool = True # boolean for led button
if powerBool:
#print('went to on button')
powerBtn.config(image=On_BtnImg)
powerBtn.image = On_BtnImg
print("on button configured")
powerLabel.config(text="POWER: ON", fg='#00FF00')
GPIO.output(26,GPIO.LOW)
else:
#print('went to off button')
powerBtn.config(image=Off_BtnImg)
powerBtn.image = Off_BtnImg
print("off button configured")
powerLabel.config(text="POWER: OFF", fg='#FF0000')
GPIO.output(26,GPIO.HIGH)
powerBool = not powerBool
def DirectionFn():
global directionBool
# print(cbBool)
if directionBool:
#print('went to CbOn button')
directionBtn.config(image = On_BtnImg)
directionBtn.image = On_BtnImg
print("CbOn button configured")
directionLabel.config(text="FORWARD", fg='#00FF00')
GPIO.output(22,GPIO.HIGH)
else:
#print('went to CbOff button')
directionBtn.config(image = Off_BtnImg)
directionBtn.image = Off_BtnImg
print("CbOff button configured")
directionLabel.config(text="REVERSE", fg='#FF0000')
GPIO.output(22,GPIO.LOW)
directionBool = not directionBool
root = Tk()
app = App(root)
root.config(bg='black')
#root.attributes('-zoomed', True)
#root.state('fullscreen')
rootWidth = root.winfo_screenwidth()
rootHeight = root.winfo_screenheight()
root.attributes('-zoomed', True)
# Create mini window
#canvas = Canvas(root, bg='black', highlightbackground='white')
#canvas.place(relx=0.1, rely=0.03, relheight=0.51, relwidth=0.505)
temperature = StringVar()
temperature.set("----"+" *C")
humidity = StringVar()
humidity.set("----"+" %")
On_img = Image.open("/home/pi/Downloads/on.png")
Off_img = Image.open("/home/pi/Downloads/off.png")
# Resize the image using resize() method according to the screen height and width
btnWidth = int(rootWidth / 6.4)
print(btnWidth)
infobtnWidth = int(rootHeight / 10)
print(infobtnWidth)
On_resize_img = On_img.resize((btnWidth, btnWidth))
Off_resize_img = Off_img.resize((btnWidth, btnWidth))
On_BtnImg = ImageTk.PhotoImage(On_resize_img)
Off_BtnImg = ImageTk.PhotoImage(Off_resize_img)
normalWidth = 1920 # Width of monitor screen used to write this code
normalHeight = 1080 # Height of monitor screen used to write this code
percentWidth = rootWidth / (normalWidth / 100)
percentHeight = rootHeight / (normalHeight / 100)
scale = ((percentWidth + percentHeight) / 2) / 100
fontsize = int(14 * scale)
fontsize = 50
fontstyle = tkFont.Font(family='Arial', size=fontsize)
titleFontsize = int(50 * scale)
if titleFontsize < 8:
titleFontsize = 8
TitleFontstyle = tkFont.Font(family="Gothic", size=titleFontsize)
## Labels ##
titleLabel = Label(root, text="MAX5487 DigiPot", font=TitleFontstyle, fg="red", bg="black")
titleLabel.place(relx=0.35, rely=0.05)
powerLabel = Label(root, text="POWER: OFF", font=fontstyle, fg='red', bg='black')
powerLabel.place(relx=0.2, rely=0.65, anchor=N)
directionLabel = Label(root, text="FORWARD", font=fontstyle, fg='#00FF00', bg='black')
directionLabel.place(relx=0.5, rely=0.65 , anchor=N)
powerBool = True # boolean for led button
powerBtn = Button(root, image=Off_BtnImg, bg='black', bd=0, activebackground='black', highlightthickness = 0, command=PowerFn)
powerBtn.place(relx=0.2, rely=0.35, anchor=N)
directionBool = False
directionBtn = Button(root, image=On_BtnImg, bg='black', bd=0, activebackground='black', highlightthickness = 0, command=DirectionFn)
directionBtn.place(relx=0.5, rely=0.35, anchor=N)
# Button for closing
exit_button = Button(root, text="Exit", font=fontstyle, fg='white', bg='red', highlightthickness = 0, command=root.destroy)
exit_button.place(relx=0.5, rely=0.9, anchor=N)
root.mainloop()
def tempsensor():
root = Tk()
app = App(root)
root.config(bg='black')
temperature = StringVar()
temperature.set("----"+" *C")
humidity = StringVar()
humidity.set("----"+" %")
normalWidth = 1920 # Width of monitor screen used to write this code
normalHeight = 1080 # Height of monitor screen used to write this code
rootWidth = root.winfo_screenwidth()
rootHeight = root.winfo_screenheight()
percentWidth = rootWidth / (normalWidth / 100)
percentHeight = rootHeight / (normalHeight / 100)
scale = ((percentWidth + percentHeight) / 2) / 100
fontsize = int(14 * scale)
fontsize = 50
fontstyle = tkFont.Font(family='Arial', size=fontsize)
root.after(2000, tempsensor)
h, t = Adafruit_DHT.read_retry(SENSOR, SENSOR_PIN)
temp = "%.1f" %t
temperature.set(temp+" *C")
hum = "%.1f" %h
humidity.set(hum+" %")
print(temperature)
print(humidity)
templabel = Label(root, textvariable=temperature, font=fontstyle, fg='white', bg='red', highlightthickness = 0)
templabel.place(relx=0.2, rely=0.8, anchor=N)
humidlabel = Label(root, textvariable=humidity, font=fontstyle, fg='white', bg='red', highlightthickness = 0)
humidlabel.place(relx=0.5, rely=0.8, anchor=N)
if __name__ == "__main__":
# print ID of current process
print("ID of process running main program: {}".format(os.getpid()))
# print name of main thread
print("Main thread name: {}".format(threading.current_thread().name))
# creating threads
t1 = threading.Thread(target=GUI, name='t1')
t2 = threading.Thread(target=tempsensor, name='t2')
# starting threads
t1.start()
t2.start()
# wait until all threads finish
t1.join()
t2.join()
However, when I tried to run the code, it would just tell me that powerBool and directionBool are not defined. The temperature and humidity values also wouldn't show up on screen and when I tried to print the variables it would just give me 'PyO3' and other similar values. Why are these things happening and how can I fix them? Any help is greatly appreciated.
I created a graphical user interface that has 3 checkboxes by using the Checkbox class given in tkinter package. I want to retrieve the screen coordinate of each checkbox. I have been trying to use pyautogui.position() to retrieve the coordianates. However, those values seem wrong because when I used those coordinates for the pyautogui engine to click on the box, it did not click. Also it seems that the window contained the checkboxes does not let me to check the boxes, when I was debugging it. What would be other possible ways that I can fix these problems?
from tkinter import *
import SpeechToDictation as Std
import pyautogui
import time
def speech_to_dictation():
speech_inst = Std.SpeechToDictation()
dictation.append(speech_inst.read_audio())
def user_speech_to_dictation():
main_window = Tk()
button = Button(text='Recording')
button.pack()
# have a while true at here so that when the function executes, it quits out of the loop
button.config(command=speech_to_dictation)
main_window.mainloop()
class Test(Frame):
def __init__(self, parent=None, picks=[]):
Frame.__init__(self, parent)
self.vars = []
self.checkboxes = []
Label(self, text='Lab Works').grid(row=0, padx=10, pady=10)
i = 1
for pick in picks:
var = IntVar()
chk = Checkbutton(self, text=pick, variable=var)
chk.grid(row=i, pady=4, padx=10)
self.vars.append(var)
self.checkboxes.append(chk)
i += 1
def state(self):
return map((lambda var: var.get()), self.vars)
def full_screen(window):
width = window.winfo_screenwidth()
height = window.winfo_screenheight()
window.geometry("%dx%d" % (width, height))
def allstates():
print(list(lng.state()))
def make_test(window):
full_screen(window=root)
window.grid(row=1, column=0)
Button(root, text='Quit', command=root.quit).grid(row=10, padx=10, pady=10)
Button(root, text='Peek', command=allstates).grid(row=12, padx=10, pady=10)
if __name__ == '__main__':
# store dictation at dictation[]
dictation = []
user_speech_to_dictation()
is_string_complete = dictation[0]['text'][:8]
if is_string_complete == 'complete':
start_time = time.time() # returns number of seconds passed since epoch
max_loop_time = 1 # 1 seconds
while True:
if (time.time() - start_time) >= max_loop_time:
root = Tk()
lng = Test(root, ['Blood Count', 'Lipid Panel', 'Hemoglobin A1C'])
make_test(window=lng)
root.state('zoomed')
root.update()
x_position, y_position = pyautogui.position()
print(x_position, y_position)
max_loop_time = time.time() - start_time + max_loop_time
# Coordinate of each boxes: they seem wrong
locations = [(53, 158), (84, 228), (36, 302)]
blood_count_string = dictation[0]['text'][9:]
if blood_count_string == 'blood count':
x_coordinate = locations[0][0]
y_coordinate = locations[0][1]
pyautogui.click(x_coordinate, y_coordinate)
allstates()
root.destroy()
# quit the program after the window is destroyed
if max_loop_time > 2:
break
Here is a quick example of how to get the coordinates and click the checkbutton (simply use .winfo_rootx() and .winfo_rooty()):
import tkinter as tk
import pyautogui as pag
def click_btn():
x = check_btn.winfo_rootx()
y = check_btn.winfo_rooty()
pag.click(x, y)
root = tk.Tk()
check_btn = tk.Checkbutton(root, text='This will be clicked in 3 seconds')
check_btn.pack()
root.after(3 * 1000, click_btn)
root.mainloop()
import tkinter as tk
from tkinter import *
import pyautogui
root = tk.Tk()
#label = tk.Label(root, text='you coomand hacking', font= ('Times New Roman','80'), fg='black', bg='white')
#label.pack()
root.title("Wallpaper")
wall = PhotoImage(file = "20170227180026_3.gif")
wall_label = Label(image = wall)
root.geometry(f"{wall.width()}x{wall.height()}")
root.overrideredirect(True)
currentMouseX,currentMouseY = pyautogui.position()
print(f'{currentMouseX} {currentMouseY}')
root.geometry("+0+0")
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
wall_label.place(x = -2,y = -2)
root.after(1000 , root.destroy)
#root.geometry("+{}+{}".format(currentMouseX,currentMouseY))
root.geometry("+{}+{}".format(currentMouseX,currentMouseY))
root.mainloop()
root.after(1000 , root.destroy)
root.geometry("+{}+{}".format(currentMouseX,currentMouseY))
Please understand even if the source code is dirty.
What I want is for the image to follow the mouse
I tried to follow the mouse by repeating the image disappearing and appearing, but it is not easy.
Here. I made the image follow the mouse, without pyautogui.
Code:
import tkinter as tk
from tkinter import *
#import pyautogui
root = tk.Tk()
#label = tk.Label(root, text='you coomand hacking', font= ('Times New Roman','80'), fg='black', bg='white')
#label.pack()
root.title("Wallpaper")
wall = PhotoImage(file = "20170227180026_3.gif")
c = Canvas(root, height = 1200, width = 1200)
c.pack()
pic = c.create_image(600, 600, image = wall)
def movepic(event):
x,y = event.x, event.y
c.coords(pic, x, y)
c.bind_all("<Motion>", movepic)
root.mainloop()
I'm using Tkinter as GUI for my program, but as I see, many programs don't have standard look as Tkinter does. By standard look I mean standard title bar, borders, etc.
For example, Tkinter's title bar:
vs GitHub's title bar:
See how they have their own custom exit, resize and minimize buttons? Is it possible to achieve that look using Tkinter?
Thanks in advance! :)
Yes it's possible. You can use the overrideredirect() method on the root window to kill the title bar and the default geometry settings. After that, you need to rebuild all those methods from scratch to set it back up like you want. Here's a small working example with minimal functionality:
root = Tk()
def move_window(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry
# make a frame for the title bar
title_bar = Frame(root, bg='white', relief='raised', bd=2)
# put a close button on the title bar
close_button = Button(title_bar, text='X', command=root.destroy)
# a canvas for the main area of the window
window = Canvas(root, bg='black')
# pack the widgets
title_bar.pack(expand=1, fill=X)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)
root.mainloop()
Most will know there is an error when using the 'move_window' method used above; I found a fix that gets the exact position of the mouse and moves with that rather than from the corner:
def get_pos(event):
xwin = app.winfo_x()
ywin = app.winfo_y()
startx = event.x_root
starty = event.y_root
ywin = ywin - starty
xwin = xwin - startx
def move_window(event):
app.geometry("400x400" + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))
startx = event.x_root
starty = event.y_root
app.TopFrame.bind('<B1-Motion>', move_window)
app.TopFrame.bind('<Button-1>', get_pos)
I found a way of making the title bar black using ctypes: (win11 only)
Tkinter dark title bar example:
import ctypes as ct
def dark_title_bar(window):
"""
MORE INFO:
https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
"""
window.update()
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
set_window_attribute = ct.windll.dwmapi.DwmSetWindowAttribute
get_parent = ct.windll.user32.GetParent
hwnd = get_parent(window.winfo_id())
rendering_policy = DWMWA_USE_IMMERSIVE_DARK_MODE
value = 2
value = ct.c_int(value)
set_window_attribute(hwnd, rendering_policy, ct.byref(value),
ct.sizeof(value))
I searched almost a year for a solution!
These are the modifications that I have made using python 3.7.2
from tkinter import *
root = Tk()
root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry
# make a frame for the title bar
title_bar = Frame(root, bg='#2e2e2e', relief='raised', bd=2,highlightthickness=0)
# put a close button on the title bar
close_button = Button(title_bar, text='X', command= root.destroy,bg = "#2e2e2e",padx = 2,pady = 2,activebackground='red',bd = 0,font="bold",fg='white',highlightthickness=0)
# a canvas for the main area of the window
window = Canvas(root, bg='#2e2e2e',highlightthickness=0)
# pack the widgets
title_bar.pack(expand=1, fill=X)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
xwin=None
ywin=None
# bind title bar motion to the move window function
def move_window(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
def change_on_hovering(event):
global close_button
close_button['bg']='red'
def return_to_normalstate(event):
global close_button
close_button['bg']='#2e2e2e'
title_bar.bind('<B1-Motion>', move_window)
close_button.bind('<Enter>',change_on_hovering)
close_button.bind('<Leave>',return_to_normalstate)
root.mainloop()
Explanation:
We use bd(border thickness)=0 to remove the borders from the the button
Then we bind the <Enter> event to a function which
changes the foreground color.
And to return to its original state we bind the <Leave> event to another function
Initial State
Change in state after hovering mouse cursor over it
Note : The cursor is not visible because my screen capture software somehow removed it
In python3.5.2 I had to make some modifications to get this to work:
#custom title bar for tkinter
from tkinter import Tk, Frame, Button, Canvas
root = Tk()
def move_window(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x100+200+200') # set new geometry
# make a frame for the title bar
title_bar = Frame(root, bg='white', relief='raised', bd=2)
# put a close button on the title bar
close_button = Button(title_bar, text='Close this Window', command=root.destroy)
# a canvas for the main area of the window
window = Canvas(root, bg='black')
# pack the widgets
title_bar.pack(expand=1, fill="x")
close_button.pack(side="right")
window.pack(expand=1, fill="both")
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)
root.mainloop()
enter image description herehere you go updated to python 3.8
and new variable for title bar background and for
main content background and title name add
and new background + clear some indention errors
from tkinter import *
root = Tk()
# turns off title bar, geometry
root.overrideredirect(True)
# set new geometry
root.geometry('400x100+200+200')
# set background color of title bar
back_ground = "#2c2c2c"
# set background of window
content_color = "#ffffff"
# make a frame for the title bar
title_bar = Frame(root, bg=back_ground, relief='raised', bd=1, highlightcolor=back_ground,highlightthickness=0)
# put a close button on the title bar
close_button = Button(title_bar, text='x', command=root.destroy,bg=back_ground, padx=5, pady=2, activebackground="red", bd=0, font="bold", fg='white', activeforeground="white", highlightthickness=0)
# window title
title_window = "Title Name"
title_name = Label(title_bar, text=title_window, bg=back_ground, fg="white")
# a canvas for the main area of the window
window = Canvas(root, bg="white", highlightthickness=0)
# pack the widgets
title_bar.pack(expand=1, fill=X)
title_name.pack(side=LEFT)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
x_axis = None
y_axis = None
# bind title bar motion to the move window function
def move_window(event):
root.geometry('+{0}+{1}'.format(event.x_root, event.y_root))
def change_on_hovering(event):
global close_button
close_button['bg'] = 'red'
def return_to_normal_state(event):
global close_button
close_button['bg'] = back_ground
title_bar.bind('<B1-Motion>', move_window)
close_button.bind('<Enter>', change_on_hovering)
close_button.bind('<Leave>', return_to_normal_state)
root.mainloop()
This is how it looksI think this is the easy one. I used it on one of my project , due to some reasons from client side, client was not able to make any payment for that project and a lot of time got wasted.
from cProfile import label
from tkinter import *
import ttkbootstrap as ttk
from ttkbootstrap import Style
from tkinter import messagebox as m_box
win = Tk()
win.overrideredirect(True)
win.attributes("-topmost", True)
win.geometry("600x300+300+300")
win.resizable(1, 1)
style = Style("cyborg")
# ============================================================= Title bar Header
def ext():
exit()
def minim():
win.overrideredirect(0)
win.wm_state("iconic")
win.overrideredirect(1)
def about():
pass
m_box.showinfo("About", "Developer: Vivek phogat\nemail: Chaudharyvivekphogat#outlook.com")
#------------------------------- Header section
Header_frame = ttk.Label(win)
info_label = ttk.Label(Header_frame,text="Customized title bar enjoy :-)",bootstyle="WARNING",font=("Comic Sans MS", 15))
info_label.pack(padx=5, pady=5,side = LEFT)
win_close_btn1 = ttk.Button(Header_frame, text=" X ", command=(win.destroy), bootstyle="danger")
win_close_btn1.pack( side = RIGHT,anchor= NE)
Min_but = ttk.Button(Header_frame, text=" _ ", command=minim, bootstyle="light")
Min_but.pack( side = RIGHT,anchor= NE)
about_btn1 = ttk.Button(Header_frame, text=" a ", command=about, bootstyle="secondary")
about_btn1.pack(side = RIGHT,anchor= NE)
Header_frame.pack(fill=X)
#-------------------------------
#title bar get position.
def get_pos(event):
global xwin
global ywin
xwin = event.x
ywin = event.y
#title bar drag functon.
def drag(event):
win.geometry(f"+{event.x_root - xwin}+{event.y_root - ywin}")
Header_frame.bind("<B1-Motion>", drag)
Header_frame.bind("<Button-1>", get_pos)
info_label.bind("<B1-Motion>", drag)
info_label.bind("<Button-1>", get_pos)
Footer_frame = ttk.Frame(win)
label = ttk.Label(Footer_frame, text= " Hope you all are doing good enjoy the code.",bootstyle="WARNING",font=("Comic Sans MS", 20)).pack()
Footer_frame.pack(expand= TRUE )
win.mainloop()
ok i left the old answer if some need it.
it is a recreated from scratch custom taskbar that assemble windows 10 bar
from tkinter import *
window = Tk()
bg = "#f5f6f7"
title_window = "test app"
class app:
def __init__(self, main):
self.main = main
self.main.configure(bg=bg)
self.main.overrideredirect(True)
self.main.geometry('230x130')
self.main.resizable(width=False, height=False)
self.top_bar = Frame(main,bg=bg, cursor="sizing")
self.top_bar.pack(fill=X)
self.title_txt = Label(self.top_bar, text=title_window ,bg=bg)
self.title_txt.pack(side="left", padx=3)
close_btn = Button(self.top_bar,text="x", cursor="arrow", bg=bg, fg="black", highlightthickness=0,activebackground="red", activeforeground="white",bd=0, command=self.main.quit)
close_btn.pack(side="right")
bottom_bar = Frame(main, bg=bg)
bottom_bar.pack()
label_scr = Label(bottom_bar, text="label 1", padx=100, pady=5, bg=bg)
label_scr.grid(row=0, column=0, columnspan=3)
button_scr = Button(bottom_bar, text="Button1", bg=bg, bd=0)
button_scr.grid(row=2, column=0, columnspan=3, pady=3)
button2_scr = Button(bottom_bar, text="Button2", bg=bg,bd=0)
button2_scr.grid(row=3, column=0, columnspan=3, pady=3)
def move_window(event):
window.geometry(f"+{event.x_root}+{event.y_root}")
execution = app(window)
execution.top_bar.bind('<B1-Motion>', move_window)
execution.title_txt.bind('<B1-Motion>', move_window)
window.mainloop()
This is for me the best form:
import tkinter as tk
from turtle import title, width
root= tk.Tk()
root.title('Civ IV select songs')
canvas1 = tk.Canvas(root, width = 300, height = 600)
canvas1.pack()
here is my one
from tkinter import *
import windll
class CustomTitle():
"""
Ex:
root = Tk()
titleBar = CustomTitle(root,title_text = 'Hello,World!' , bg = "#000000" , fg = '#ffffff')
titleBar.resizeable = True
titleBar.packBar()
root.mainloop()
Note:
Try to Give Color value in Hex and the 3rd car should be number
#7a4e7a
↑ (this one)
"""
resizeable = True
font_style = ('Candara',13)
def __init__(self,win,title_text='Custom Title Bar',bg='#ffffff',fg="#000000"):
# deactivating main title bar
self._win = win
win.title(title_text)
# props
self.bg = bg
self._maximized = False
self._win_width = win.winfo_width()
self._win_height = win.winfo_height()
self._scr_width = win.winfo_screenwidth()
self._scr_height = win.winfo_screenheight()
self._addWidget(title_text,bg,fg)
def packBar(self):
self._title_bar.pack(fill=X)
self._checkAbility()
self._win.overrideredirect(1)
self._finilize()
def _checkAbility(self):
if not self.resizeable:
self._maximize_btn.config(state=DISABLED)
else:
self._resizey_widget.pack(side=BOTTOM,ipadx=.1,fill=X)
self._resizex_widget.pack(side=RIGHT,ipadx=.1,fill=Y)
def _maximize_win(self):
if not self._maximized:
self._past_size = root.geometry()
self._win.geometry(f"{self._scr_width}x{self._scr_height}+{0}+{0}")
self._maximize_btn.config(text = '🗗')
else:
self._win.geometry(self._past_size)
self._maximize_btn.config(text = '🗖')
self._maximized = not self._maximized
def _minimize(self):
Minimize = win32gui.GetForegroundWindow()
win32gui.ShowWindow(Minimize, win32con.SW_MINIMIZE)
def _setIconToTaskBar(self,mainWindow):
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
# Magic
hwnd = windll.user32.GetParent(mainWindow.winfo_id())
stylew = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
stylew = stylew & ~WS_EX_TOOLWINDOW
stylew = stylew | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, stylew)
mainWindow.wm_withdraw()
mainWindow.after(10, mainWindow.wm_deiconify)
def _addWidget(self,title_text,bg,fg):
self._title_bar = Frame(self._win,bd=1,bg=bg)
self._title_text = Label(self._title_bar,text=title_text,bg=bg,fg=fg,font=self.font_style)
self._title_text.pack(side=LEFT,padx=4,pady=3)
self._title_text.bind("<B1-Motion>",self._drag)
self._close_btn = Button(self._title_bar,text = '×',bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._win.destroy)
self._close_btn.pack(side=RIGHT,fill=Y)
self._maximize_btn = Button(self._title_bar,text="🗖",bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._maximize_win)
self._maximize_btn.pack(side=RIGHT,fill=Y)
self._minimize_btn = Button(self._title_bar,text="_",bd=0,bg=bg,fg=fg,width=3,font=self.font_style,command=self._minimize)
self._minimize_btn.pack(side=RIGHT,fill=Y)
self._title_bar.bind('<Button-1>', self._drag)
self._resizex_widget = Frame(self._win,cursor='sb_h_double_arrow')
self._resizex_widget.bind("<B1-Motion>",self._resizex)
self._resizey_widget = Frame(self._win,cursor='sb_v_double_arrow')
self._resizey_widget.bind("<B1-Motion>",self._resizey)
self._hover_effect()
def _hover_effect(self):
try:
num = int(self.bg[3]) - 1
newbg = self.bg.replace(self.bg[3],str(num))
except:
newbg = "#c7ebe8"
def change_bg(which_one,bg = newbg):
which_one.config(bg=bg)
def restore_bg(which_one):
which_one.config(bg=self.bg)
self._maximize_btn.bind('<Enter>',lambda event: change_bg(self._maximize_btn))
self._maximize_btn.bind('<Leave>',lambda event: restore_bg(self._maximize_btn))
self._minimize_btn.bind('<Enter>',lambda event: change_bg(self._minimize_btn))
self._minimize_btn.bind('<Leave>',lambda event: restore_bg(self._minimize_btn))
self._close_btn.bind('<Enter>',lambda event: change_bg(self._close_btn,bg='#db2730'))
self._close_btn.bind('<Leave>',lambda event: restore_bg(self._close_btn))
def _finilize(self):
self._win.after(10, lambda: self._setIconToTaskBar(self._win))
def _drag(self,event):
xwin = root.winfo_x()
ywin = root.winfo_y()
startx = event.x_root
starty = event.y_root
ywin = ywin - starty
xwin = xwin - startx
def _move_window(event): # runs when window is dragged
root.geometry(f'+{event.x_root + xwin}+{event.y_root + ywin}')
def _release_window(event): # runs when window is released
root.config(cursor="arrow")
self._title_bar.bind('<B1-Motion>', _move_window)
self._title_bar.bind('<ButtonRelease-1>', _release_window)
self._title_text.bind('<B1-Motion>', _move_window)
self._title_text.bind('<ButtonRelease-1>', _release_window)
def _resizex(self,event):
xwin = root.winfo_x()
difference = (event.x_root - xwin) - root.winfo_width()
if root.winfo_width() > 150 : # 150 is the minimum width for the window
try:
root.geometry(f"{ root.winfo_width() + difference }x{ root.winfo_height() }")
except:
pass
else:
if difference > 0: # so the window can't be too small (150x150)
try:
root.geometry(f"{ root.winfo_width() + difference }x{ root.winfo_height() }")
except:
pass
def _resizey(self,event):
ywin = root.winfo_y()
difference = (event.y_root - ywin) - root.winfo_height()
if root.winfo_height() > 150: # 150 is the minimum height for the window
try:
root.geometry(f"{ root.winfo_width() }x{ root.winfo_height() + difference}")
except:
pass
else:
if difference > 0: # so the window can't be too small (150x150)
try:
root.geometry(f"{ root.winfo_width() }x{ root.winfo_height() + difference}")
except:
pass
We have to change the DWMWindowAttributes for the tkinter window with ctypes which is under windll.dwmapi.DwmSetWindowAttribute
For changing the title bar color, we can use DWMWA_CAPTION_COLOR which is set as 35 (attribute number) for windows 11.
Here is an example:
import tkinter
from ctypes import windll, byref, sizeof, c_int
root = tkinter.Tk()
root.title("Tkinter Window")
root.update()
HWND = windll.user32.GetParent(root.winfo_id()) # the window we want to change
# These attributes are for windows 11
DWMWA_CAPTION_COLOR = 35
DWMWA_TITLE_COLOR = 36
COLOR_1 = 0x0000FFFF # color should be in hex order: 0x00bbggrr
COLOR_2 = 0x00008000
windll.dwmapi.DwmSetWindowAttribute(HWND, DWMWA_CAPTION_COLOR, byref(c_int(COLOR_1)), sizeof(c_int))
windll.dwmapi.DwmSetWindowAttribute(HWND, DWMWA_TITLE_COLOR, byref(c_int(COLOR_2)), sizeof(c_int))
root.mainloop()
more info about the color api used here:
https://learn.microsoft.com/en-us/windows/win32/gdi/colorref