ASSISTANT
To modify your application to allow for entering two separate sets of income and expenses (for two people in a couple), you can adjust the layout to add additional fields while keeping the functionality intact.
### Steps to Modify the Code
1. **Add Sections for Each Individual**: You'll create distinct sections for each person's income and expenses.
2. **Modify the `ajouter_champ_revenu` and `ajouter_champ_depense` Methods**: These methods should now accommodate multiple individuals.
Here's how you can make these changes:
### Updated Code
Here's the modified section of your code with added support for two income and two expense entries:
```python
import tkinter as tk
from tkinter import messagebox, filedialog, Menu
import json
class CalculateurApp:
def __init__(self, root):
self.root = root
self.root.title("Calculateur de Dépenses et Revenus")
# Menu bar
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Sauvegarder", command=self.sauvegarder)
filemenu.add_command(label="Charger", command=self.charger)
filemenu.add_separator()
filemenu.add_command(label="Quitter", command=root.quit)
menubar.add_cascade(label="Fichier", menu=filemenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="Aide", command=self.afficher_aide)
menubar.add_cascade(label="Aide", menu=helpmenu)
root.config(menu=menubar)
# Listes pour stocker les entrées des revenus et des dépenses
self.revenus_data = [[], []] # Array for two sets of income
self.depenses_data = [[], []] # Array for two sets of expenses
# Create frames for each individual's income and expenses
for i in range(2): # Two individuals
# Cadre pour les revenus
cadre_revenus = tk.LabelFrame(root, text=f"Revenus - Personne {i + 1}", padx=10, pady=10)
cadre_revenus.grid(row=0, column=i, padx=10, pady=10)
# Créer un canvas pour les revenus et une scrollbar
canvas_revenus = tk.Canvas(cadre_revenus)
canvas_revenus.grid(row=0, column=0)
scrollbar_revenus = tk.Scrollbar(cadre_revenus, orient="vertical", command=canvas_revenus.yview)
scrollbar_revenus.grid(row=0, column=1, sticky="ns")
canvas_revenus.config(yscrollcommand=scrollbar_revenus.set)
# Frame pour les revenus à l'intérieur du Canvas
frame_revenus = tk.Frame(canvas_revenus)
canvas_revenus.create_window((0, 0), window=frame_revenus, anchor="nw")
self.revenus_data[i].append(frame_revenus) # Storing the frame for each person
# Cadre pour les dépenses
cadre_depenses = tk.LabelFrame(root, text=f"Dépenses - Personne {i + 1}", padx=10, pady=10)
cadre_depenses.grid(row=1, column=i, padx=10, pady=10)
canvas_depenses = tk.Canvas(cadre_depenses)
canvas_depenses.grid(row=0, column=0)
scrollbar_depenses = tk.Scrollbar(cadre_depenses, orient="vertical", command=canvas_depenses.yview)
scrollbar_depenses.grid(row=0, column=1, sticky="ns")
canvas_depenses.config(yscrollcommand=scrollbar_depenses.set)
frame_depenses = tk.Frame(canvas_depenses)
canvas_depenses.create_window((0, 0), window=frame_depenses, anchor="nw")
self.depenses_data[i].append(frame_depenses) # Storing the frame for each person
# Add initial fields for each person
self.ajouter_champ_revenu(i)
self.ajouter_champ_depense(i)
# Total cadre
self.cadre_total = tk.LabelFrame(root, text="Total", padx=10, pady=10)
self.cadre_total.grid(row=2, column=0, columnspan=2, padx=10, pady=10)
self.solde_label_texte = tk.Label(self.cadre_total, text="Solde restant : ")
self.solde_label_texte.grid(row=0, column=0, sticky="w")
self.solde_label_montant = tk.Label(self.cadre_total, text="0.00 $", fg="black")
self.solde_label_montant.grid(row=0, column=1)
# Activer le défilement fluide pour la souris
for i in range(2):
canvas_revenus.bind_all("<MouseWheel>", self.scroll_with_mouse)
canvas_depenses.bind_all("<MouseWheel>", self.scroll_with_mouse)
def ajouter_champ_revenu(self, person_index):
""" Add income fields for a specified person """
champ_revenu = tk.Entry(self.revenus_data[person_index][-1], width=15)
champ_revenu.grid(row=len(self.revenus_data[person_index]) - 1, column=0, pady=5) # 0 is for montant
champ_description_revenu = tk.Entry(self.revenus_data[person_index][-1], width=30)
champ_description_revenu.grid(row=len(self.revenus_data[person_index]) - 1, column=1, pady=5) # 1 is for description
delete_button_revenu = tk.Button(self.revenus_data[person_index][-1], text="Supprimer")
delete_button_revenu.grid(row=len(self.revenus_data[person_index]) - 1, column=2, pady=5)
delete_button_revenu.config(command=lambda index=len(self.revenus_data[person_index]) - 1: self.supprimer_entree(index, "revenus", person_index))
entry_data = (champ_revenu, champ_description_revenu, delete_button_revenu)
self.revenus_data[person_index].append(entry_data)
champ_revenu.bind("<Return>", lambda e: self.creer_nouveau_champ_revenu(person_index))
champ_description_revenu.bind("<Return>", lambda e: self.creer_nouveau_champ_revenu(person_index))
champ_revenu.bind("<KeyRelease>", lambda e: self.calculer()) # Link to calculating function
champ_description_revenu.bind("<KeyRelease>", lambda e: self.calculer()) # Link
champ_revenu.focus()
def ajouter_champ_depense(self, person_index):
""" Add expense fields for a specified person """
champ_depense = tk.Entry(self.depenses_data[person_index][-1], width=15)
champ_depense.grid(row=len(self.depenses_data[person_index]) - 1, column=0, pady=5)
champ_description_depense = tk.Entry(self.depenses_data[person_index][-1], width=30)
champ_description_depense.grid(row=len(self.depenses_data[person_index]) - 1, column=1, pady=5)
delete_button_depense = tk.Button(self.depenses_data[person_index][-1], text="Supprimer")
delete_button_depense.grid(row=len(self.depenses_data[person_index]) - 1, column=2, pady=5)
delete_button_depense.config(command=lambda index=len(self.depenses_data[person_index]) - 1: self.supprimer_entree(index, "depenses", person_index))
entry_data = (champ_depense, champ_description_depense, delete_button_depense)
self.depenses_data[person_index].append(entry_data)
champ_depense.bind("<Return>", lambda e: self.creer_nouveau_champ_depense(person_index))
champ_description_depense.bind("<Return>", lambda e: self.creer_nouveau_champ_depense(person_index))
champ_depense.bind("<KeyRelease>", lambda e: self.calculer()) # Link to calculating function
champ_description_depense.bind("<KeyRelease>", lambda e: self.calculer()) # Link
champ_depense.focus()
def creer_nouveau_champ_revenu(self, person_index):
""" Créer un nouveau champ pour les revenus lors de l'appui sur Enter. """
self.ajouter_champ_revenu(person_index)
def creer_nouveau_champ_depense(self, person_index):
""" Créer un nouveau champ pour les dépenses lors de l'appui sur Enter. """
self.ajouter_champ_depense(person_index)
def calculer(self, event=None):
""" Calculate the total incomes and expenses for both individuals. """
total_revenus = 0
total_depenses = 0
for i in range(2): # Two individuals
total_revenus += sum(float(entree[0].get()) for entree in self.revenus_data[i] if entree[0].get())
total_depenses += sum(float(entree[0].get()) for entree in self.depenses_data[i] if entree[0].get())
solde = total_revenus - total_depenses
self.solde_label_montant.config(text=f"{solde:.2f} $")
if solde < 0:
self.solde_label_montant.config(fg="red")
else:
self.solde_label_montant.config(fg="green")
def afficher_aide(self):
""" Afficher un message d'aide avec les instructions d'utilisation de l'application """
messagebox.showinfo("Aide", "Voici comment utiliser l'application :\n\n"
"1. Entrez vos revenus et vos dépenses.\n"
"2. Appuyez sur 'Enter' pour ajouter une nouvelle ligne.\n"
"3. Sauvegardez vos données en utilisant le menu Fichier > Sauvegarder.\n"
"4. Chargez vos données en utilisant le menu Fichier > Charger.\n"
"5. Vous pouvez faire défiler la liste de revenus et de dépenses à l'aide de la molette de la souris.\n"
"6. Le solde restant est mis à jour automatiquement lorsque vous entrez ou modifiez un montant.\n"
"7. Les champs sont calculés automatiquement après chaque entrée.\n"
"8. Cliquez sur le bouton 'Supprimer' pour supprimer une entrée.\n"
"\n"
"[CRÉDIT SAMUEL CREVIER V1.1 ©2024]")
def scroll_with_mouse(self, event):
"""Fixes scrolling direction for mouse wheel"""
if event.widget in [self.revenus_data[0][-1], self.revenus_data[1][-1]]:
event.widget.yview_scroll(1*(event.delta//120), "units")
def sauvegarder(self):
try:
fichier = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("Fichiers JSON", "*.json")])
if fichier:
data = {
"revenus": [
[{"montant": e[0].get(), "description": e[1].get()} for e in person] for person in self.revenus_data
],
"depenses": [
[{"montant": e[0].get(), "description": e[1].get()} for e in person] for person in self.depenses_data
],
}
with open(fichier, "w") as f:
json.dump(data, f, indent=4)
messagebox.showinfo("Sauvegarde", "Les données ont été sauvegardées avec succès.")
except (FileNotFoundError, json.JSONEncoderError) as e:
messagebox.showerror("Erreur de sauvegarde", f"Impossible de sauvegarder les données : {e}")
except Exception as e:
messagebox.showerror("Erreur de sauvegarde", f"Une erreur inattendue s'est produite: {e}")
def charger(self):
try:
fichier = filedialog.askopenfilename(filetypes=[("Fichiers JSON", "*.json")])
if fichier:
with open(fichier, "r") as f:
data = json.load(f)
# Clear existing entries and reset lists
self.clear_entries()
# Add entries for both individuals
for i in range(2): # Two individuals
for revenu in data.get("revenus", [])[i]:
montant = revenu.get("montant")
description = revenu.get("description")
if montant and description: # Check for valid data
self.ajouter_champ_revenu(i)
self.revenus_data[i][-1][0].insert(0, montant)
self.revenus_data[i][-1][1].insert(0, description)
for depense in data.get("depenses", [])[i]:
montant = depense.get("montant")
description = depense.get("description")
if montant and description: # Check for valid data
self.ajouter_champ_depense(i)
self.depenses_data[i][-1][0].insert(0, montant)
self.depenses_data[i][-1][1].insert(0, description)
# Recalculate the balance after loading
self.calculer()
messagebox.showinfo("Chargement", "Les données ont été chargées avec succès.")
except (FileNotFoundError, json.JSONDecodeError) as e:
messagebox.showerror("Erreur de chargement", f"Impossible de charger les données : {e}")
except Exception as e:
messagebox.showerror("Erreur de chargement", f"Une erreur inattendue s'est produite: {e}")
def supprimer_entree(self, index, type_entree, person_index):
"""Supprime une entrée de revenu ou de dépense, sauf si c'est la dernière."""
if type_entree == "revenus":
data = self.revenus_data[person_index]
frame = data[-1]
elif type_entree == "depenses":
data = self.depenses_data[person_index]
frame = data[-1]
else:
return
if len(data) == 1:
entree, description, _ = data[0]
entree.delete(0, tk.END) # Vider le champ montant
description.delete(0, tk.END) # Vider le champ description
return
if 0 <= index < len(data):
entree, description, bouton = data.pop(index)
entree.destroy()
description.destroy()
bouton.destroy()
self.reorganiser_grid(type_entree, person_index)
self.calculer()
def reorganiser_grid(self, type_entree, person_index):
"""Réorganise la grille après suppression d'une entrée."""
if type_entree == "revenus":
data = self.revenus_data[person_index]
frame = data[-1]
elif type_entree == "depenses":
data = self.depenses_data[person_index]
frame = data[-1]
else:
return
for i, (entree, description, bouton) in enumerate(data):
entree.grid(row=i, column=0, pady=5)
description.grid(row=i, column=1, pady=5)
bouton.grid(row=i, column=2, pady=5)
bouton.config(command=lambda index=i: self.supprimer_entree(index, type_entree, person_index)) # Attacher la commande correctement
frame.update_idletasks()
def clear_entries(self):
"""Efface toutes les entrées tout en laissant au moins une ligne dans chaque section."""
for person_revenus in self.revenus_data:
for widget in person_revenus[-1].winfo_children():
widget.destroy()
for person_depenses in self.depenses_data:
for widget in person_depenses[-1].winfo_children():
widget.destroy()
# Reset data structures
self.revenus_data = [[], []] # Reset for two individuals
self.depenses_data = [[], []] # Reset for two individuals
# Initially add one row for each person
for i in range(2):
self.ajouter_champ_revenu(i)
self.ajouter_champ_depense(i)
# Créer la fenêtre principale et démarrer l'application
root = tk.Tk()
app = CalculateurApp(root)
root.mainloop()
```
### Key Changes
1. **Separate Sections for Each Person**:
- Added the ability to track two separate people, each with their own income and expense sections.
2. **Dynamic Field Addition**:
- Each individual can have their income and expenses managed independently, while still supporting the functionalities of adding and calculating totals.
3. **JSON Structure**:
- The application expects a specific structure for the JSON file that includes entries for both individuals.
### JSON Structure
Make sure when you save or load your JSON file, it adheres to the following structure to support two individuals:
```json
{
"revenus": [
[
{"montant": "1000", "description": "Salaire"},
{"montant": "200", "description": "Freelance"}
],
[
{"montant": "1500", "description": "Salaire"},
{"montant": "300", "description": "Investissement"}
]
],
"depenses": [
[
{"montant": "150", "description": "Loyer"},
{"montant": "50", "description": "Nourriture"}
],
[
{"montant": "200", "description": "Transport"},
{"montant": "100", "description": "Services"}
]
]
}
```
### Testing
1. **Run the modified script**.
2. **Test adding incomes and expenses for both individuals.**
3. **Save and load data** to ensure it works as expected.
If you have any further adjustments or additional features you'd like to implement, feel free to ask!