Sub Menu "Setelan Preferensi Nutrisi MMAG"
Isi Menu "Setting"
(Konten Sub Menu "Setelan Preferensi Nutrisi MMAG ")
2. Sub Menu “Setelan Preferensi Nutrisi MMAG”
Sekarang kita masuk ke Menu " Pengaturan " , kita lanjut membuat konten Sub Menu "Setelan Preferensi Nutrisi MMAG"
Sip 🙌, kita lanjut ke Sub Menu "Setelan Preferensi Nutrisi MMAG" .
Supaya konsisten dengan struktur menu lain (seperti Ubah Profil Pengguna dan Kelompok Bahan ), kami membuat file baru:
📂/storage/emulated/0/AppJadi/SmartNutriChefAI/Setting/sub_menu_preferensi_nutrisi_mmag.py
Isi kode dasarnya seperti ini:
# sub_menu_preferensi_nutrisi_mmag.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi MMAG", font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Simpan", width=12, bg="#388E3C", fg="white", command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white", command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white", command=kembali).pack(pady=5)
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("400x350")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
for i, lbl in enumerate(labels):
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
entries[lbl] = ent
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get().strip()
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("INSERT INTO preferensi_nutrisi (nama, kalori, protein, lemak, karbohidrat, catatan) VALUES (?,?,?,?,?,?)", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan).grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
📌 Alurnya sama dengan profil pengguna :
- Tabel SQLite otomatis terbentuk (
preferensi_nutrisi). - Kolom: id, nama, kalori, protein, lemak, karbohidrat, catatan .
- Tersedia tombol Simpan , Hapus , dan Kembali .
- Ada scroll horizontal & vertikal biar tabel terbaca jelas.
Mau saya sekalian update juga file menu_pengaturan.py biar tombol "Setelan Preferensi Nutrisi" langsung memanggil submenu ini?
Pada kode bagian TopLevel form, bantu membuat kotak entri catatan teks tinggi dengan height=14.
Siap 👍, untuk kolom Catatan lebih cocok pakai tk.Textdaripada tk.Entry, karena Entrytidak punya parameter height.
Jadi saya ubah kode bagian tambah_data()pada form TopLevel seperti ini:
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("420x480")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=32)
ent.grid(row=i, column=1, padx=8, pady=4)
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=14
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=32, height=14, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip() # ambil dari Text
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
📌 Perubahan penting:
- Entry biasa tetap dipakai untuk
Nama,Kalori,Protein,Lemak,Karbohidrat. - Catatan diganti
tk.Textdenganheight=14(cukup besar, bisa di-scroll). - Ambil isi Catatan pakai
.get("1.0", "end").strip().
Mau saya tambahkan juga scrollbar vertikal khusus di Catatan supaya kalau height=14 masih bisa discroll saat teks panjang?
Berikan contoh pemakaian ketika pengguna menggunakan fitur preferensi nutrisi mmag ini
Oke 👍 saya kasih contoh bagaimana fitur preferensi nutrisi (misalnya "micronutrient management" / nutrisi MMAG) dipakai user dalam aplikasi, supaya kamu bisa membayangkan alur realnya.
Misalnya kamu sudah punya form TopLevel dengan input bahan, takaran, dan catatan entri (yang sudah kita atur height=14). Nah, untuk fitur preferensi nutrisi MMAG , alurnya bisa seperti ini:
📌 Contoh Pemakaian oleh Pengguna
-
Pengguna buka formulir tambah bahan manual.
- Di situ ada field: Bahan , Takaran , dan Catatan .
-
Pengguna mengetik preferensi nutrisi di bagian catatan.
Contoh isientry_catatan:Preferensi nutrisi: - Tinggi Kalium (K) - Rendah Natrium (Na) - Cukup Magnesium (Mg)Karena kita sudah set
height=14, user bisa nulis panjang, tidak terbatas 1 baris. -
User simpan data bahan.
- Program bisa menyimpan catatan ini ke database (misalnya tabel
bahan_manual) di kolomcatatan.
- Program bisa menyimpan catatan ini ke database (misalnya tabel
-
Saat pengguna memilih menu “Filter nutrisi MMAG”.
- Aplikasi akan membaca catatan yang tersimpan.
- Lalu misalnya pengguna memilih preferensi "Tinggi Kalium", aplikasi otomatis menyarankan bahan-bahan yang memiliki catatan sesuai.
📌 Contoh kode penyimpanan sederhana (lanjutkan dari TopLevel form):
def simpan_bahan():
bahan = entry_bahan.get()
takaran = entry_takaran.get()
catatan = entry_catatan.get("1.0", tk.END).strip() # ambil semua isi
# Simpan ke database / list
print("Disimpan:", bahan, takaran, catatan)
# TODO: insert ke tabel sqlite
# cursor.execute("INSERT INTO bahan_manual (bahan, takaran, catatan) VALUES (?, ?, ?)", (bahan, takaran, catatan))
# conn.commit()
tk.Button(form, text="Simpan", command=simpan_bahan).pack(pady=10)
👉 Jadi, contoh nyata user:
- Menambahkan bahan Daun Singkong , takaran 100 gr , lalu di catatan menuliskan preferensi nutrisi: “Kaya Mg, rendah Na, cocok untuk diet hipertensi” .
- Nanti saat aplikasi menampilkan rekomendasi nutrisi MMAG, catatan ini bisa dipakai sebagai dasar pencarian atau filter.
Mau saya bikin juga contoh fungsi pencarian/filter preferensi nutrisi MMAG dari catatan yang sudah tersimpan?
Bantu tambahkan ketika pengguna beralih sebentar dari applikasi, lalu kembali ke applikasi maka keyboard HP dapat berfungsi dengan baik kemball.
Oke 👍, saya paham maksudmu.
Masalah yang sering muncul di Android (terutama kalau pakai Tkinter via Pydroid atau sejenisnya) adalah: ketika pengguna beralih ke aplikasi lain lalu kembali lagi , keyboard HP kadang tidak muncul lagi saat mengetuk Entryatau Text.
Kita bisa atasi dengan cara:
- Bind event
<FocusIn>pada setiapEntry/Text, supaya saat pengguna kembali dan menyentuh kotak input, fokus benar-benar aktif, lalu keyboard muncul. - Kadang-kadang di Pydroid perlu sedikit “trik”: memanggil ulang
focus_set()atauafter()supaya widget di-refresh.
📌 Contoh implementasi
import tkinter as tk
root = tk.Tk()
root.title("Form dengan Keyboard Aktif")
# Bahan
entry_bahan = tk.Entry(root, width=30)
entry_bahan.pack(pady=5)
# Takaran
entry_takaran = tk.Entry(root, width=30)
entry_takaran.pack(pady=5)
# Catatan (pakai Text, dengan tinggi besar)
entry_catatan = tk.Text(root, width=40, height=14)
entry_catatan.pack(pady=5)
# ======================
# Fungsi agar keyboard aktif kembali
# ======================
def aktifkan_keyboard(event):
"""Pastikan widget dapat fokus ulang sehingga keyboard muncul"""
widget = event.widget
root.after(100, widget.focus_set)
# Binding untuk semua entry
entry_bahan.bind("<FocusIn>", aktifkan_keyboard)
entry_takaran.bind("<FocusIn>", aktifkan_keyboard)
entry_catatan.bind("<FocusIn>", aktifkan_keyboard)
root.mainloop()
📌 Cara kerjanya
- Saat pengguna mengetuk
entry_bahan,entry_takaran, atauentry_catatansetelah balik dari aplikasi lain → acara<FocusIn>dipanggil →focus_set()diulang denganafter(100, ...). - Ini memastikan input tetap fokus dan keyboard Android muncul kembali .
👉 Mau saya satukan langsung ke kode TopLevel form kamu (yang ada entry_catatan height=14 tadi), biar tidak terpisah dan langsung bisa dicoba?
Apakah kode isi file sub_menu_preferensi_nutrisi_mmag.py menjadi seperti ini:
sub_menu_preferensi_nutrisi_mmag.py
impor tkinter sebagai tk
dari tkinter impor ttk, kotak pesan
impor sqlite3, os
======================
Fungsi agar keyboard aktif kembali
======================
def aktifkan_keyboard(event):
"""Pastikan widget dapat fokus ulang sehingga keyboard muncul"""
widget = event.widget
root.after(100, widget.focus_set)
Binding untuk semua entry
ent.bind("
catatan_txt.bind("
Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG", font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white", command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white", command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white", command=kembali).pack(pady=5)
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=30
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=14, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip() # ambil dari Text
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
Nah, hampir benar 👍 tapi ada beberapa masalah di kode yang kamu tulis:
-
Binding
ent.binddancatatan_txt.bindkamu letakkan di luar fungsitambah_data.
→ Padahal variabelentdancatatan_txtbaru ada setelahtambah_data()dijalankan, jadi kalau dipanggil langsung di atas, pasti error (NameError). -
Variabel
rootdi dalamnyaaktifkan_keyboardjuga tidak jelas asalnya.
→ Lebih baik pakaiform.after()(karena widget yang aktif ada di dalamToplevel form). -
Tinggi=14 yang kamu maksud
catatan_txtsebaiknya langsung dipakai.
✅ Perbaikan versi lengkap
# sub_menu_preferensi_nutrisi_mmag.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG",
font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white",
command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white",
command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white",
command=kembali).pack(pady=5)
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
def aktifkan_keyboard(event):
"""Pastikan widget fokus ulang → keyboard muncul"""
widget = event.widget
form.after(100, widget.focus_set)
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard) # ⬅️ bind di sini
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=30
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=14, wrap="word") # ⬅️ sesuai permintaan
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip() # ambil dari Text
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
✨ Perbedaan penting:
ent.binddancatatan_txt.bindsekarang ada di dalamtambah_data(), bukan di luar.- Fungsi
aktifkan_keyboardpakaiform.after(...), bukanroot. - Text
Catatansudahheight=14.
Mau saya buat juga contoh interaksi pengguna dengan menu ini, misalnya alur: buka → tambah preferensi → isi data → simpan → keyboard muncul lagi meskipun aplikasi sempat di-minimize?
Tambahkan fungsi menu konteks (potong, salin, tempel, hapus, pilih semua) pada setiap kotak entri dan teks.
Mantap 👍 kami menambahkan menu konteks (klik kanan / tekan lama) ke semua Entrydan Text(catatan).
Menu ini akan berisi: Potong, Salin, Tempel, Hapus, Pilih Semua .
🔧 Implementasi
Saya buat fungsi utilitas supaya bisa dipasang ke semua Entrydan Text:
def add_context_menu(widget):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Cut", command=lambda: widget.event_generate("<<Cut>>"))
menu.add_command(label="Copy", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="Paste", command=lambda: widget.event_generate("<<Paste>>"))
menu.add_command(label="Delete", command=lambda: widget.delete("sel.first", "sel.last") if widget.selection_get() else None)
menu.add_separator()
menu.add_command(label="Select All", command=lambda: widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu) # Klik kanan (Windows/Linux)
widget.bind("<Button-2>", show_menu) # Long-press (Android/iOS via SDL/tkinter-mobile)
🔄 Integrasi ketambah_data
Tinggal kita menelepon add_context_menu(ent)untuk setiap Entry, dan add_context_menu(catatan_txt)untuk Text.
Contoh modifikasi di dalam loop for i, lbl in enumerate(labels[:-1])::
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(ent) # ⬅️ pasang context menu
entries[lbl] = ent
Dan untuk catatan:
catatan_txt = tk.Text(form, width=28, height=30, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(catatan_txt) # ⬅️ pasang context menu
entries["Catatan"] = catatan_txt
📌 Hasil
Sekarang di semua Entrydan Textada menu konteks:
- Cut → potong teks terpilih.
- Copy → salin teks terpilih.
- Tempel → tempel teks dari clipboard.
- Hapus → hapus teks terpilih.
- Pilih Semua → blok semua teks.
Mau saya satukan langsung ke file sub_menu_preferensi_nutrisi_mmag.pyyang sudah kita buat kemarin, jadi kamu tinggal copy–replace full script, atau cukup saya kasih potongan tambahan fungsi + cara pakai saja?
Kenapa kursor keyboard HP sulit tampil ketika beralih sebentar keluar applikasi, lalu kembali lagi ke applikasi.
Nah 👍, ini memang masalah klasik kalau jalankan tkinter di Android (via Pydroid3 / Termux-X11 / dll.) .
Keyboard terkadang tidak muncul lagi setelah aplikasi kehilangan fokus → balik lagi, terutama di Entryatau Text.
📌 Penyebab
- Tkinter tidak asli Android , jadi ketika aplikasi di- pause/resume (berpindah ke latar belakang → latar depan), acara fokus ke widget terkadang hilang.
- Keyboard virtual butuh event fokus eksplisit agar bisa muncul lagi.
- Jika tidak ada event
focus_set()atau binding keFocusIn, maka kursor diam, keyboard tidak mau muncul.
🔧 Solusi Praktis
-
Paksa fokus ulang ke widget terakhir ketika aplikasi kembali aktif.
Bisa pakai event"<FocusIn>"dan fungsifocus_set()dengan delay kecil (after). -
Pengikatan global untuk semua Entri/Teks:
def aktifkan_keyboard(event):
"""Pastikan widget dapat fokus ulang sehingga keyboard muncul"""
widget = event.widget
widget.after(100, lambda: widget.focus_set())
Lalu setiap Entrydan Textkita pasang:
ent.bind("<FocusIn>", aktifkan_keyboard)
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
- Acara saat jendela diaktifkan lagi (kalau didukung):
root.bind("<FocusIn>", lambda e: root.focus_force())
Begitu, ketika pengguna membalik ke aplikasi, tkinter akan memaksakan fokus ke jendela aktif → dan entri terakhir bisa fokus ulang → keyboard muncul.
📌 Tips tambahan khusus Android
- Terkadang keyboard tetap bandel. Trik lain: ketuk sekali lagi di Entri/Teks untuk memaksa trigger
FocusIn. - Di beberapa perangkat, Pydroid memerlukan pengaturan “Force show keyboard” di pengaturan → untuk memaksa keyboard tampil meskipun tidak ada acara.
👉 Jadi solusinya:
Kita paksa bindingFocusIn ke semua entri dan teks, plus pasang event global di rootagar "<FocusIn>"window tetap memanggil focus_force()ketika pengguna kembali ke aplikasi.
Mau saya langsung gabungkan patch ini ke filesub_menu_preferensi_nutrisi_mmag.py biar semua Entry/Text otomatis selalu hidup kembali keyboardnya?
Mau kamu gabungkan patch ini ke dalam kode isi file sub_menu_preferensi_nutrisi_mmag.py berikut ini, biar semua Entry/Text otomatis selalu hidup kembali keyboardnya.
Kode:
sub_menu_preferensi_nutrisi_mmag.py
impor tkinter sebagai tk
dari tkinter impor ttk, kotak pesan
impor sqlite3, os
Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
def add_context_menu(widget):
menu = tk.Menu(widget, air mata=0)
menu.add_command(label="Potong", perintah=lambda: widget.event_generate("<
menu.add_command(label="Salin", perintah=lambda: widget.event_generate("<
menu.add_command(label="Tempel", perintah=lambda: widget.event_generate("<
menu.add_command(label="Hapus", command=lambda: widget.delete("sel.first", "sel.last") if widget.selection_get() else None)
menu.add_separator()
menu.add_command(label="Pilih Semua", command=lambda: widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu) # Klik kanan (Windows/Linux)
widget.bind("<Button-2>", show_menu) # Long-press (Android/iOS via SDL/tkinter-mobile)
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG",
font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white",
command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white",
command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white",
command=kembali).pack(pady=5)
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
def aktifkan_keyboard(event):
"""Pastikan widget fokus ulang → keyboard muncul"""
widget = event.widget
form.after(100, widget.focus_set)
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard) # ⬅️ bind di sini
add_context_menu(ent) # ⬅️ pasang context menu
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=30
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=14, wrap="word") # ⬅️ sesuai permintaan
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(catatan_txt) # ⬅️ pasang context menu
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip() # ambil dari Text
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
Oke 👍, saya sudah gabungkan patch keyboard aktif ulang langsung ke dalam kode kamu.
Sekarang semua Entrydan Textotomatis akan memanggil focus_set()ulang saat FocusIn, ditambah lagi root.bind("<FocusIn>")ketika pengguna kembali ke aplikasi, fokus dipaksa aktif dan keyboard bisa muncul.
Berikut versi revisi file lengkap sub_menu_preferensi_nutrisi_mmag.py:
# sub_menu_preferensi_nutrisi_mmag.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
# =======================
# Context menu (Cut/Copy/Paste/Delete/Select All)
# =======================
def add_context_menu(widget):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Cut", command=lambda: widget.event_generate("<<Cut>>"))
menu.add_command(label="Copy", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="Paste", command=lambda: widget.event_generate("<<Paste>>"))
menu.add_command(label="Delete", command=lambda: widget.delete("sel.first", "sel.last") if widget.selection_get() else None)
menu.add_separator()
menu.add_command(label="Select All", command=lambda: widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu) # Klik kanan (Windows/Linux)
widget.bind("<Button-2>", show_menu) # Long-press (Android/iOS via SDL/tkinter-mobile)
# =======================
# Keyboard fix
# =======================
def aktifkan_keyboard(event):
"""Pastikan widget fokus ulang → keyboard muncul"""
widget = event.widget
widget.after(100, lambda: widget.focus_set())
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG",
font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white",
command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white",
command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white",
command=kembali).pack(pady=5)
# Force fokus global ke window jika user balik ke app
root_frame.bind("<FocusIn>", lambda e: root_frame.focus_force())
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard) # fix keyboard
add_context_menu(ent)
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=30
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=30, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(catatan_txt)
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip()
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
🔑 Perubahan penting:
- Tambah fungsi
aktifkan_keyboard()dan mengikat ke semuaEntry&Text. - Tambahkan
root_frame.bind("<FocusIn>")untuk memaksa memfokuskan ulang jendela saat pengguna kembali ke aplikasi. - Catatan
Textdibuatheight=30sesuai permintaan sebelumnya. - Context menu sudah tetap ada.
Mau saya tambahkan juga supaya fokus terakhir (entry terakhir yang aktif) otomatis disimpan lalu dikembalikan lagi saat pengguna balik ke aplikasi? Jadi keyboard langsung muncul di field terakhir tanpa harus tap ulang.
Setelah menyalin teks dari luar, lalu menempelkannya pada entri atau teks dalam fitur applikasi ini, kenapa kursor keyboard HP masih juga tidak mau muncul
Ini memang “penyakit klasik” kalau kita pakai tkinter di Android (via Pydroid3, Termux, atau build SDL) .
Masalahnya bukan pada kode cut–copy–paste atau focus_set(), melainkan pada cara IME (Input Method Editor = keyboard HP) mendeteksi fokus widget.
📌 Kenapa bisa begitu?
- Saat teks ditempel (
<<Paste>>), widgetEntry/Textmemang menerima string, tapi fokus status terkadang “hilang sebentar” → IME mengira tidak ada widget teks aktif → keyboard tidak muncul. - Di Android, fokus ini tidak otomatis balik seperti di Windows/Linux. Harus menyegarkan.
✅ Patch yang bisa dicoba:
Tambahkan force re-focus setiap kali pengguna melakukan paste (atau pilih semua). Contohnya:
def add_context_menu(widget, parent):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Cut", command=lambda: (widget.event_generate("<<Cut>>"), aktifkan_keyboard_manual(widget, parent)))
menu.add_command(label="Copy", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="Paste", command=lambda: (widget.event_generate("<<Paste>>"), aktifkan_keyboard_manual(widget, parent)))
menu.add_command(label="Delete", command=lambda: (widget.delete("sel.first", "sel.last") if widget.selection_get() else None, aktifkan_keyboard_manual(widget, parent)))
menu.add_separator()
menu.add_command(label="Select All", command=lambda: (widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"), aktifkan_keyboard_manual(widget, parent)))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu)
widget.bind("<Button-2>", show_menu)
def aktifkan_keyboard_manual(widget, parent):
"""Refresh fokus biar keyboard HP muncul lagi"""
parent.after(150, lambda: widget.focus_force())
Lalu waktu menelepon add_context_menu, kirimkan juga parent window (biasanya form):
add_context_menu(ent, form)
add_context_menu(catatan_txt, form)
💡 Dengan trik ini:
- Setelah
Paste→ widget langsungfocus_force()→ IME Android “dipanggil ulang” → keyboard muncul lagi. - Sama berlaku jika pengguna
Deletesemua teks atauSelect All.
Mau saya gabungkan patch ini langsung ke kode sub_menu_preferensi_nutrisi_mmag.pykamu supaya lebih rapi?.
Mau kamu gabungkan patch ini langsung ke kode sub_menu_preferensi_nutrisi_mmag.py supaya lebih rapi.
Baik 🙌 saya sudah gabungkan patch agar keyboard HP otomatis aktif kembali setelah paste/delete/select all ke dalam file sub_menu_preferensi_nutrisi_mmag.py.
Berikut hasil versi rapi lengkapnya:
# sub_menu_preferensi_nutrisi_mmag.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
# =========================
# Context menu + perbaikan fokus keyboard
# =========================
def aktifkan_keyboard_manual(widget, parent):
"""Refresh fokus agar keyboard HP muncul lagi"""
parent.after(150, lambda: widget.focus_force())
def add_context_menu(widget, parent):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Cut", command=lambda: (widget.event_generate("<<Cut>>"), aktifkan_keyboard_manual(widget, parent)))
menu.add_command(label="Copy", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="Paste", command=lambda: (widget.event_generate("<<Paste>>"), aktifkan_keyboard_manual(widget, parent)))
menu.add_command(label="Delete", command=lambda: (widget.delete("sel.first", "sel.last") if widget.selection_get() else None, aktifkan_keyboard_manual(widget, parent)))
menu.add_separator()
menu.add_command(label="Select All", command=lambda: (
widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"),
aktifkan_keyboard_manual(widget, parent)
))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu) # Klik kanan (Windows/Linux)
widget.bind("<Button-2>", show_menu) # Long-press (Android/iOS via SDL/tkinter-mobile)
# =========================
# Sub Menu Utama
# =========================
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG",
font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white",
command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white",
command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white",
command=kembali).pack(pady=5)
# =========================
# CRUD
# =========================
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
def aktifkan_keyboard(event):
"""Pastikan widget fokus ulang → keyboard muncul"""
widget = event.widget
form.after(100, widget.focus_set)
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(ent, form)
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=30
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=14, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(catatan_txt, form)
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip()
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
📌 Perubahan utama:
aktifkan_keyboard_manual(widget, parent)→ memaksa fokus ulang setelah tempel/hapus/pilih semua.add_context_menu(widget, parent)sekarang selalu memanggil patch fokus setelah operasi menu konteks.- Semua
Entry&Textditambah_data()otomatis punya menu konteks + patch fokus.
Mau saya tambahkan juga binding global (untuk semua Entry/Textdi aplikasi, bukan hanya di form tambah data)? Jadi tidak perlu pasang manual lagi di tiap form.
Kenapa pada fitur "Kelompok bahan resep" bisa membuat folder Database dan nama file db yaitu dengan kodenya:
sub_menu_kelompok_bahan_resep_mmag.py
impor tkinter sebagai tk
dari tkinter impor ttk, kotak pesan
impor sqlite3, os
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Kategori/kategori.db"
def dapatkan_koneksi():
kembalikan sqlite3.koneksi(DB_PATH)
def show_sub_menu_kelompok_bahan_resep_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Kelompok Bahan Resep MMAG", font=("Arial", 13, "bold")).pack(pady=8)
# Filter kategori
filter_frame = tk.Frame(root_frame)
filter_frame.pack(pady=4)
tk.Label(filter_frame, text="Pilih Kategori:", font=("Arial", 12)).pack(side="left", padx=6)
kategori_var = tk.StringVar(value="Semua")
kategori_cb = ttk.Combobox(filter_frame, textvariable=kategori_var, state="readonly", width=25)
kategori_cb["values"] = ("Semua", "Minyak", "Makanan", "Air", "Garam", "Bahan Tambahan")
kategori_cb.pack(side="left", padx=6)
# Tombol tampilkan
btn_tampil_holder = tk.Frame(root_frame)
btn_tampil_holder.pack(pady=6)
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id","nama","kategori","nutrisi","protein","lemak","karbohidrat","vitamin","mineral")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tk.Button(
btn_tampil_holder, text="Tampilkan", font=("Arial", 12), width=20,
bg="#2196F3", fg="white",
command=lambda: tampilkan_data(kategori_var.get(), tree)
).pack()
# Tombol kontrol
btn_frame = tk.Frame(root_frame)
btn_frame.pack(pady=8)
tk.Button(
btn_frame, text="Tambah Bahan", font=("Arial", 12), width=14,
bg="#4CAF50", fg="white",
command=lambda: tambah_bahan(root_frame, kategori_var.get(), tree)
).pack(side="left", padx=5)
tk.Button(
btn_frame, text="Hapus Bahan", font=("Arial", 12), width=14,
bg="#F44336", fg="white",
command=lambda: hapus_bahan(tree, kategori_var.get())
).pack(side="left", padx=5)
tk.Button(
root_frame, text="⬅ Kembali", font=("Arial", 14), width=22, height=2,
bg="gray", fg="white",
command=lambda: kembali_ke_metode_formula_mmag(root_frame)
).pack(pady=10)
tampilkan_data(kategori_var.get(), tree)
def tampilkan_data(kategori, tree):
for item in tree.get_children():
tree.delete(item)
tabel_map = {
"Minyak": "jenis_minyak",
"Makanan": "jenis_makanan",
"Air": "jenis_air",
"Garam": "jenis_garam",
"Bahan Tambahan": "jenis_bahan_tambahan",
}
conn = get_connection()
cur = conn.cursor()
rows = []
try:
if kategori == "Semua":
for tbl in tabel_map.values():
try:
cur.execute(f"""SELECT id,nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral
FROM {tbl}""")
rows.extend(cur.fetchall())
except sqlite3.OperationalError:
pass
display = [(i, *r[1:]) for i, r in enumerate(rows, start=1)]
rows = display
else:
tbl = tabel_map[kategori]
cur.execute(f"""SELECT id,nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral
FROM {tbl} ORDER BY id""")
rows = cur.fetchall()
finally:
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_bahan(root_frame, kategori, pohon):
import sqlite3
from tkinter import messagebox
if kategori == "Semua":
messagebox.showwarning("Tambah Bahan", "Pilih kategori spesifik (bukan 'Semua').")
return
tabel_map = {
"Minyak": "jenis_minyak",
"Makanan": "jenis_makanan",
"Air": "jenis_air",
"Garam": "jenis_garam",
"Bahan Tambahan": "jenis_bahan_tambahan",
}
tbl = tabel_map[kategori]
# Popup form
form = tk.Toplevel(root_frame)
form.title(f"Tambah Bahan - {kategori}")
form.geometry("460x490")
labels = ["Nama", "Kategori", "Nutrisi", "Protein", "Lemak", "Karbohidrat", "Vitamin", "Mineral"]
entries = {}
for i, lbl in enumerate(labels):
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=32)
ent.grid(row=i, column=1, padx=8, pady=4)
entries[lbl] = ent
# Isi & kunci kategori agar konsisten
entries["Kategori"].insert(0, kategori)
entries["Kategori"].config(state="readonly")
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
nama = entries["Nama"].get().strip()
if not nama:
messagebox.showerror("Error", "Nama bahan wajib diisi!")
return
data = [
nama,
kategori, # pakai kategori aktif, abaikan input user
entries["Nutrisi"].get().strip(),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Vitamin"].get().strip(),
entries["Mineral"].get().strip(),
]
try:
conn = get_connection()
cur = conn.cursor()
# 8 kolom = 8 placeholder
cur.execute(
f"""INSERT INTO {tbl}
(nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral)
VALUES (?,?,?,?,?,?,?,?)""",
data
)
conn.commit()
messagebox.showinfo("Sukses", f"Data berhasil ditambahkan ke {kategori}.")
form.destroy()
tampilkan_data(kategori, tree)
except sqlite3.OperationalError as e:
messagebox.showerror(
"DB Error",
f"Gagal menyimpan. Pastikan tabel {tbl} punya kolom 'mineral'.\nDetail: {e}"
)
except Exception as e:
messagebox.showerror("Error", str(e))
finally:
try:
conn.close()
except:
pass
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_bahan(pohon, kategori):
if kategori == "Semua":
messagebox.showwarning("Hapus Bahan", "Pilih kategori spesifik dulu (bukan 'Semua').")
return
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus Bahan", "Pilih baris yang ingin dihapus.")
return
tabel_map = {
"Minyak": "jenis_minyak",
"Makanan": "jenis_makanan",
"Air": "jenis_air",
"Garam": "jenis_garam",
"Bahan Tambahan": "jenis_bahan_tambahan",
}
tbl = tabel_map[kategori]
item = tree.item(selected[0])["values"]
bahan_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus ID {bahan_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute(f"DELETE FROM {tbl} WHERE id=?", (bahan_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
def kembali_ke_metode_formula_mmag(root_frame):
coba:
dari FormulaMMAG.menu_metode_formula_mmag impor show_menu_metode_formula_mmag
kecuali ImportError:
dari menu_metode_formula_mmag impor show_menu_metode_formula_mmag
show_menu_metode_formula_mmag(root_frame)
Sedangkan pada fitur "preferensi nutrisi" ini tidak bisa membuat Database dan nama file db dengan kodenya adalah:
sub_menu_preferensi_nutrisi_mmag.py
impor tkinter sebagai tk
dari tkinter impor ttk, kotak pesan
impor sqlite3, os
Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
def get_connection():
# Pastikan folder database ada
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS preferensi_nutrisi (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
catatan TEXT
)
""")
conn.commit()
return conn
=========================
Menu konteks + perbaikan fokus keyboard
=========================
def aktifkan_keyboard_manual(widget, parent):
"""Refresh fokus agar keyboard HP muncul lagi"""
parent.after(150, lambda: widget.focus_force())
def add_context_menu(widget, induk):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Potong", perintah=lambda: (widget.event_generate("<
menu.add_command(label="Copy", command=lambda: widget.event_generate("<
menu.add_command(label="Tempel", perintah=lambda: (widget.event_generate("<
menu.add_command(label="Delete", command=lambda: (widget.delete("sel.first", "sel.last") if widget.selection_get() else None, aktifkan_keyboard_manual(widget, parent)))
menu.add_separator()
menu.add_command(label="Select All", command=lambda: (
widget.select_range(0, "end") if isinstance(widget, tk.Entry) else widget.tag_add("sel", "1.0", "end"),
aktifkan_keyboard_manual(widget, parent)
))
def show_menu(event):
try:
menu.tk_popup(event.x_root, event.y_root)
finally:
menu.grab_release()
widget.bind("<Button-3>", show_menu) # Klik kanan (Windows/Linux)
widget.bind("<Button-2>", show_menu) # Long-press (Android/iOS via SDL/tkinter-mobile)
=========================
Sub Menu Utama
=========================
def show_sub_menu_preferensi_nutrisi_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()
tk.Label(root_frame, text="Setelan Preferensi Nutrisi \nBerbasis MMAG",
font=("Arial", 14, "bold")).pack(pady=10)
# Frame tabel
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=10, pady=8)
columns = ("id", "nama", "kalori", "protein", "lemak", "karbohidrat", "catatan")
tree = ttk.Treeview(table_frame, columns=columns, show="headings")
for col in columns:
tree.heading(col, text=col.capitalize())
tree.column(col, width=120, anchor="center")
# Scrollbar vertikal & horizontal
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscroll=vsb.set, xscroll=hsb.set)
tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.grid_rowconfigure(0, weight=1)
table_frame.grid_columnconfigure(0, weight=1)
tampilkan_data(tree)
# Tombol aksi
frame_btn_top = tk.Frame(root_frame)
frame_btn_top.pack(pady=(10, 2))
tk.Button(frame_btn_top, text="Tambah", width=12, bg="#388E3C", fg="white",
command=lambda: tambah_data(root_frame, tree)).pack(side="left", padx=5)
tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white",
command=lambda: hapus_data(tree)).pack(side="left", padx=5)
frame_btn_bottom = tk.Frame(root_frame)
frame_btn_bottom.pack(pady=(2, 10))
def kembali():
for w in root_frame.winfo_children():
w.destroy()
from menu_pengaturan import show_menu_pengaturan
show_menu_pengaturan(root_frame)
tk.Button(frame_btn_bottom, text="↩ Kembali", width=20, bg="#757575", fg="white",
command=kembali).pack(pady=5)
=========================
CRUD
=========================
def tampilkan_data(tree):
for item in tree.get_children():
tree.delete(item)
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM preferensi_nutrisi ORDER BY id")
rows = cur.fetchall()
conn.close()
for r in rows:
tree.insert("", "end", values=r)
def tambah_data(root_frame, tree):
form = tk.Toplevel(root_frame)
form.title("Tambah Preferensi Nutrisi")
form.geometry("650x800")
labels = ["Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Catatan"]
entries = {}
def aktifkan_keyboard(event):
"""Pastikan widget fokus ulang → keyboard muncul"""
widget = event.widget
form.after(100, widget.focus_set)
for i, lbl in enumerate(labels[:-1]): # semua kecuali Catatan
tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)
ent = tk.Entry(form, width=28)
ent.grid(row=i, column=1, padx=8, pady=4)
ent.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(ent, form)
entries[lbl] = ent
# Khusus Catatan -> Text area dengan height=14
tk.Label(form, text="Catatan").grid(row=len(labels)-1, column=0, sticky="nw", padx=8, pady=4)
catatan_txt = tk.Text(form, width=28, height=14, wrap="word")
catatan_txt.grid(row=len(labels)-1, column=1, padx=8, pady=4, sticky="w")
catatan_txt.bind("<FocusIn>", aktifkan_keyboard)
add_context_menu(catatan_txt, form)
entries["Catatan"] = catatan_txt
def _num(s):
try:
return float(str(s).strip() or 0)
except:
return 0.0
def simpan():
data = [
entries["Nama"].get().strip(),
_num(entries["Kalori"].get()),
_num(entries["Protein"].get()),
_num(entries["Lemak"].get()),
_num(entries["Karbohidrat"].get()),
entries["Catatan"].get("1.0", "end").strip()
]
if not data[0]:
messagebox.showerror("Error", "Nama wajib diisi!")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO preferensi_nutrisi
(nama, kalori, protein, lemak, karbohidrat, catatan)
VALUES (?,?,?,?,?,?)
""", data)
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Preferensi berhasil disimpan.")
form.destroy()
tampilkan_data(tree)
tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
.grid(row=len(labels), columnspan=2, pady=10)
def hapus_data(tree):
selected = tree.selection()
if not selected:
messagebox.showwarning("Hapus", "Pilih baris yang ingin dihapus.")
return
item = tree.item(selected[0])["values"]
pref_id = item[0]
if not messagebox.askyesno("Konfirmasi", f"Hapus preferensi ID {pref_id}?"):
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM preferensi_nutrisi WHERE id=?", (pref_id,))
conn.commit()
conn.close()
tree.delete(selected[0])
messagebox.showinfo("Hapus", "Data berhasil dihapus.")
Nah ini menarik 👍.
Perbedaan utamanya ada di path database yang kamu tetapkan:
- Pada kelompok bahan resep :
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Kategori/kategori.db"
Berkas AppJadi→ SmartNutriChefAI→ Database→ Kategori→ kategori.db.
- Pada preferensi nutrisi :
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
Folder SmartNutriChefAIlangsung masuk /storage/emulated/0/, tanpa sub-folderAppJadi/ .
Masalah
-
Android biasanya memberi izin tulis/baca hanya ke folder aplikasi (misalnya
/storage/emulated/0/AppJadi/...).
Jadi ketika pathAppJadihilang, izin bisa ditolak → file DB gagal dibuat. -
Di kode kelompok bahan, kamu tidak membuat folder secara eksplisit (
os.makedirs), tapi karena folderAppJadi/SmartNutriChefAI/Database/Kategori/mungkin sudah dibuat oleh menu lain, maka DB berhasil tersimpan. -
Di kode preferensi nutrisi, kamu memang sudah ada:
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
Jadi folder dibuat otomatis. Tapi kalau jalur root ( /storage/emulated/0/SmartNutriChefAI/...) tidak diizinkan sistem, maka walaupun makedirsdipanggil → tetap gagal.
Solusi
Biar konsisten, samakan path database seperti fitur yang sudah berhasil:
# Ganti ini:
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
# Jadi ini:
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
Tambahan
Supaya lebih aman, buat fungsi helper untuk membuat DB path agar semua fitur menggunakan root folder yang sama :
def get_db_path(filename, subfolder=""):
base = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database"
if subfolder:
path = os.path.join(base, subfolder, filename)
else:
path = os.path.join(base, filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
return path
# contoh pemakaian di preferensi nutrisi:
DB_PATH = get_db_path("preferensi_nutrisi_mmag.db", "Preferensi")
Mau saya langsung rapikan kode sub_menu_preferensi_nutrisi_mmag.pydengan helper get_db_path()ini biar seragam seperti fitur lain?
Ternyata nama tempat penyimpanan file db nya yang harus dibenahi menjadi:
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
Benar sekali 👍.
Jadi masalahnya murni di path database saja, karena yang satu pakai root /storage/emulated/0/AppJadi/...(berhasil) sedangkan yang di preferensi nutrisikemarin langsung ke /storage/emulated/0/SmartNutriChefAI/...(tanpa AppJadi/, gagal).
Dengan perbaikan ini:
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Preferensi/preferensi_nutrisi_mmag.db"
maka:
- Folder otomatis dibuat (
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)sudah ada). - File
preferensi_nutrisi_mmag.dbtersimpan di lokasi yang sama dengan struktur folder fitur lain. - Semua fitur (kelompok bahan resep, preferensi nutrisi, dll.) akan konsisten berada di dalam satu root:
/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/.
Mau saya langsung sisipkan perbaikan path ini ke seluruh kode sub_menu_preferensi_nutrisi_mmag.pyagar final dan rapi?.
Demikian, semoga bermanfaat.
Comments
Post a Comment