Styling Interface dengan ttk dan Custom Theme
Dalam pengembangan aplikasi desktop dengan Python, tampilan yang menarik dan modern sangat penting untuk memberikan pengalaman pengguna yang baik. Tkinter standar memiliki keterbatasan dalam hal styling, namun dengan ttk (Themed Tkinter) dan custom theme, kita dapat membuat aplikasi yang lebih profesional dan modern.
Bagian 1: Dasar-dasar ttk dan Styling
1.1 Perbandingan Widget Tkinter vs ttk
Praktikum 1.1: Membuat aplikasi perbandingan dasar
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkFont
class PerbandinganWidget:
def __init__(self):
self.root = tk.Tk()
self.root.title("Perbandingan Widget Tkinter vs ttk")
self.root.geometry("800x600")
self.root.configure(bg='#f0f0f0')
# Variabel untuk menyimpan data
self.nama_var = tk.StringVar()
self.email_var = tk.StringVar()
self.gender_var = tk.StringVar(value="Laki-laki")
self.hobi_vars = {
'membaca': tk.BooleanVar(),
'olahraga': tk.BooleanVar(),
'musik': tk.BooleanVar()
}
self._buat_interface()
Setelah class PerbandinganWidget dan method __init__, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Header
header_frame = tk.Frame(self.root, bg='#2c3e50', height=80)
header_frame.pack(fill='x', padx=10, pady=(10,5))
header_frame.pack_propagate(False)
title_label = tk.Label(header_frame, text="Perbandingan Widget Tkinter vs ttk",
font=('Arial', 16, 'bold'), fg='white', bg='#2c3e50')
title_label.pack(expand=True)
Setelah header frame, lanjutkan dengan frame untuk widget standar:
# Frame untuk widget Tkinter standar
tk_frame = tk.LabelFrame(self.root, text="Widget Tkinter Standar",
font=('Arial', 12, 'bold'), bg='#f0f0f0', fg='#2c3e50')
tk_frame.pack(side='left', fill='both', expand=True, padx=(10,5), pady=5)
# Input nama dengan Tkinter standar
tk.Label(tk_frame, text="Nama:", font=('Arial', 10), bg='#f0f0f0').pack(anchor='w', padx=10, pady=(10,0))
self.tk_entry = tk.Entry(tk_frame, textvariable=self.nama_var, font=('Arial', 10))
self.tk_entry.pack(fill='x', padx=10, pady=(0,10))
# Combobox dengan Tkinter standar (menggunakan OptionMenu)
tk.Label(tk_frame, text="Gender:", font=('Arial', 10), bg='#f0f0f0').pack(anchor='w', padx=10)
self.tk_option = tk.OptionMenu(tk_frame, self.gender_var, "Laki-laki", "Perempuan")
self.tk_option.pack(fill='x', padx=10, pady=(0,10))
# Checkbutton dengan Tkinter standar
tk.Label(tk_frame, text="Hobi:", font=('Arial', 10), bg='#f0f0f0').pack(anchor='w', padx=10)
for hobi, var in self.hobi_vars.items():
tk.Checkbutton(tk_frame, text=hobi.capitalize(), variable=var,
font=('Arial', 9), bg='#f0f0f0').pack(anchor='w', padx=20)
# Button dengan Tkinter standar
tk.Button(tk_frame, text="Submit (Tkinter)", command=self.submit_tk,
font=('Arial', 10), bg='#3498db', fg='white').pack(pady=20, padx=10)
Setelah frame widget standar, lanjutkan dengan frame untuk widget ttk:
# Frame untuk widget ttk
ttk_frame = tk.LabelFrame(self.root, text="Widget ttk (Themed)",
font=('Arial', 12, 'bold'), bg='#f0f0f0', fg='#2c3e50')
ttk_frame.pack(side='right', fill='both', expand=True, padx=(5,10), pady=5)
# Input nama dengan ttk
ttk.Label(ttk_frame, text="Nama:", font=('Arial', 10)).pack(anchor='w', padx=10, pady=(10,0))
self.ttk_entry = ttk.Entry(ttk_frame, textvariable=self.nama_var, font=('Arial', 10))
self.ttk_entry.pack(fill='x', padx=10, pady=(0,10))
# Combobox dengan ttk
ttk.Label(ttk_frame, text="Gender:", font=('Arial', 10)).pack(anchor='w', padx=10)
self.ttk_combo = ttk.Combobox(ttk_frame, textvariable=self.gender_var,
values=["Laki-laki", "Perempuan"], state="readonly")
self.ttk_combo.pack(fill='x', padx=10, pady=(0,10))
# Checkbutton dengan ttk
ttk.Label(ttk_frame, text="Hobi:", font=('Arial', 10)).pack(anchor='w', padx=10)
for hobi, var in self.hobi_vars.items():
ttk.Checkbutton(ttk_frame, text=hobi.capitalize(), variable=var).pack(anchor='w', padx=20)
# Button dengan ttk
ttk.Button(ttk_frame, text="Submit (ttk)", command=self.submit_ttk).pack(pady=20, padx=10)
Setelah frame ttk, lanjutkan dengan method untuk handle submit:
def submit_tk(self):
hobi_terpilih = [hobi for hobi, var in self.hobi_vars.items() if var.get()]
print(f"Tkinter Submit - Nama: {self.nama_var.get()}, Gender: {self.gender_var.get()}, Hobi: {hobi_terpilih}")
def submit_ttk(self):
hobi_terpilih = [hobi for hobi, var in self.hobi_vars.items() if var.get()]
print(f"ttk Submit - Nama: {self.nama_var.get()}, Gender: {self.gender_var.get()}, Hobi: {hobi_terpilih}")
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = PerbandinganWidget()
app.run()
Tugas: Jalankan aplikasi dan bandingkan tampilan widget Tkinter standar dengan ttk. Coba isi form di kedua sisi, lalu klik tombol Submit untuk melihat perbedaan visual dan fungsionalitas. Perhatikan perbedaan styling, responsivitas, dan konsistensi tampilan.
1.2 Implementasi Theme Switching
Praktikum 1.2: Membuat aplikasi dengan kemampuan mengganti theme
import tkinter as tk
from tkinter import ttk
class ThemeSwitcher:
def __init__(self):
self.root = tk.Tk()
self.root.title("Theme Switcher Demo")
self.root.geometry("600x400")
# Inisialisasi style
self.style = ttk.Style()
self.current_theme = tk.StringVar(value=self.style.theme_use())
self._buat_interface()
self._setup_themes()
Setelah method __init__, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Frame utama
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill='both', expand=True)
# Header
ttk.Label(main_frame, text="Theme Switcher Demo",
font=('Arial', 16, 'bold')).pack(pady=(0,20))
# Frame untuk theme selection
theme_frame = ttk.LabelFrame(main_frame, text="Pilih Theme", padding="10")
theme_frame.pack(fill='x', pady=(0,20))
# Combobox untuk memilih theme
ttk.Label(theme_frame, text="Theme:").pack(anchor='w')
self.theme_combo = ttk.Combobox(theme_frame, textvariable=self.current_theme,
values=self.style.theme_names(), state="readonly")
self.theme_combo.pack(fill='x', pady=(5,10))
self.theme_combo.bind('<<ComboboxSelected>>', self.change_theme)
Setelah theme selection frame, lanjutkan dengan widget demo:
# Frame untuk demo widgets
demo_frame = ttk.LabelFrame(main_frame, text="Demo Widgets", padding="10")
demo_frame.pack(fill='both', expand=True)
# Entry widget
ttk.Label(demo_frame, text="Entry Widget:").pack(anchor='w')
self.demo_entry = ttk.Entry(demo_frame)
self.demo_entry.pack(fill='x', pady=(5,10))
self.demo_entry.insert(0, "Contoh text...")
# Button widgets
button_frame = ttk.Frame(demo_frame)
button_frame.pack(fill='x', pady=(0,10))
ttk.Button(button_frame, text="Normal Button").pack(side='left', padx=(0,5))
ttk.Button(button_frame, text="Disabled Button", state='disabled').pack(side='left')
# Progressbar
ttk.Label(demo_frame, text="Progress Bar:").pack(anchor='w')
self.progress = ttk.Progressbar(demo_frame, mode='determinate', value=70)
self.progress.pack(fill='x', pady=(5,10))
# Scale
ttk.Label(demo_frame, text="Scale Widget:").pack(anchor='w')
self.scale = ttk.Scale(demo_frame, from_=0, to=100, orient='horizontal')
self.scale.pack(fill='x', pady=(5,10))
self.scale.set(50)
Setelah demo widgets, lanjutkan dengan method untuk setup themes dan change theme:
def _setup_themes(self):
# Konfigurasi tambahan untuk beberapa theme
available_themes = self.style.theme_names()
print(f"Available themes: {available_themes}")
def change_theme(self, event=None):
selected_theme = self.current_theme.get()
try:
self.style.theme_use(selected_theme)
print(f"Theme changed to: {selected_theme}")
except tk.TclError as e:
print(f"Error changing theme: {e}")
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = ThemeSwitcher()
app.run()
Tugas: Jalankan aplikasi dan coba ganti-ganti theme yang tersedia melalui combobox. Perhatikan bagaimana setiap theme mengubah tampilan widget secara otomatis. Coba interaksi dengan berbagai widget untuk melihat konsistensi theme di seluruh aplikasi.
Bagian 2: Styling Advanced dan Custom Theme
2.1 Custom Styling dengan ttk.Style
Praktikum 2.1: Membuat aplikasi dengan custom styling
import tkinter as tk
from tkinter import ttk
class CustomStyleApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("Custom Styling dengan ttk")
self.root.geometry("700x500")
self.root.configure(bg='#2c3e50')
# Inisialisasi style
self.style = ttk.Style()
self._setup_custom_styles()
self._buat_interface()
Setelah method __init__, lanjutkan dengan method untuk setup custom styles:
def _setup_custom_styles(self):
# Custom style untuk button
self.style.configure('Custom.TButton',
background='#3498db',
foreground='white',
borderwidth=0,
focuscolor='none',
padding=(20, 10))
self.style.map('Custom.TButton',
background=[('active', '#2980b9'),
('pressed', '#21618c')])
# Custom style untuk entry
self.style.configure('Custom.TEntry',
fieldbackground='#ecf0f1',
borderwidth=2,
insertcolor='#2c3e50',
padding=(10, 8))
self.style.map('Custom.TEntry',
focuscolor=[('focus', '#3498db')])
# Custom style untuk label
self.style.configure('Title.TLabel',
background='#2c3e50',
foreground='#ecf0f1',
font=('Arial', 18, 'bold'))
self.style.configure('Subtitle.TLabel',
background='#2c3e50',
foreground='#bdc3c7',
font=('Arial', 12))
Setelah setup custom styles, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Main container
main_frame = ttk.Frame(self.root)
main_frame.pack(fill='both', expand=True, padx=20, pady=20)
# Header section
header_frame = ttk.Frame(main_frame)
header_frame.pack(fill='x', pady=(0,30))
ttk.Label(header_frame, text="Custom Styling Demo",
style='Title.TLabel').pack()
ttk.Label(header_frame, text="Aplikasi dengan styling kustom menggunakan ttk.Style",
style='Subtitle.TLabel').pack(pady=(5,0))
# Form section
form_frame = ttk.LabelFrame(main_frame, text="Form Input", padding="20")
form_frame.pack(fill='x', pady=(0,20))
# Nama input
ttk.Label(form_frame, text="Nama Lengkap:").pack(anchor='w', pady=(0,5))
self.nama_entry = ttk.Entry(form_frame, style='Custom.TEntry', width=40)
self.nama_entry.pack(fill='x', pady=(0,15))
# Email input
ttk.Label(form_frame, text="Email:").pack(anchor='w', pady=(0,5))
self.email_entry = ttk.Entry(form_frame, style='Custom.TEntry', width=40)
self.email_entry.pack(fill='x', pady=(0,15))
Setelah form section, lanjutkan dengan button section dan method handlers:
# Button section
button_frame = ttk.Frame(form_frame)
button_frame.pack(fill='x', pady=(10,0))
ttk.Button(button_frame, text="Submit Data",
style='Custom.TButton', command=self.submit_data).pack(side='left', padx=(0,10))
ttk.Button(button_frame, text="Reset Form",
style='Custom.TButton', command=self.reset_form).pack(side='left')
# Result section
self.result_frame = ttk.LabelFrame(main_frame, text="Hasil Input", padding="20")
self.result_frame.pack(fill='both', expand=True)
self.result_text = tk.Text(self.result_frame, height=8, bg='#ecf0f1',
fg='#2c3e50', font=('Consolas', 10))
self.result_text.pack(fill='both', expand=True)
def submit_data(self):
nama = self.nama_entry.get()
email = self.email_entry.get()
if nama and email:
result = f"Data berhasil disubmit:\nNama: {nama}\nEmail: {email}\n" + "="*40 + "\n"
self.result_text.insert('end', result)
self.result_text.see('end')
else:
self.result_text.insert('end', "Error: Semua field harus diisi!\n" + "="*40 + "\n")
def reset_form(self):
self.nama_entry.delete(0, 'end')
self.email_entry.delete(0, 'end')
self.result_text.delete('1.0', 'end')
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = CustomStyleApp()
app.run()
Tugas: Jalankan aplikasi dan perhatikan perbedaan styling yang telah dikustomisasi. Coba isi form dengan data, klik Submit Data untuk melihat hasilnya, lalu gunakan Reset Form untuk membersihkan. Amati bagaimana custom styling memberikan tampilan yang lebih profesional.
2.2 Responsive Design dengan ttk
Praktikum 2.2: Membuat aplikasi yang responsif terhadap perubahan ukuran window
import tkinter as tk
from tkinter import ttk
class ResponsiveApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("Responsive Design dengan ttk")
self.root.geometry("800x600")
self.root.minsize(600, 400)
# Variabel untuk tracking ukuran window
self.current_width = 800
self.current_height = 600
self._setup_styles()
self._buat_interface()
self._bind_events()
Setelah method __init__, lanjutkan dengan setup styles untuk responsive design:
def _setup_styles(self):
self.style = ttk.Style()
# Style untuk desktop (default)
self.style.configure('Desktop.TLabel',
font=('Arial', 12),
padding=(10, 5))
# Style untuk tablet
self.style.configure('Tablet.TLabel',
font=('Arial', 10),
padding=(8, 4))
# Style untuk mobile
self.style.configure('Mobile.TLabel',
font=('Arial', 9),
padding=(5, 3))
# Button styles
self.style.configure('Desktop.TButton',
padding=(15, 8),
font=('Arial', 11))
self.style.configure('Tablet.TButton',
padding=(12, 6),
font=('Arial', 10))
self.style.configure('Mobile.TButton',
padding=(8, 4),
font=('Arial', 9))
Setelah setup styles, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Main container dengan scrollable frame
self.canvas = tk.Canvas(self.root)
self.scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
self.scrollable_frame = ttk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar.set)
# Header section
self.header_frame = ttk.Frame(self.scrollable_frame)
self.header_frame.pack(fill='x', padx=20, pady=20)
self.title_label = ttk.Label(self.header_frame, text="Responsive Design Demo",
style='Desktop.TLabel')
self.title_label.pack()
self.size_label = ttk.Label(self.header_frame, text=f"Window Size: {self.current_width}x{self.current_height}",
style='Desktop.TLabel')
self.size_label.pack(pady=(5,0))
Setelah header section, lanjutkan dengan content sections:
# Content grid yang akan berubah layout
self.content_frame = ttk.Frame(self.scrollable_frame)
self.content_frame.pack(fill='both', expand=True, padx=20, pady=(0,20))
# Cards yang akan di-reposition berdasarkan ukuran window
self.cards = []
for i in range(6):
card = ttk.LabelFrame(self.content_frame, text=f"Card {i+1}", padding="15")
ttk.Label(card, text=f"Content untuk card {i+1}",
style='Desktop.TLabel').pack()
ttk.Button(card, text=f"Action {i+1}",
style='Desktop.TButton').pack(pady=(10,0))
self.cards.append(card)
# Control panel
self.control_frame = ttk.LabelFrame(self.scrollable_frame, text="Controls", padding="15")
self.control_frame.pack(fill='x', padx=20, pady=(0,20))
ttk.Button(self.control_frame, text="Simulate Mobile (400x600)",
command=lambda: self.resize_window(400, 600),
style='Desktop.TButton').pack(side='left', padx=(0,10))
ttk.Button(self.control_frame, text="Simulate Tablet (600x800)",
command=lambda: self.resize_window(600, 800),
style='Desktop.TButton').pack(side='left', padx=(0,10))
ttk.Button(self.control_frame, text="Simulate Desktop (800x600)",
command=lambda: self.resize_window(800, 600),
style='Desktop.TButton').pack(side='left')
# Pack canvas dan scrollbar
self.canvas.pack(side="left", fill="both", expand=True)
self.scrollbar.pack(side="right", fill="y")
# Initial layout
self._update_layout()
Setelah interface setup, lanjutkan dengan method untuk handle responsive behavior:
def _bind_events(self):
self.root.bind('<Configure>', self._on_window_resize)
def _on_window_resize(self, event):
if event.widget == self.root:
self.current_width = self.root.winfo_width()
self.current_height = self.root.winfo_height()
self.size_label.config(text=f"Window Size: {self.current_width}x{self.current_height}")
self._update_layout()
def _update_layout(self):
# Clear current layout
for card in self.cards:
card.pack_forget()
# Determine layout based on window width
if self.current_width < 500: # Mobile
self._apply_mobile_layout()
elif self.current_width < 700: # Tablet
self._apply_tablet_layout()
else: # Desktop
self._apply_desktop_layout()
def _apply_mobile_layout(self):
# Single column layout
for card in self.cards:
card.pack(fill='x', pady=(0,10))
self._update_styles('Mobile')
def _apply_tablet_layout(self):
# Two column layout
for i, card in enumerate(self.cards):
if i % 2 == 0:
card.pack(side='left', fill='both', expand=True, padx=(0,5), pady=(0,10))
else:
card.pack(side='right', fill='both', expand=True, padx=(5,0), pady=(0,10))
self._update_styles('Tablet')
def _apply_desktop_layout(self):
# Three column layout
for i, card in enumerate(self.cards):
row = i // 3
col = i % 3
if col == 0:
card.pack(side='left', fill='both', expand=True, padx=(0,5), pady=(0,10))
elif col == 1:
card.pack(side='left', fill='both', expand=True, padx=(5,5), pady=(0,10))
else:
card.pack(side='left', fill='both', expand=True, padx=(5,0), pady=(0,10))
self._update_styles('Desktop')
def _update_styles(self, size):
style_suffix = f'{size}.TLabel'
button_style = f'{size}.TButton'
self.title_label.config(style=style_suffix)
self.size_label.config(style=style_suffix)
# Update all labels and buttons in cards
for card in self.cards:
for child in card.winfo_children():
if isinstance(child, ttk.Label):
child.config(style=style_suffix)
elif isinstance(child, ttk.Button):
child.config(style=button_style)
def resize_window(self, width, height):
self.root.geometry(f"{width}x{height}")
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = ResponsiveApp()
app.run()
Tugas: Jalankan aplikasi dan coba ubah ukuran window dengan drag corner atau gunakan tombol simulasi. Perhatikan bagaimana layout berubah dari desktop (3 kolom) ke tablet (2 kolom) ke mobile (1 kolom). Amati juga perubahan ukuran font dan padding yang menyesuaikan dengan ukuran layar.
Bagian 3: Advanced Techniques dan Animasi
3.1 Animasi dan Transisi dengan ttk
Praktikum 3.1: Membuat aplikasi dengan efek animasi smooth
import tkinter as tk
from tkinter import ttk
import threading
import time
class AnimatedApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("Animasi dan Transisi dengan ttk")
self.root.geometry("800x600")
self.root.configure(bg='#1a1a1a')
# Animation variables
self.animation_running = False
self.progress_value = 0
self.fade_alpha = 1.0
self._setup_styles()
self._buat_interface()
Setelah method __init__, lanjutkan dengan setup styles untuk animasi:
def _setup_styles(self):
self.style = ttk.Style()
# Dark theme styles
self.style.configure('Dark.TFrame',
background='#2d2d2d',
relief='flat')
self.style.configure('Dark.TLabel',
background='#2d2d2d',
foreground='#ffffff',
font=('Arial', 11))
self.style.configure('Title.TLabel',
background='#2d2d2d',
foreground='#00ff88',
font=('Arial', 16, 'bold'))
self.style.configure('Animated.TButton',
background='#00ff88',
foreground='#1a1a1a',
borderwidth=0,
focuscolor='none',
padding=(20, 10),
font=('Arial', 10, 'bold'))
self.style.map('Animated.TButton',
background=[('active', '#00cc6a'),
('pressed', '#009954')])
Setelah setup styles, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Main container
self.main_frame = ttk.Frame(self.root, style='Dark.TFrame')
self.main_frame.pack(fill='both', expand=True, padx=20, pady=20)
# Header dengan animasi fade
self.header_frame = ttk.Frame(self.main_frame, style='Dark.TFrame')
self.header_frame.pack(fill='x', pady=(0,30))
self.title_label = ttk.Label(self.header_frame, text="🎬 Animation Demo",
style='Title.TLabel')
self.title_label.pack()
# Progress animation section
self.progress_frame = ttk.LabelFrame(self.main_frame, text="Progress Animation", padding="20")
self.progress_frame.pack(fill='x', pady=(0,20))
ttk.Label(self.progress_frame, text="Animated Progress Bar:",
style='Dark.TLabel').pack(anchor='w', pady=(0,10))
self.progress_bar = ttk.Progressbar(self.progress_frame, mode='determinate',
length=400, style='TProgressbar')
self.progress_bar.pack(fill='x', pady=(0,15))
# Control buttons untuk progress
progress_controls = ttk.Frame(self.progress_frame, style='Dark.TFrame')
progress_controls.pack(fill='x')
ttk.Button(progress_controls, text="Start Animation",
style='Animated.TButton', command=self.start_progress_animation).pack(side='left', padx=(0,10))
ttk.Button(progress_controls, text="Stop Animation",
style='Animated.TButton', command=self.stop_animation).pack(side='left', padx=(0,10))
ttk.Button(progress_controls, text="Reset",
style='Animated.TButton', command=self.reset_progress).pack(side='left')
Setelah progress section, lanjutkan dengan sliding panel animation:
# Sliding panel animation
self.slide_frame = ttk.LabelFrame(self.main_frame, text="Sliding Panel", padding="20")
self.slide_frame.pack(fill='x', pady=(0,20))
# Container untuk sliding panel
self.slide_container = ttk.Frame(self.slide_frame, style='Dark.TFrame', height=200)
self.slide_container.pack(fill='x', pady=(0,15))
self.slide_container.pack_propagate(False)
# Panel yang akan slide
self.sliding_panel = ttk.Frame(self.slide_container, style='Dark.TFrame')
self.sliding_panel.place(x=-300, y=0, width=300, height=200)
# Content dalam sliding panel
panel_content = ttk.Frame(self.sliding_panel, style='Dark.TFrame')
panel_content.pack(fill='both', expand=True, padx=20, pady=20)
ttk.Label(panel_content, text="🎯 Sliding Panel Content",
style='Title.TLabel').pack()
ttk.Label(panel_content, text="Panel ini dapat slide masuk dan keluar",
style='Dark.TLabel').pack(pady=(10,0))
# Controls untuk sliding
slide_controls = ttk.Frame(self.slide_frame, style='Dark.TFrame')
slide_controls.pack(fill='x')
ttk.Button(slide_controls, text="Slide In",
style='Animated.TButton', command=self.slide_in).pack(side='left', padx=(0,10))
ttk.Button(slide_controls, text="Slide Out",
style='Animated.TButton', command=self.slide_out).pack(side='left')
Setelah sliding panel, lanjutkan dengan fade animation dan methods:
# Fade animation section
self.fade_frame = ttk.LabelFrame(self.main_frame, text="Fade Animation", padding="20")
self.fade_frame.pack(fill='both', expand=True)
# Content yang akan fade
self.fade_content = ttk.Frame(self.fade_frame, style='Dark.TFrame')
self.fade_content.pack(fill='both', expand=True, pady=(0,15))
ttk.Label(self.fade_content, text="✨ Fade Content",
style='Title.TLabel').pack(pady=20)
ttk.Label(self.fade_content, text="Content ini dapat fade in/out dengan smooth transition",
style='Dark.TLabel').pack()
# Fade controls
fade_controls = ttk.Frame(self.fade_frame, style='Dark.TFrame')
fade_controls.pack(fill='x')
ttk.Button(fade_controls, text="Fade Out",
style='Animated.TButton', command=self.fade_out).pack(side='left', padx=(0,10))
ttk.Button(fade_controls, text="Fade In",
style='Animated.TButton', command=self.fade_in).pack(side='left')
# Animation methods
def start_progress_animation(self):
if not self.animation_running:
self.animation_running = True
threading.Thread(target=self._animate_progress, daemon=True).start()
def _animate_progress(self):
while self.animation_running and self.progress_value < 100:
self.progress_value += 1
self.progress_bar['value'] = self.progress_value
time.sleep(0.05) # 50ms delay untuk smooth animation
if self.progress_value >= 100:
self.animation_running = False
def stop_animation(self):
self.animation_running = False
def reset_progress(self):
self.animation_running = False
self.progress_value = 0
self.progress_bar['value'] = 0
Setelah progress animation methods, lanjutkan dengan sliding dan fade methods:
def slide_in(self):
threading.Thread(target=self._animate_slide_in, daemon=True).start()
def _animate_slide_in(self):
start_x = -300
end_x = 0
steps = 30
for i in range(steps + 1):
current_x = start_x + (end_x - start_x) * (i / steps)
self.sliding_panel.place(x=current_x, y=0, width=300, height=200)
time.sleep(0.02) # 20ms untuk smooth animation
def slide_out(self):
threading.Thread(target=self._animate_slide_out, daemon=True).start()
def _animate_slide_out(self):
start_x = 0
end_x = -300
steps = 30
for i in range(steps + 1):
current_x = start_x + (end_x - start_x) * (i / steps)
self.sliding_panel.place(x=current_x, y=0, width=300, height=200)
time.sleep(0.02)
def fade_out(self):
threading.Thread(target=self._animate_fade_out, daemon=True).start()
def _animate_fade_out(self):
steps = 20
for i in range(steps + 1):
alpha = 1.0 - (i / steps)
# Simulate fade dengan mengubah state
if alpha < 0.5:
for child in self.fade_content.winfo_children():
child.configure(state='disabled' if hasattr(child, 'configure') else None)
time.sleep(0.05)
# Hide content
self.fade_content.pack_forget()
def fade_in(self):
self.fade_content.pack(fill='both', expand=True, pady=(0,15))
threading.Thread(target=self._animate_fade_in, daemon=True).start()
def _animate_fade_in(self):
steps = 20
for i in range(steps + 1):
alpha = i / steps
# Restore state
if alpha > 0.5:
for child in self.fade_content.winfo_children():
if hasattr(child, 'configure'):
try:
child.configure(state='normal')
except:
pass
time.sleep(0.05)
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = AnimatedApp()
app.run()
Tugas: Jalankan aplikasi dan coba semua animasi yang tersedia. Mulai dengan Start Animation untuk progress bar, lalu coba Slide In/Out untuk panel, dan Fade In/Out untuk content. Perhatikan bagaimana setiap animasi berjalan smooth dengan threading untuk mencegah UI freeze.
3.2 Dashboard Interaktif dengan Real-time Updates
Praktikum 3.2: Membuat dashboard dengan update data real-time
import tkinter as tk
from tkinter import ttk
import threading
import time
import random
from datetime import datetime
class InteractiveDashboard:
def __init__(self):
self.root = tk.Tk()
self.root.title("Interactive Dashboard - Real-time Updates")
self.root.geometry("1000x700")
self.root.configure(bg='#0f1419')
# Data variables
self.is_running = False
self.cpu_data = []
self.memory_data = []
self.network_data = []
self.max_data_points = 50
# Metrics
self.current_cpu = 0
self.current_memory = 0
self.current_network = 0
self.total_requests = 0
self.active_users = 0
self._setup_styles()
self._buat_interface()
Setelah method __init__, lanjutkan dengan setup styles untuk dashboard:
def _setup_styles(self):
self.style = ttk.Style()
# Dashboard theme
self.style.configure('Dashboard.TFrame',
background='#1e2328',
relief='flat')
self.style.configure('Card.TFrame',
background='#252a31',
relief='raised',
borderwidth=1)
self.style.configure('Dashboard.TLabel',
background='#1e2328',
foreground='#ffffff',
font=('Arial', 10))
self.style.configure('Title.TLabel',
background='#1e2328',
foreground='#00d4ff',
font=('Arial', 14, 'bold'))
self.style.configure('Metric.TLabel',
background='#252a31',
foreground='#00ff88',
font=('Arial', 20, 'bold'))
self.style.configure('MetricLabel.TLabel',
background='#252a31',
foreground='#ffffff',
font=('Arial', 9))
self.style.configure('Control.TButton',
background='#00d4ff',
foreground='#0f1419',
borderwidth=0,
padding=(15, 8),
font=('Arial', 10, 'bold'))
Setelah setup styles, lanjutkan dengan method untuk membuat interface:
def _buat_interface(self):
# Main container
self.main_frame = ttk.Frame(self.root, style='Dashboard.TFrame')
self.main_frame.pack(fill='both', expand=True, padx=10, pady=10)
# Header
header_frame = ttk.Frame(self.main_frame, style='Dashboard.TFrame')
header_frame.pack(fill='x', pady=(0,20))
ttk.Label(header_frame, text="📊 System Dashboard",
style='Title.TLabel').pack(side='left')
self.status_label = ttk.Label(header_frame, text="● Stopped",
style='Dashboard.TLabel')
self.status_label.pack(side='right')
self.time_label = ttk.Label(header_frame, text="",
style='Dashboard.TLabel')
self.time_label.pack(side='right', padx=(0,20))
# Metrics cards row
metrics_frame = ttk.Frame(self.main_frame, style='Dashboard.TFrame')
metrics_frame.pack(fill='x', pady=(0,20))
# CPU Card
self.cpu_card = ttk.Frame(metrics_frame, style='Card.TFrame', padding="15")
self.cpu_card.pack(side='left', fill='both', expand=True, padx=(0,10))
ttk.Label(self.cpu_card, text="CPU Usage", style='MetricLabel.TLabel').pack()
self.cpu_metric = ttk.Label(self.cpu_card, text="0%", style='Metric.TLabel')
self.cpu_metric.pack(pady=(5,0))
self.cpu_progress = ttk.Progressbar(self.cpu_card, mode='determinate', length=200)
self.cpu_progress.pack(pady=(10,0), fill='x')
Setelah CPU card, lanjutkan dengan memory dan network cards:
# Memory Card
self.memory_card = ttk.Frame(metrics_frame, style='Card.TFrame', padding="15")
self.memory_card.pack(side='left', fill='both', expand=True, padx=(5,5))
ttk.Label(self.memory_card, text="Memory Usage", style='MetricLabel.TLabel').pack()
self.memory_metric = ttk.Label(self.memory_card, text="0%", style='Metric.TLabel')
self.memory_metric.pack(pady=(5,0))
self.memory_progress = ttk.Progressbar(self.memory_card, mode='determinate', length=200)
self.memory_progress.pack(pady=(10,0), fill='x')
# Network Card
self.network_card = ttk.Frame(metrics_frame, style='Card.TFrame', padding="15")
self.network_card.pack(side='left', fill='both', expand=True, padx=(10,0))
ttk.Label(self.network_card, text="Network I/O", style='MetricLabel.TLabel').pack()
self.network_metric = ttk.Label(self.network_card, text="0 MB/s", style='Metric.TLabel')
self.network_metric.pack(pady=(5,0))
self.network_progress = ttk.Progressbar(self.network_card, mode='determinate', length=200)
self.network_progress.pack(pady=(10,0), fill='x')
# Statistics row
stats_frame = ttk.Frame(self.main_frame, style='Dashboard.TFrame')
stats_frame.pack(fill='x', pady=(0,20))
# Requests Card
requests_card = ttk.Frame(stats_frame, style='Card.TFrame', padding="15")
requests_card.pack(side='left', fill='both', expand=True, padx=(0,10))
ttk.Label(requests_card, text="Total Requests", style='MetricLabel.TLabel').pack()
self.requests_metric = ttk.Label(requests_card, text="0", style='Metric.TLabel')
self.requests_metric.pack(pady=(5,0))
# Users Card
users_card = ttk.Frame(stats_frame, style='Card.TFrame', padding="15")
users_card.pack(side='left', fill='both', expand=True, padx=(10,0))
ttk.Label(users_card, text="Active Users", style='MetricLabel.TLabel').pack()
self.users_metric = ttk.Label(users_card, text="0", style='Metric.TLabel')
self.users_metric.pack(pady=(5,0))
Setelah statistics cards, lanjutkan dengan chart area dan controls:
# Chart area (simulated dengan text widget)
chart_frame = ttk.LabelFrame(self.main_frame, text="Real-time Monitoring", padding="15")
chart_frame.pack(fill='both', expand=True, pady=(0,20))
self.chart_text = tk.Text(chart_frame, height=15, bg='#0f1419', fg='#00ff88',
font=('Consolas', 9), state='disabled')
self.chart_text.pack(fill='both', expand=True)
# Scrollbar untuk chart
chart_scroll = ttk.Scrollbar(chart_frame, orient="vertical", command=self.chart_text.yview)
chart_scroll.pack(side="right", fill="y")
self.chart_text.configure(yscrollcommand=chart_scroll.set)
# Control buttons
control_frame = ttk.Frame(self.main_frame, style='Dashboard.TFrame')
control_frame.pack(fill='x')
ttk.Button(control_frame, text="▶ Start Monitoring",
style='Control.TButton', command=self.start_monitoring).pack(side='left', padx=(0,10))
ttk.Button(control_frame, text="⏸ Stop Monitoring",
style='Control.TButton', command=self.stop_monitoring).pack(side='left', padx=(0,10))
ttk.Button(control_frame, text="🗑 Clear Data",
style='Control.TButton', command=self.clear_data).pack(side='left', padx=(0,10))
ttk.Button(control_frame, text="📊 Generate Report",
style='Control.TButton', command=self.generate_report).pack(side='right')
# Start time updates
self._update_time()
Setelah interface setup, lanjutkan dengan monitoring methods:
def _update_time(self):
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.time_label.config(text=current_time)
self.root.after(1000, self._update_time)
def start_monitoring(self):
if not self.is_running:
self.is_running = True
self.status_label.config(text="● Running", foreground='#00ff88')
threading.Thread(target=self._monitoring_loop, daemon=True).start()
def stop_monitoring(self):
self.is_running = False
self.status_label.config(text="● Stopped", foreground='#ff4444')
def _monitoring_loop(self):
while self.is_running:
# Generate random data
self.current_cpu = random.randint(10, 90)
self.current_memory = random.randint(20, 80)
self.current_network = random.uniform(0.1, 10.0)
# Update requests and users
self.total_requests += random.randint(1, 5)
self.active_users = random.randint(50, 200)
# Store data for chart
self.cpu_data.append(self.current_cpu)
self.memory_data.append(self.current_memory)
self.network_data.append(self.current_network)
# Limit data points
if len(self.cpu_data) > self.max_data_points:
self.cpu_data.pop(0)
self.memory_data.pop(0)
self.network_data.pop(0)
# Update UI
self._update_metrics()
self._update_chart()
time.sleep(1) # Update every second
def _update_metrics(self):
# Update metric displays
self.cpu_metric.config(text=f"{self.current_cpu}%")
self.cpu_progress['value'] = self.current_cpu
self.memory_metric.config(text=f"{self.current_memory}%")
self.memory_progress['value'] = self.current_memory
self.network_metric.config(text=f"{self.current_network:.1f} MB/s")
self.network_progress['value'] = min(self.current_network * 10, 100)
self.requests_metric.config(text=str(self.total_requests))
self.users_metric.config(text=str(self.active_users))
Setelah monitoring methods, lanjutkan dengan chart dan utility methods:
def _update_chart(self):
if not self.cpu_data:
return
self.chart_text.config(state='normal')
# Create simple ASCII chart
timestamp = datetime.now().strftime("%H:%M:%S")
cpu_bar = "█" * (self.current_cpu // 5)
memory_bar = "█" * (self.current_memory // 5)
network_bar = "█" * min(int(self.current_network), 20)
chart_line = f"[{timestamp}] CPU: {cpu_bar:<20} {self.current_cpu}% | "
chart_line += f"MEM: {memory_bar:<20} {self.current_memory}% | "
chart_line += f"NET: {network_bar:<20} {self.current_network:.1f}MB/s\n"
self.chart_text.insert('end', chart_line)
self.chart_text.see('end')
self.chart_text.config(state='disabled')
def clear_data(self):
self.cpu_data.clear()
self.memory_data.clear()
self.network_data.clear()
self.total_requests = 0
self.chart_text.config(state='normal')
self.chart_text.delete('1.0', 'end')
self.chart_text.config(state='disabled')
# Reset metrics
self.cpu_metric.config(text="0%")
self.memory_metric.config(text="0%")
self.network_metric.config(text="0 MB/s")
self.requests_metric.config(text="0")
self.users_metric.config(text="0")
self.cpu_progress['value'] = 0
self.memory_progress['value'] = 0
self.network_progress['value'] = 0
def generate_report(self):
if not self.cpu_data:
return
# Generate summary report
avg_cpu = sum(self.cpu_data) / len(self.cpu_data)
avg_memory = sum(self.memory_data) / len(self.memory_data)
avg_network = sum(self.network_data) / len(self.network_data)
report = f"\n{'='*60}\n"
report += f"SYSTEM PERFORMANCE REPORT - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
report += f"{'='*60}\n"
report += f"Average CPU Usage: {avg_cpu:.1f}%\n"
report += f"Average Memory Usage: {avg_memory:.1f}%\n"
report += f"Average Network I/O: {avg_network:.1f} MB/s\n"
report += f"Total Requests: {self.total_requests}\n"
report += f"Current Active Users: {self.active_users}\n"
report += f"Data Points Collected: {len(self.cpu_data)}\n"
report += f"{'='*60}\n\n"
self.chart_text.config(state='normal')
self.chart_text.insert('end', report)
self.chart_text.see('end')
self.chart_text.config(state='disabled')
def run(self):
self.root.mainloop()
# Jalankan aplikasi
if __name__ == "__main__":
app = InteractiveDashboard()
app.run()
Tugas: Jalankan aplikasi dashboard dan klik Start Monitoring untuk memulai simulasi data real-time. Amati bagaimana metrics berubah setiap detik, progress bar terupdate, dan chart menampilkan data historis. Coba Generate Report untuk melihat summary, lalu Clear Data untuk reset. Perhatikan bagaimana threading memungkinkan UI tetap responsif selama update data.