Pourquoi quand je fais un copie coller dans mon IDE il ne me le prend pas comme du code mais juste comme du texte ?
Vu, manquait .py dans le nom du fichier
Version imprimable
Pourquoi quand je fais un copie coller dans mon IDE il ne me le prend pas comme du code mais juste comme du texte ?
Vu, manquait .py dans le nom du fichier
OH!!!! superbe.
Bon, bé, je n'en suis pas là....
Je vais partir de ta base en ajouter tout ce que j'ai déjà ajouté...
Merci encore mais une fois cette fenêtre saisie validée je pourrais passer aux autres comme saisie compta...
Voilà le code tel qu'il est à l'instant:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 import tkinter as tk from tkinter import messagebox from tkinter import font import tkinter.font as font from tkinter import * import re import datetime # --- Gestion des placeholders --- def setup_placeholder(entry, placeholder_text): """Configure un placeholder pour un champ Entry""" entry.insert(0, placeholder_text) entry.config(fg="grey") def on_focus_in(event): if entry.get() == placeholder_text: entry.delete(0, tk.END) entry.config(fg="black") def on_focus_out(event): if entry.get() == "": entry.insert(0, placeholder_text) entry.config(fg="grey") entry.bind("<FocusIn>", on_focus_in) entry.bind("<FocusOut>", on_focus_out) # --- Fonctions de validation --- def valider_email(email): """Valide le format d'un email""" pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" return re.match(pattern, email) is not None def valider_telephone(telephone): """Valide le format d'un numéro de téléphone (FR, BE, ES)""" if telephone == "": # Champ optionnel return True # Retirer les espaces pour la validation tel_clean = telephone.replace(" ", "") # Format avec indicatif international: 0033, 0032, 0034 # France: 0033 1 23 45 67 89 (11 chiffres après nettoyage) # Belgique: 0032 4 12 34 56 78 (11 chiffres) # Espagne: 0034 6 12 34 56 78 (11 chiffres) pattern_international = r"^00(33|32|34)[1-9]\d{8}$" # Format national français: 01 23 45 67 89 (10 chiffres) pattern_national = r"^0[1-9]\d{8}$" return re.match(pattern_international, tel_clean) or re.match(pattern_national, tel_clean) def valider_date(date): """Valide le format jj/mm/aaaa""" pattern = r"^\d{2}/\d{2}/\d{4}$" return re.match(pattern, date) is not None def valider_annee(annee): """Valide une année (4 chiffres)""" return annee.isdigit() and len(annee) == 4 # --- Fonction pour récupérer les données lors du clic sur VALIDER --- def valider_donnees(): # 1. Récupération des données Lacivilite = entryCivilite.get() Lenom = entryNom.get() Leprenom = entryPrenom.get() Ladresse1 = entryAdresse1.get() Ladresse2 = entryAdresse2.get() Lecodepostal = entryCpostal.get() Laville = entryVille.get() Letelefixe = entryTphfixe.get() Leteleport = entryTphport.get() Lemail = entryMail.get() Ladhesion = entryAdhesion.get() Lanaissance = entryNaissance.get() Laprofession = entryProfession.get() Lalettre = entryLettre.get().upper() Lacotisation = entryCotisation.get() Larelance = entryRelance.get() Lestatut=entryStatut.get() Lobserv=entryObserv.get() # 2. Vérifications if Lenom == "Entrez votre nom ici" or Lenom == "": messagebox.showwarning("Attention", "Veuillez entrer un nom valide") entryNom.focus() return if Leprenom == "Entrez votre prénom ici" or Leprenom == "": messagebox.showwarning("Attention", "Veuillez entrer un prénom valide") entryPrenom.focus() return if Ladresse1 == "": messagebox.showwarning("Attention", "Veuillez entrer une adresse") entryAdresse1.focus() return if Lecodepostal == "": messagebox.showwarning("Attention", "Veuillez entrer un code postal") entryCpostal.focus() return if Laville == "": messagebox.showwarning("Attention", "Veuillez entrer une ville") entryVille.focus() return if Lemail != "" and not valider_email(Lemail): messagebox.showwarning("Attention", "Format d'email invalide") entryMail.focus() return # Validation des téléphones if Letelefixe != "" and not valider_telephone(Letelefixe): messagebox.showwarning( "Attention", "Format de téléphone fixe invalide\n\n" "Exemples acceptés:\n" " National: 01 23 45 67 89\n" " International:\n" " - France: 0033 1 23 45 67 89\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphfixe.focus() return if Leteleport != "" and not valider_telephone(Leteleport): messagebox.showwarning( "Attention", "Format de téléphone portable invalide\n\n" "Exemples acceptés:\n" " National: 06 12 34 56 78\n" " International:\n" " - France: 0033 6 12 34 56 78\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphport.focus() return if Lanaissance != "" and not valider_date(Lanaissance): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryNaissance.focus() return if Ladhesion != "" and not valider_date(Ladhesion): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryAdhesion.focus() return if Lacotisation != "" and not valider_annee(Lacotisation): messagebox.showwarning("Attention", "Année de cotisation invalide (4 chiffres)") entryCotisation.focus() return if Lalettre != "" and Lalettre not in ["O", "N"]: messagebox.showwarning("Attention", "Lettre info doit être O ou N") entryLettre.focus() return # 3. Afficher les données récupérées message = ( f"Création de {Lenom} {Leprenom}\n" f"Adresse 1: {Ladresse1}\n" f"Adresse 2: {Ladresse2}\n" f"CP/Ville: {Lecodepostal} {Laville}\n" f"Fixe/Port: {Letelefixe} / {Leteleport}\n" f"Mail: {Lemail}\n" f"Adhésion: {Ladhesion}, Naissance: {Lanaissance}, Profession: {Laprofession}\n" f"Lettre: {Lalettre}, Cotisation: {Lacotisation}, Relance: {Larelance}" ) messagebox.showinfo("Données saisies", message) messagebox.showinfo( "Succès", f"Adhérent: {Lenom} {Leprenom} enregistré avec succès." ) # 4. Effacer les champs après validation effacer_formulaire() def effacer_formulaire(): """Efface tous les champs du formulaire""" entryCivile.delete(0, tk.END) entryNom.delete(0, tk.END) entryPrenom.delete(0, tk.END) entryAdresse1.delete(0, tk.END) entryAdresse2.delete(0, tk.END) entryCpostal.delete(0, tk.END) entryVille.delete(0, tk.END) entryTphfixe.delete(0, tk.END) entryTphport.delete(0, tk.END) entryMail.delete(0, tk.END) entryAdhesion.delete(0, tk.END) entryNaissance.delete(0, tk.END) entryProfession.delete(0, tk.END) entryLettre.delete(0, tk.END) entryCotisation.delete(0, tk.END) entryRelance.delete(0, tk.END) entryStatut.delete(0, tk.END) entryObserv.delete(0, tk.END) # Réinitialiser les placeholders setup_placeholder(entryNom, "Entrez votre nom ici") setup_placeholder(entryPrenom, "Entrez votre prénom ici") def modifier_donnees(): """Fonction pour modifier un adhérent (à implémenter)""" messagebox.showinfo("Modifier", "Fonction MODIFIER à implémenter") def supprimer_donnees(): """Fonction pour supprimer un adhérent""" reponse = messagebox.askyesno( "Confirmation", "Voulez-vous vraiment supprimer cet adhérent ?" ) if reponse: effacer_formulaire() messagebox.showinfo("Suppression", "Adhérent supprimé") # Fonction helper pour créer des lignes avec label et entry def create_entry_row(parent, label_text, entry_width, row_num): """Crée une ligne avec label aligné à gauche et entry""" label = tk.Label(parent, text=label_text, anchor="w", bg="#f0f0f0") label.grid(row=row_num, column=0, sticky="w", padx=10, pady=5) entry = tk.Entry(parent, width=entry_width) entry.grid(row=row_num, column=1, sticky="w", padx=10, pady=5) return entry def create_tip_label(parent, text, row_num): """Crée un label de conseil en rouge""" tip = tk.Label(parent, text=text, fg="red", font=("Helvetica", 8), bg="#f0f0f0") tip.grid(row=row_num, column=2, sticky="w", padx=5, pady=5) # ----------------- Configuration de la fenêtre principale ----------------- app = tk.Tk() app.title("Assistance Humanitaire Internationale") app.geometry("1400x750") app.configure(bg="#e8f4f8") #Chargement de l'image photo = PhotoImage(file="D:\Program\IMAGE\LOGO AHI.png") #---- photo = photo.zoom(4, 4)# Double la taille de l'image photo = photo.subsample(27, 27)# ----- Réduit la taille de moitié (facteur 2) labelIcone= tk.Label(app, image=photo) labelIcone.pack(side="top", pady=(10)) # Polices titre_font = font.Font(family="Helvetica", size=20, weight="bold") section_font = font.Font(family="Helvetica", size=20, weight="bold") # ----------------- En-tête avec titre ----------------- frame_header = tk.Frame(app, bg="#4a90e2", height=80) frame_header.pack(fill="x", pady=(0, 15)) labelTitre = tk.Label( frame_header, text="SAISIE ADHÉRENTS", fg="white", bg="#4a90e2", font=titre_font, pady=15 ) labelTitre.pack() # afficher la date et éventuellement l'heure ---- from datetime import date Ladate=datetime.date labelDate = tk.Label(frame_header, text="Nous sommes le :",fg="white", bg="#4a90e2", font=titre_font, pady=15,) # et là je ne trouve pas... labelDate.pack() # ----------------- Conteneur principal avec padding ----------------- main_container = tk.Frame(app, bg="#e8f4f8") main_container.pack(fill="both", expand=True, padx=20, pady=10) # ----------------- Frame pour les deux colonnes ----------------- content_frame = tk.Frame(main_container, bg="#e8f4f8") content_frame.pack(fill="both", expand=True) # ========== COLONNE GAUCHE : COORDONNÉES ========== frame_coordonnees = tk.LabelFrame( content_frame, text="📋 Coordonnées", font=section_font, bg="#f0f0f0", relief="groove", borderwidth=2, padx=10, pady=10 ) frame_coordonnees.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) entryCivilite = create_entry_row(frame_coordonnees, "Civilité", 5, 0) create_tip_label(frame_coordonnees, "ATTENTION: utiliser M. ou Md ou Et", 0) # Nom et Prénom entryNom = create_entry_row(frame_coordonnees, "Nom :", 35, 1) setup_placeholder(entryNom, "Entrez votre nom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour noms composés", 1) entryPrenom = create_entry_row(frame_coordonnees, "Prénom :", 35, 2) setup_placeholder(entryPrenom, "Entrez votre prénom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour prénoms composés", 2) # Séparateur tk.Frame(frame_coordonnees, height=2, bg="#d0d0d0").grid(row=3, column=0, columnspan=3, sticky="ew", pady=10, padx=10) # Adresses entryAdresse1 = create_entry_row(frame_coordonnees, "Adresse 1 :", 45, 4) entryAdresse2 = create_entry_row(frame_coordonnees, "Adresse 2 :", 45, 5) # Code Postal et Ville sur la même ligne row_cp_ville = tk.Frame(frame_coordonnees, bg="#f0f0f0") row_cp_ville.grid(row=6, column=0, columnspan=3, sticky="w", pady=6) tk.Label(row_cp_ville, text="Code Postal :", anchor="w", bg="#f0f0f0").pack(side="left", padx=(10, 6)) entryCpostal = tk.Entry(row_cp_ville, width=10) entryCpostal.pack(side="left", padx=6) tk.Label(row_cp_ville, text="Ville :", anchor="w", bg="#f0f0f0").pack(side="left", padx=(20, 5)) entryVille = tk.Entry(row_cp_ville, width=30) entryVille.pack(side="left", padx=5) # Séparateur tk.Frame(frame_coordonnees, height=2, bg="#d0d0d0").grid(row=7, column=0, columnspan=3, sticky="ew", pady=10, padx=10) # Téléphones entryTphfixe = create_entry_row(frame_coordonnees, "Tél. fixe :", 20, 8) create_tip_label(frame_coordonnees, "Ex: 01 23 45 67 89 ou 0033 1 23 45 67 89", 8) entryTphport = create_entry_row(frame_coordonnees, "Tél. portable :", 20, 9) create_tip_label(frame_coordonnees, "Ex: 06 12 34 56 78 ou 0033 6 12 34 56 78", 9) # Mail entryMail = create_entry_row(frame_coordonnees, "Email :", 40, 10) # ========== COLONNE DROITE : INFORMATIONS ADHÉSION ========== frame_adhesion = tk.LabelFrame( content_frame, text="👤 Informations Adhésion", font=section_font, bg="#f0f0f0", relief="groove", borderwidth=2, padx=15, pady=15 ) frame_adhesion.grid(row=0, column=1, sticky="nsew", padx=(10, 0)) # Champs adhésion entryAdhesion = create_entry_row(frame_adhesion, "Date Adhésion :", 12, 0) entryNaissance = create_entry_row(frame_adhesion, "Date de naissance :", 12, 1) create_tip_label(frame_adhesion, "Format: jj/mm/aaaa", 1) entryProfession = create_entry_row(frame_adhesion, "Profession :", 30, 2) # Séparateur tk.Frame(frame_adhesion, height=2, bg="#d0d0d0").grid(row=3, column=0, columnspan=3, sticky="ew", pady=10, padx=10) entryLettre = create_entry_row(frame_adhesion, "Lettre info :", 3, 4) create_tip_label(frame_adhesion, "Tapez O ou N", 4) entryCotisation = create_entry_row(frame_adhesion, "Année Cotisation :", 5, 5) entryRelance = create_entry_row(frame_adhesion, "Relance Cotisation :", 3, 6) entryStatut = create_entry_row(frame_adhesion, "Statut", 40, 20) entryObserv = create_entry_row(frame_adhesion, "Observation", 40, 60) # Configuration des poids pour expansion content_frame.columnconfigure(0, weight=1) content_frame.columnconfigure(1, weight=1) # ----------------- Frame des Boutons ----------------- frame_boutons = tk.Frame(app, bg="#e8f4f8", pady=20) frame_boutons.pack(side="bottom", fill="x") # Style des boutons avec plus de padding button_style = {"font": ("Helvetica", 10, "bold"), "padx": 20, "pady": 8, "width": 12} Button0 = tk.Button( frame_boutons, text="✓ VALIDER", command=valider_donnees, bg="#90EE90", **button_style ) Button0.pack(side="left", padx=15) Button1 = tk.Button( frame_boutons, text="✎ MODIFIER", command=modifier_donnees, bg="#87CEEB", **button_style ) Button1.pack(side="left", padx=15) Button2 = tk.Button( frame_boutons, text="✗ SUPPRIMER", command=supprimer_donnees, bg="#FFB6C1", **button_style ) Button2.pack(side="left", padx=15) Button3 = tk.Button( frame_boutons, text="⏻ QUITTER", command=app.quit, bg="#FFA07A", **button_style ) Button3.pack(side="right", padx=15) Button4 = tk.Button( frame_boutons, text="✎ ENVOYER UN MAIL", #command=app.quit, bg="#F5F5DC", **button_style ) Button4.pack(side="left", padx=15) app.mainloop()
Je voudrais intégrer la date système en haut sous "SAISIE ADHERENTS" mais je n'y arrive pas...
Est-il possible d'agrandir les caractères dans les Labels et le Entry car là c'est très petit pour des yeux de retraités.
Bonne soirée.
Yves
Il faut que ça reste cohérent au niveau de tailles de police,
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465 import tkinter as tk from tkinter import messagebox from tkinter import font import tkinter.font as font from tkinter import * import re import datetime # --- Gestion des placeholders --- def setup_placeholder(entry, placeholder_text): """Configure un placeholder pour un champ Entry""" entry.insert(0, placeholder_text) entry.config(fg="grey") def on_focus_in(event): if entry.get() == placeholder_text: entry.delete(0, tk.END) entry.config(fg="black") def on_focus_out(event): if entry.get() == "": entry.insert(0, placeholder_text) entry.config(fg="grey") entry.bind("<FocusIn>", on_focus_in) entry.bind("<FocusOut>", on_focus_out) # --- Fonctions de validation --- def valider_email(email): """Valide le format d'un email""" pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" return re.match(pattern, email) is not None def valider_telephone(telephone): """Valide le format d'un numéro de téléphone (FR, BE, ES)""" if telephone == "": # Champ optionnel return True # Retirer les espaces pour la validation tel_clean = telephone.replace(" ", "") # Format avec indicatif international: 0033, 0032, 0034 pattern_international = r"^00(33|32|34)[1-9]\d{8}$" # Format national français: 01 23 45 67 89 (10 chiffres) pattern_national = r"^0[1-9]\d{8}$" return re.match(pattern_international, tel_clean) or re.match(pattern_national, tel_clean) def valider_date(date): """Valide le format jj/mm/aaaa""" pattern = r"^\d{2}/\d{2}/\d{4}$" return re.match(pattern, date) is not None def valider_annee(annee): """Valide une année (4 chiffres)""" return annee.isdigit() and len(annee) == 4 # --- Fonction pour récupérer les données lors du clic sur VALIDER --- def valider_donnees(): # 1. Récupération des données Lacivilite = entryCivilite.get() Lenom = entryNom.get() Leprenom = entryPrenom.get() Ladresse1 = entryAdresse1.get() Ladresse2 = entryAdresse2.get() Lecodepostal = entryCpostal.get() Laville = entryVille.get() Letelefixe = entryTphfixe.get() Leteleport = entryTphport.get() Lemail = entryMail.get() Ladhesion = entryAdhesion.get() Lanaissance = entryNaissance.get() Laprofession = entryProfession.get() Lalettre = entryLettre.get().upper() Lacotisation = entryCotisation.get() Larelance = entryRelance.get() Lestatut=entryStatut.get() Lobserv=entryObserv.get() # 2. Vérifications if Lenom == "Entrez votre nom ici" or Lenom == "": messagebox.showwarning("Attention", "Veuillez entrer un nom valide") entryNom.focus() return if Leprenom == "Entrez votre prénom ici" or Leprenom == "": messagebox.showwarning("Attention", "Veuillez entrer un prénom valide") entryPrenom.focus() return if Ladresse1 == "": messagebox.showwarning("Attention", "Veuillez entrer une adresse") entryAdresse1.focus() return if Lecodepostal == "": messagebox.showwarning("Attention", "Veuillez entrer un code postal") entryCpostal.focus() return if Laville == "": messagebox.showwarning("Attention", "Veuillez entrer une ville") entryVille.focus() return if Lemail != "" and not valider_email(Lemail): messagebox.showwarning("Attention", "Format d'email invalide") entryMail.focus() return # Validation des téléphones if Letelefixe != "" and not valider_telephone(Letelefixe): messagebox.showwarning( "Attention", "Format de téléphone fixe invalide\n\n" "Exemples acceptés:\n" " National: 01 23 45 67 89\n" " International:\n" " - France: 0033 1 23 45 67 89\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphfixe.focus() return if Leteleport != "" and not valider_telephone(Leteleport): messagebox.showwarning( "Attention", "Format de téléphone portable invalide\n\n" "Exemples acceptés:\n" " National: 06 12 34 56 78\n" " International:\n" " - France: 0033 6 12 34 56 78\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphport.focus() return if Lanaissance != "" and not valider_date(Lanaissance): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryNaissance.focus() return if Ladhesion != "" and not valider_date(Ladhesion): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryAdhesion.focus() return if Lacotisation != "" and not valider_annee(Lacotisation): messagebox.showwarning("Attention", "Année de cotisation invalide (4 chiffres)") entryCotisation.focus() return if Lalettre != "" and Lalettre not in ["O", "N"]: messagebox.showwarning("Attention", "Lettre info doit être O ou N") entryLettre.focus() return # 3. Afficher les données récupérées message = ( f"Création de {Lenom} {Leprenom}\n" f"Adresse 1: {Ladresse1}\n" f"Adresse 2: {Ladresse2}\n" f"CP/Ville: {Lecodepostal} {Laville}\n" f"Fixe/Port: {Letelefixe} / {Leteleport}\n" f"Mail: {Lemail}\n" f"Adhésion: {Ladhesion}, Naissance: {Lanaissance}, Profession: {Laprofession}\n" f"Lettre: {Lalettre}, Cotisation: {Lacotisation}, Relance: {Larelance}" ) messagebox.showinfo("Données saisies", message) messagebox.showinfo( "Succès", f"Adhérent: {Lenom} {Leprenom} enregistré avec succès." ) # 4. Effacer les champs après validation effacer_formulaire() def effacer_formulaire(): """Efface tous les champs du formulaire""" entryCivilite.delete(0, tk.END) entryNom.delete(0, tk.END) entryPrenom.delete(0, tk.END) entryAdresse1.delete(0, tk.END) entryAdresse2.delete(0, tk.END) entryCpostal.delete(0, tk.END) entryVille.delete(0, tk.END) entryTphfixe.delete(0, tk.END) entryTphport.delete(0, tk.END) entryMail.delete(0, tk.END) entryAdhesion.delete(0, tk.END) entryNaissance.delete(0, tk.END) entryProfession.delete(0, tk.END) entryLettre.delete(0, tk.END) entryCotisation.delete(0, tk.END) entryRelance.delete(0, tk.END) entryStatut.delete(0, tk.END) entryObserv.delete(0, tk.END) # Réinitialiser les placeholders setup_placeholder(entryNom, "Entrez votre nom ici") setup_placeholder(entryPrenom, "Entrez votre prénom ici") def modifier_donnees(): """Fonction pour modifier un adhérent (à implémenter)""" messagebox.showinfo("Modifier", "Fonction MODIFIER à implémenter") def supprimer_donnees(): """Fonction pour supprimer un adhérent""" reponse = messagebox.askyesno( "Confirmation", "Voulez-vous vraiment supprimer cet adhérent ?" ) if reponse: effacer_formulaire() messagebox.showinfo("Suppression", "Adhérent supprimé") # Fonction helper pour créer des lignes avec label et entry (avec polices agrandies) def create_entry_row(parent, label_text, entry_width, row_num): """Crée une ligne avec label aligné à gauche et entry avec police agrandie""" # Label avec police de taille 12 pour meilleure lisibilité label = tk.Label(parent, text=label_text, anchor="w", bg="#f0f0f0", font=("Helvetica", 12)) label.grid(row=row_num, column=0, sticky="w", padx=10, pady=5) # Entry avec police de taille 12 et hauteur adaptée entry = tk.Entry(parent, width=entry_width, font=("Helvetica", 12)) entry.grid(row=row_num, column=1, sticky="w", padx=10, pady=5) return entry def create_tip_label(parent, text, row_num): """Crée un label de conseil en rouge avec police agrandie""" tip = tk.Label(parent, text=text, fg="red", font=("Helvetica", 10), bg="#f0f0f0") tip.grid(row=row_num, column=2, sticky="w", padx=5, pady=5) # ----------------- Configuration de la fenêtre principale ----------------- app = tk.Tk() app.title("Assistance Humanitaire Internationale") app.geometry("1400x750") app.configure(bg="#e8f4f8") # Chargement de l'image (commenté pour éviter les erreurs si le fichier n'existe pas) # Vous pouvez décommenter ces lignes si votre image existe à cet emplacement # photo = PhotoImage(file="D:\Program\IMAGE\LOGO AHI.png") # photo = photo.subsample(27, 27) # labelIcone= tk.Label(app, image=photo) # labelIcone.pack(side="top", pady=(10)) # Polices titre_font = font.Font(family="Helvetica", size=20, weight="bold") section_font = font.Font(family="Helvetica", size=16, weight="bold") # Légèrement réduit pour l'équilibre # ----------------- En-tête avec titre ----------------- frame_header = tk.Frame(app, bg="#4a90e2", height=80) frame_header.pack(fill="x", pady=(0, 15)) labelTitre = tk.Label( frame_header, text="SAISIE ADHÉRENTS", fg="white", bg="#4a90e2", font=titre_font, pady=15 ) labelTitre.pack() # ======= CORRECTION: Affichage de la date système ======= # On récupère la date actuelle avec datetime.date.today() # puis on la formate en français avec strftime() date_actuelle = datetime.date.today() # Formatage: %A = jour de la semaine, %d = jour, %B = mois, %Y = année # Note: par défaut, strftime() utilise les noms anglais. Pour le français, # vous pouvez utiliser locale ou formater manuellement date_formatee = date_actuelle.strftime("%d/%m/%Y") # Alternative avec jour de la semaine en français (méthode simple) jours_fr = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"] mois_fr = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] jour_semaine = jours_fr[date_actuelle.weekday()] texte_date = f"Nous sommes le {jour_semaine} {date_actuelle.day} {mois_fr[date_actuelle.month-1]} {date_actuelle.year}" # Création du label avec la date formatée et police agrandie labelDate = tk.Label( frame_header, text=texte_date, fg="white", bg="#4a90e2", font=("Helvetica", 14), # Police agrandie pour meilleure lisibilité pady=5 ) labelDate.pack() # ----------------- Conteneur principal avec padding ----------------- main_container = tk.Frame(app, bg="#e8f4f8") main_container.pack(fill="both", expand=True, padx=20, pady=10) # ----------------- Frame pour les deux colonnes ----------------- content_frame = tk.Frame(main_container, bg="#e8f4f8") content_frame.pack(fill="both", expand=True) # ========== COLONNE GAUCHE : COORDONNÉES ========== frame_coordonnees = tk.LabelFrame( content_frame, text="📋 Coordonnées", font=section_font, bg="#f0f0f0", relief="groove", borderwidth=2, padx=10, pady=10 ) frame_coordonnees.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) entryCivilite = create_entry_row(frame_coordonnees, "Civilité", 5, 0) create_tip_label(frame_coordonnees, "ATTENTION: utiliser M. ou Md ou Et", 0) # Nom et Prénom entryNom = create_entry_row(frame_coordonnees, "Nom :", 35, 1) setup_placeholder(entryNom, "Entrez votre nom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour noms composés", 1) entryPrenom = create_entry_row(frame_coordonnees, "Prénom :", 35, 2) setup_placeholder(entryPrenom, "Entrez votre prénom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour prénoms composés", 2) # Séparateur tk.Frame(frame_coordonnees, height=2, bg="#d0d0d0").grid(row=3, column=0, columnspan=3, sticky="ew", pady=10, padx=10) # Adresses entryAdresse1 = create_entry_row(frame_coordonnees, "Adresse 1 :", 45, 4) entryAdresse2 = create_entry_row(frame_coordonnees, "Adresse 2 :", 45, 5) # Code Postal et Ville sur la même ligne (avec polices agrandies) row_cp_ville = tk.Frame(frame_coordonnees, bg="#f0f0f0") row_cp_ville.grid(row=6, column=0, columnspan=3, sticky="w", pady=6) tk.Label(row_cp_ville, text="Code Postal :", anchor="w", bg="#f0f0f0", font=("Helvetica", 12)).pack(side="left", padx=(10, 6)) entryCpostal = tk.Entry(row_cp_ville, width=10, font=("Helvetica", 12)) entryCpostal.pack(side="left", padx=6) tk.Label(row_cp_ville, text="Ville :", anchor="w", bg="#f0f0f0", font=("Helvetica", 12)).pack(side="left", padx=(20, 5)) entryVille = tk.Entry(row_cp_ville, width=30, font=("Helvetica", 12)) entryVille.pack(side="left", padx=5) # Séparateur tk.Frame(frame_coordonnees, height=2, bg="#d0d0d0").grid(row=7, column=0, columnspan=3, sticky="ew", pady=10, padx=10) # Téléphones entryTphfixe = create_entry_row(frame_coordonnees, "Tél. fixe :", 20, 8) create_tip_label(frame_coordonnees, "Ex: 01 23 45 67 89 ou 0033 1 23 45 67 89", 8) entryTphport = create_entry_row(frame_coordonnees, "Tél. portable :", 20, 9) create_tip_label(frame_coordonnees, "Ex: 06 12 34 56 78 ou 0033 6 12 34 56 78", 9) # Mail entryMail = create_entry_row(frame_coordonnees, "Email :", 40, 10) # ========== COLONNE DROITE : INFORMATIONS ADHÉSION ========== frame_adhesion = tk.LabelFrame( content_frame, text="👤 Informations Adhésion", font=section_font, bg="#f0f0f0", relief="groove", borderwidth=2, padx=15, pady=15 ) frame_adhesion.grid(row=0, column=1, sticky="nsew", padx=(10, 0)) # Champs adhésion entryAdhesion = create_entry_row(frame_adhesion, "Date Adhésion :", 12, 0) entryNaissance = create_entry_row(frame_adhesion, "Date de naissance :", 12, 1) create_tip_label(frame_adhesion, "Format: jj/mm/aaaa", 1) entryProfession = create_entry_row(frame_adhesion, "Profession :", 30, 2) # Séparateur tk.Frame(frame_adhesion, height=2, bg="#d0d0d0").grid(row=3, column=0, columnspan=3, sticky="ew", pady=10, padx=10) entryLettre = create_entry_row(frame_adhesion, "Lettre info :", 3, 4) create_tip_label(frame_adhesion, "Tapez O ou N", 4) entryCotisation = create_entry_row(frame_adhesion, "Année Cotisation :", 5, 5) entryRelance = create_entry_row(frame_adhesion, "Relance Cotisation :", 3, 6) entryStatut = create_entry_row(frame_adhesion, "Statut", 40, 20) entryObserv = create_entry_row(frame_adhesion, "Observation", 40, 60) # Configuration des poids pour expansion content_frame.columnconfigure(0, weight=1) content_frame.columnconfigure(1, weight=1) # ----------------- Frame des Boutons ----------------- frame_boutons = tk.Frame(app, bg="#e8f4f8", pady=20) frame_boutons.pack(side="bottom", fill="x") # Style des boutons avec police agrandie button_style = {"font": ("Helvetica", 12, "bold"), "padx": 20, "pady": 10, "width": 15} Button0 = tk.Button( frame_boutons, text="✓ VALIDER", command=valider_donnees, bg="#90EE90", **button_style ) Button0.pack(side="left", padx=15) Button1 = tk.Button( frame_boutons, text="✎ MODIFIER", command=modifier_donnees, bg="#87CEEB", **button_style ) Button1.pack(side="left", padx=15) Button2 = tk.Button( frame_boutons, text="✗ SUPPRIMER", command=supprimer_donnees, bg="#FFB6C1", **button_style ) Button2.pack(side="left", padx=15) Button3 = tk.Button( frame_boutons, text="⏻ QUITTER", command=app.quit, bg="#FFA07A", **button_style ) Button3.pack(side="right", padx=15) Button4 = tk.Button( frame_boutons, text="✎ ENVOYER UN MAIL", #command=app.quit, bg="#F5F5DC", **button_style ) Button4.pack(side="left", padx=15) app.mainloop()
Bon mon premier formulaire est au point. Il me reste à mettre en route les bouton Valider, Supprimer, Modifier, ...
Ce qui n'est pas gagné.
D'abord pour que je comprenne est-il possible de travailler à partir de Python sur un classeur Excel 2007 ?
Ensuite si je construit:
un formulaire "MENU DE DEPART" avec des bouton me renvoyant à
Sous menu EFFECTIF
Saisie des effectifs,
Listing des effectifs, ....
Idem pour
Sous menu GESTION
saisie comptable
Listing journal comptable
Docs Bilan résutat ....
et ainsi de suite
comment je peux lancer tout cela ?
Que de questions ...
Merci tout de même pour votre "formation".
Bonne nuit.
Yves
Oui bien sûr, vous pouvez utiliser le module openpyxl par exemple, mais il y en a d'autres...Citation:
D'abord pour que je comprenne est-il possible de travailler à partir de Python sur un classeur Excel 2007 ?
En faisant le même travail que précédemment, posez vos boutons, vous avez même la possibilité de créer des menus dans l'interface...Citation:
Ensuite si je construit:
un formulaire "MENU DE DEPART" avec des bouton me renvoyant à
Sous menu EFFECTIF
Saisie des effectifs,
Listing des effectifs, ....
Idem pour
Sous menu GESTION
saisie comptable
Listing journal comptable
Docs Bilan résutat ....
et ainsi de suite
comment je peux lancer tout cela ?
Après, personne ne développera tout ceci tant qu'il n'y aura pas un peu de rangement dans votre code actuel :?
Vous pouvez déjà faire une séparation des responsabilités entre le code "interface" et le code "fonctionnalités", l'un ne gérant que les emplacements widgets alors que l'autre gère les actions de vos widgets (lors d'un appui sur un bouton par exemple). Tant que vous ne comprendrez pas un minimum comment tout ça fonctionne, la suite reste d'être difficile.
Je ne serai pas toujours là, et si un moment, quelque chose pète dans le fonctionnement général de votre code alors vous serez perdu, et ce n'est pas l'objectif !
Vous devrez gérer cela seul quand ça arrivera...
Bonsoir,
Merci de votre réponse. Evidemment je ne vous demanderai pas de m'aider à réécrire tout mon programme. Mais juste quelques passage obligés pour comprendre tout cela. Enfin je ne veux en aucun cas vous obliger et comprendrai très bien si vous ne me répondiez plus. Le but pour moi est bien de comprendre Python comme je suis arrivé à comprendre VBA en d'autres temps. Mais à 72 ans cela demande un peu plus d'efforts.
Imaginons que j'arrive à écrire tous mes formulaires EFFECTIFS, GESTION.
Je voudrai comprendre comment on passe de plusieurs modules.py à un exécutable global installable sur plusieurs machines. Dans VBA les USERFORMS sont tous regroupés dans un tableur Excel mais là ?
Quelle est la command qui permet de lancer un fichier .py extérieur au formulaire en cours ? En VBA c'est ceci :
Private Sub CommandButton15_Click() '---- INTEGRATION EFFECTIFS ------
Load UserForm15
UserForm15.Show
End Sub
J'ai essayé d'installer le module get-pip.py qui a eu l'air de s'installer mais pourtant dans le formulaire la ligne pour charger import openpyxl .... provoque une erreur.
Bon, je ne vais pas vous embéter plus longtemps après tout rien ne me presse puisque je suis resté sur windows 10 et excel 2007.
Bonne soirée
Yves
Vous avez pyinstaller qui permet cela.Citation:
Je voudrai comprendre comment on passe de plusieurs modules.py à un exécutable global installable sur plusieurs machines.
En VBA, vous lancez d'autres formulaires ou scripts internes à l'application Office. En Python, si vous voulez lancer un fichier .py externe à votre script principal (un peu comme un autre programme ou un processus séparé), vous devez utiliser des outils pour exécuter des commandes du système d'exploitation.Citation:
Quelle est la command qui permet de lancer un fichier .py extérieur au formulaire en cours ? En VBA c'est ceci :
Private Sub CommandButton15_Click() '---- INTEGRATION EFFECTIFS ------
Load UserForm15
UserForm15.Show
End Sub
La commande la plus courante et la plus recommandée pour cela est d'utiliser le module standard subprocess.
Quand vous installez un paquet (comme openpyxl) avec pip, il est installé dans le dossier de l'interpréteur Python que pip utilise. Si le formulaire que vous exécutez utilise un autre interpréteur Python ou un environnement virtuel différent, il ne trouvera pas le module.Citation:
J'ai essayé d'installer le module get-pip.py qui a eu l'air de s'installer mais pourtant dans le formulaire la ligne pour charger import openpyxl .... provoque une erreur.
Il est possible que vous ayez plusieurs versions de Python (ex. Python 3.12 et 3.14) sur votre machine ou plusieurs environnements virtuels avec la même version.
Bonsoir et grand merci.
je garde précieusement ces informations...
Pour l'instant je me penche sur la construction de mon menu de départ.
J'ai fait un truc sympa avec l'aide au départ de l'AI.
Je ne suis pas loin du but. Cependant il a un truc qui m'énerve !
Quand j'appuie sur un bouton pour ouvrir de S/menu correspondant j'ai tout qui se décale vers le bas, grrrrrrr
Comme si un espace se créé en haut.
Je ne comprend pas pourquoi.
Une fois ce MENU fait, je vais pouvoir travailler sur lancer saisi.py à partir du MENU.
Si vous avez encore la patience de regarder cela, je sui preneur... Lol
Pièce jointe 671462Pièce jointe 671463Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import tkinter as tk from tkinter import ttk, messagebox from tkinter import font import tkinter.font as font from tkinter import * import re import datetime from tkinter import Tk, Button # ============================================================ # Fonctions (placeholders) # ============================================================ def action(message: str) -> None: messagebox.showinfo("Information", f"Fonction à implémenter :\n{message}") # ============================================================ # Construction dynamique des menus # ============================================================ menus = { "EFFECTIF": [ ("Création & M à J", lambda: action("Création & mise à jour des effectifs")), ("Listing des effectifs", lambda: action("Listing des effectifs")), ("Relance cotisation", lambda: action("Relance cotisation")), ("Anniversaires", lambda: action("Anniversaires")), ("Lettre infos", lambda: action("Lettre d'information")), ], "GESTION COMPTABLE": [ ("Saisie comptable", lambda: action("Saisie comptable")), ("Journal des écritures", lambda: action("Journal des écritures")), ("Gestion des CERFAs", "CERFA"), ], "RESULTATS": [ ("Tableau de bord", lambda: action("Tableau de bord")), ("Résultat annuel", lambda: action("Résultat annuel")), ("Bilan annuel", lambda: action("Bilan annuel")), ], "PROJETS": [ ("Création projet", lambda: action("Création projet")), ("Suivi de gestion des projets", lambda: action("Suivi gestion des projets")), ("Listing des projets", lambda: action("Listing des projets")), ], "PARAMETRES": [ ("Natures comptables", lambda: action("Natures comptables")), ("Comptes bancaires", lambda: action("Comptes bancaires")), ] } cerfa_submenu = [ ("Générer liste CERFA", lambda: action("Génération liste CERFA")), ("Listing des CERFA N-1", lambda: action("Listing des CERFA N-1")), ("Envoi des CERFA", lambda: action("Envoi des CERFA")), ] # ============================================================ # Interface Tkinter Fenêtre ouverture initiale # ============================================================ root = tk.Tk() root.title("GESTION A.H.I") root.geometry("1400x800+10+10") # centrer la fenetre #app.geometry("1400x800") root.configure(bg="#e8f4f8") #Chargement de l'image photo = PhotoImage(file="D:\Program\IMAGE\LOGO AHI.png") #---- photo = photo.zoom(4, 4) # ----- Double la taille de l'image photo = photo.subsample(27, 27) # ----- Réduit la taille de moitié (facteur 2) labelIcone= tk.Label(root, image=photo) labelIcone.pack(side="top", pady=(50)) root.resizable(False, False) frame_header = tk.Frame(root, bg="#4a90e2", height=80) frame_header.pack(fill="x", pady=(0, 15)) # ======= CORRECTION: Affichage de la date système ======= # On récupère la date actuelle avec datetime.date.today() # puis on la formate en français avec strftime() date_actuelle = datetime.date.today() # Formatage: %A = jour de la semaine, %d = jour, %B = mois, %Y = année # Note: par défaut, strftime() utilise les noms anglais. Pour le français, # vous pouvez utiliser locale ou formater manuellement date_formatee = date_actuelle.strftime("%d/%m/%Y") # Alternative avec jour de la semaine en français (méthode simple) jours_fr = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"] mois_fr = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] jour_semaine = jours_fr[date_actuelle.weekday()] texte_date = f"Nous sommes le {jour_semaine} {date_actuelle.day} {mois_fr[date_actuelle.month-1]} {date_actuelle.year}" # Création du label avec la date formatée et police agrandie labelDate = tk.Label( frame_header, text=texte_date, fg="white", bg="#4a90e2", font=("Helvetica", 14), # Police agrandie pour meilleure lisibilité pady=5 ) labelDate.pack() # Cadre principal # =============================== frame_submenus = {} # Dictionnaire global frame_top = ttk.Frame(root) frame_top.pack(fill="x") frame_subbar = ttk.Frame(root) frame_subbar.pack(fill="x") frame_nav = tk.Frame(root, pady=10) frame_nav.pack(fill="x") frame_menu = tk.Frame(root, bg="#F0FFFF", padx=20, pady=20) frame_menu.pack(fill="both", expand=True) label_title = ttk.Label(root, text="GESTION A.H.I - (c): Y.Fouquet 2025", font=("Arial", 20, "bold")) label_title.place(relx=0.5, y=10, anchor="n") # Style des boutons et label avec police agrandie button_style = {"font": ("Helvetica", 14, "bold"), "padx": 10, "pady": 10, "width": 10} label_style = {"font": ("Helvetica", 14, "bold"), "padx": 10, "pady": 10, "width": 30} # ============================================================ # Fonction pour afficher les sous-menus # ============================================================ from tkinter import ttk # Création de styles différents pour les boutons style = ttk.Style() style.theme_use("default") couleurs = { "Création & M à J": "#98FB98", "Listing des effectifs": "#98FB98", "Relance cotisation": "#98FB98", "Anniversaires": "#98FB98", "Lettre infos": "#98FB98", "Saisie comptable": "#9FB8F4", "Journal des écritures": "#9FB8F4", "Gestion des CERFAs": "#B6D3E6", "Générer liste CERFA": "#B6D3E6", "Listing des CERFA N-1": "#B6D3E6", "Envoi des CERFA": "#B6D3E6", "Tableau de bord": "#E1C1BB", "Résultat annuel": "#E1C1BB", "Bilan annuel": "#E1C1BB", "Création projet": "#CCCCFF", "Suivi de gestion des projets": "#CCCCFF", "Listing des projets": "#CCCCFF", "Natures comptables": "#CECECE", "Comptes bancaires": "#CECECE", "Retour": "#FFA07A", } #================================== #Style pour S/Menus #================================== style_map = {} for nom, couleur in couleurs.items(): style_name = f"{nom}.TButton" style_map[nom] = style_name style.configure( style_name, background=couleur, foreground="black", padding=6, font=("Arial", 12, "bold") ) style.map(style_name, background=[("active", couleur)]) #================================== #Création des Menu et S/Menus #================================== def creer_menus_principaux(): for menu_nom in menus.keys(): # Cadre pour chaque menu + sous menu container = ttk.Frame(frame_top) container.pack(side="left", padx=5) # Bouton principal ttk.Button( container, text=menu_nom, style=style_map.get(menu_nom, None), command=lambda m=menu_nom: afficher_sous_menu(m) ).pack(fill="x") # Frame pour le sous-menu sm = ttk.Frame(container) sm.pack() frame_submenus[menu_nom] = sm # IMPORTANT : créer ici le frame du sous-menu sm = ttk.Frame(frame_subbar) frame_submenus[menu_nom] = sm #--------------------------------------- def afficher_sous_menu(nom: str) -> None: # Effacer uniquement le sous-menu du bouton cliqué for widget in frame_submenus[nom].winfo_children(): widget.destroy() # Remplir le sous-menu for texte, action_ref in menus[nom]: style_btn = style_map.get(texte, None) if action_ref == "CERFA": cmd = afficher_menu_cerfa else: cmd = action_ref ttk.Button( frame_submenus[nom], text=texte, style=style_btn, command=cmd ).pack(anchor="w", pady=2, fill="x") #--------------------------------------- def afficher_menu_cerfa(): sm = frame_submenus["GESTION COMPTABLE"] for widget in sm.winfo_children(): widget.destroy() ttk.Label(sm, text="GESTION CERFA", font=("Arial", 12, "bold")).pack(anchor="w", pady=4) for texte, cmd in cerfa_submenu: ttk.Button(sm, text=texte, style=style_map[texte], command=cmd)\ .pack(anchor="w", pady=2, fill="x") ttk.Button(sm, text="Retour", style=style_map["Retour"], command=lambda: afficher_sous_menu("GESTION COMPTABLE"))\ .pack(anchor="w", pady=8) # ============================================================ # Création de la barre de navigation horizontale # ============================================================ for menu_nom in menus.keys(): cont = ttk.Frame(frame_top) cont.pack(side="left", padx=5) ttk.Button(cont, text=menu_nom, style=style_map["Retour"], command=lambda m=menu_nom: afficher_sous_menu(m))\ .pack(fill="x") sm = ttk.Frame(cont) sm.pack() frame_submenus[menu_nom] = sm # ============================================================ # Création du bouton QUITTER # ============================================================ Button1 = tk.Button( text="⏻ QUITTER", command=root.quit, bg="#FC5D5D", **button_style ) Button1.pack(side="right", padx=10) #--------------------------------------- root.mainloop()
Pourquoi dans cette fonction de trie, la date se met dans la cellulke au format anglais avec l'heure alors que je ne veux garder le format initial jj/mm/aa.
Merci de votre aide.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 def trier_feuille(): fichier = "Donnees.xlsm" feuille = "Effectif" print("🔄 Début du tri...") # Charger la feuille en gardant les dates en datetime df = pd.read_excel(fichier, sheet_name=feuille) # Trier sur la colonne B (index 1) df_trie = df.sort_values(by=df.columns[1], ascending=True) # Reformater les colonnes datetime pour garder format jj/mm/aaaa à l'export for col in df_trie.select_dtypes(include=['datetime']): df_trie[col] = df_trie[col].dt.strftime('%d/%m/%Y') # Réécrire la feuille triée dans le fichier sans casser les macros with pd.ExcelWriter(fichier, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer: df_trie.to_excel(writer, sheet_name=feuille, index=False)
Le problème vient du fait que lorsque vous utilisez strftime('%d/%m/%Y'), vous convertissez les dates en chaînes de caractères. Excel ne les reconnaît plus comme des dates, donc il les affiche telles quelles (et parfois les interprète à sa façon).
Ne pas convertir en chaînes, mais conserver le format datetime et laisser Excel gérer l'affichage
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 def trier_feuille(): fichier = "Donnees.xlsm" feuille = "Effectif" print("🔄 Début du tri...") # Charger la feuille en gardant les dates en datetime df = pd.read_excel(fichier, sheet_name=feuille) # Trier sur la colonne B (index 1) df_trie = df.sort_values(by=df.columns[1], ascending=True) # Réécrire la feuille triée dans le fichier sans casser les macros with pd.ExcelWriter(fichier, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer: df_trie.to_excel(writer, sheet_name=feuille, index=False) # Appliquer le format de date français aux colonnes datetime workbook = writer.book worksheet = writer.sheets[feuille] for idx, col in enumerate(df_trie.columns, start=1): if pd.api.types.is_datetime64_any_dtype(df_trie[col]): for row in range(2, len(df_trie) + 2): # +2 car commence à 1 et header cell = worksheet.cell(row=row, column=idx) cell.number_format = 'DD/MM/YYYY'
En fait je récupére dans une variable le résultat de la saisie
et je me sers de cette variable pour garnir la cellule de mon tableau ExcelCode:
1
2 Ladhesion = entryAdhesion.get()
mais si j'ai tout compris ce que j'intègre est du String et donc lors du trie la colonne ayant une cellule "non date" toutes les cellules sont transformées en String.Code:
1
2
3 sheet.cell(row=x, column=col).value = Ladhesion col = 5
Il me faudrait trouver comment transformer "Ladhesion" en DATE avant de l'intégrer au tableau Excel.....
En fait si je trie sans faire de saisie cela fonctionne bien. car toutes les cellules de la colonne sont bien au format DATE. Dés que je saisie cela met la zoubia... LolCode:
1
2
3
4 #Ladhesion doit devenir DATE... à partir de import datetime sheet.cell(row=x, column=col).value = Ladhesion col = 5
Ok
Voici comment convertir ta saisie texte en vraie date Python avant de l'insérer dans Excel.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 from datetime import datetime # Récupérer la saisie Ladhesion = entryAdhesion.get() # Format attendu : "25/11/2024" # Convertir en objet datetime Python try: date_adhesion = datetime.strptime(Ladhesion, '%d/%m/%Y') sheet.cell(row=x, column=col).value = date_adhesion sheet.cell(row=x, column=col).number_format = 'DD/MM/YYYY' except ValueError: print(f"Format de date invalide : {Ladhesion}") # Gérer l'erreur selon ton besoin
J'ai solutionné mon problème de trie en passant la saisie entryNaissance au format Date à la saisie
mais pour faire cela j'ai du importerCode:
1
2
3
4 #==== trouver la solution changer string saisie en format Date ============= Lanaissance = entryNaissance.get() Lanaissance2 = datetime.strptime(Lanaissance, "%d/%m/%Y").date()
qui rentre en conflit avec " import datetime".Code:
1
2
3 from datetime import date, datetime, timedelta #import datetime
Pour faire mes essaie j'ai donc était obligé d'isoler toute les lignes qui affichent la Date en haut du menu,
qui déclenchaient des erreurs du fait de l'absence de import datetime.
Si je laisse "import datetime" c'est le trie qui marche plusCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# ======= CORRECTION: Affichage de la date système ======= #!!!!! [/B]date_actuelle = datetime.date.today() #!!!! date_formatee = date_actuelle.strftime("%d/%m/%Y") # Alternative avec jour de la semaine en français (méthode simple) jours_fr = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"] mois_fr = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] #!!!!jour_semaine = jours_fr[date_actuelle.weekday()] #!!! texte_date = f"Nous sommes le {jour_semaine} {date_actuelle.day} {mois_fr[date_actuelle.month-1]} {date_actuelle.year}" labelDate = tk.Label( frame_header, #text=texte_date, fg="white", bg="#4a90e2", font=("Helvetica", 14), # Police agrandie pour meilleure lisibilité pady=5 ) labelDate.pack()
si je laisse "from datetime import date, datetime, timedelta" c'est la date du menu qui marche plus...
Je ne peux laisser les deux.
Ça serait plus simple si vous envoyez un code testable et qui reproduit le problème.
Votre problème est courant et connu...
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704 import tkinter as tk from tkinter import messagebox from tkinter import font import tkinter.font as font from tkinter import * import re from datetime import date, datetime, timedelta #-- celle ci est nécessaire à a fonction de trie #import datetime # celle-ci est nécessaire à l'affichage de la date en entête du menu (J'ai isolé les lignes de la date) #C'est l que j'ai problème, je ne peux laisser ces deux lignes actives from openpyxl import Workbook, load_workbook import os import logging import pandas as pd from openpyxl.utils import get_column_letter import pandas as pd from openpyxl.styles import NamedStyle x = 0 #========================== AFFICHER CELLULES EXCEL ================================================ #----- Iterate through rows #for row in sheet.iter_rows(min_row=1, max_row=5, values_only=True): # print(row) # Returns a tuple of values #----- Iterate through columns #for column in sheet.iter_cols(min_col=1, max_col=3, values_only=True): # print(column) # Returns a tuple of values #----- Access a range of cells #cell_range = sheet["A1:C5"] #for row in cell_range: # for cell in row: # print(cell.value) #============================================================================== # --- Gestion des placeholders --- def setup_placeholder(entry, placeholder_text): """Configure un placeholder pour un champ Entry""" entry.insert(0, placeholder_text) entry.config(fg="grey") def on_focus_in(event): if entry.get() == placeholder_text: entry.delete(0, tk.END) entry.config(fg="black") #grey def on_focus_out(event): if entry.get() == "": entry.insert(0, placeholder_text) entry.config(fg="grey") entry.bind("<FocusIn>", on_focus_in) entry.bind("<FocusOut>", on_focus_out) #======= FONCTIONS PROPRES AUX VALIDATIONS ============================================================= def valider_email(email): """Valide le format d'un email""" pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" return re.match(pattern, email) is not None def valider_telephone(telephone): """Valide le format d'un numéro de téléphone (FR, BE, ES)""" if telephone == "": # Champ optionnel return True # Retirer les espaces pour la validation tel_clean = telephone.replace(" ", "") # Format avec indicatif international: 0033, 0032, 0034 pattern_international = r"^00(33|32|34)[1-9]\d{8}$" # Format national français: 01 23 45 67 89 (10 chiffres) pattern_national = r"^0[1-9]\d{8}$" return re.match(pattern_international, tel_clean) or re.match(pattern_national, tel_clean) def valider_date(date): """Valide le format jj/mm/aaaa""" pattern = r"^\d{2}/\d{2}/\d{4}$" return re.match(pattern, date) is not None def valider_annee(annee): """Valide une année (4 chiffres)""" return annee.isdigit() and len(annee) == 4 #======================== SAISIE DES EFFECTIF ======================================== # ------ 1. Fonction pour RECUPERER LES DONNEES lors du clic sur VALIDER === def valider_donnees(): # 1. Récupération des données pour vérification Lacivilite = entryCivilite.get() Lenom = entryNom.get() Leprenom = entryPrenom.get() Ladhesion = entryAdhesion.get() Lacotisation = entryCotisation.get() Lestatut=entryStatut.get() Ladresse1 = entryAdresse1.get() Ladresse2 = entryAdresse2.get() Lecodepostal = entryCpostal.get() Laville = entryVille.get() Lemail = entryMail.get() Lanaissance = entryNaissance.get() Lanation = entryLanation.get() Laprofession = entryProfession.get() Lobserv=entryObserv.get() Larelance = entryRelance.get() Letelefixe = entryTphfixe.get() Leteleport = entryTphport.get() Lalettre = entryLettre.get() #------ 2. VERIFICATION de la SAISIE champ par champ --------- if Lacivilite != "" and Lacivilite not in ["Mr", "Md", "Et"]: messagebox.showwarning("Attention pour la civilité", "Utiliser Mr - Md - Et") entryCivilite.focus() return if Lenom == "Entrez votre nom ici" or Lenom == "": messagebox.showwarning("Attention", "Veuillez entrer un nom valide") entryNom.focus() return if Leprenom == "Entrez votre prénom ici" or Leprenom == "": messagebox.showwarning("Attention", "Veuillez entrer un prénom valide") entryPrenom.focus() return #if Ladresse1 == "": # messagebox.showwarning("Attention", "Veuillez entrer une adresse") # entryAdresse1.focus() # return #if Lecodepostal == "": # messagebox.showwarning("Attention", "Veuillez entrer un code postal") # entryCpostal.focus() # return #if Laville == "": # messagebox.showwarning("Attention", "Veuillez entrer une ville") # entryVille.focus() # return if Lemail != "" and not valider_email(Lemail): messagebox.showwarning("Attention", "Format d'email invalide") entryMail.focus() return # Validation des téléphones if Letelefixe != "" and not valider_telephone(Letelefixe): messagebox.showwarning( "Attention", "Format de téléphone fixe invalide\n\n" "Exemples acceptés:\n" " National: 01 23 45 67 89\n" " International:\n" " - France: 0033 1 23 45 67 89\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphfixe.focus() return if Leteleport != "" and not valider_telephone(Leteleport): messagebox.showwarning( "Attention", "Format de téléphone portable invalide\n\n" "Exemples acceptés:\n" " National: 06 12 34 56 78\n" " International:\n" " - France: 0033 6 12 34 56 78\n" " - Belgique: 0032 4 12 34 56 78\n" " - Espagne: 0034 6 12 34 56 78" ) entryTphport.focus() return if Lanaissance != "" and not valider_date(Lanaissance): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryNaissance.focus() return if Ladhesion != "" and not valider_date(Ladhesion): messagebox.showwarning("Attention", "Format de date invalide (jj/mm/aaaa)") entryAdhesion.focus() return if Lacotisation != "" and not valider_annee(Lacotisation): messagebox.showwarning("Attention", "Année de cotisation invalide (4 chiffres)") entryCotisation.focus() return if Larelance != "" and Larelance not in ["O", "N"]: messagebox.showwarning("Attention", "Lettre info doit être O ou N") entryLettre.focus() return if Lalettre != "" and Lalettre not in ["O", "N"]: messagebox.showwarning("Attention", "Lettre info doit être O ou N") entryLettre.focus() return #------ 3. Afficher les données récupérées message = ( f"Création de {Lacivilite} {Lenom} {Leprenom}\n" f"Adresse 1: {Ladresse1}\n" f"Adresse 2: {Ladresse2}\n" f"CP/Ville: {Lecodepostal} {Laville}\n" f"Fixe/Port: {Letelefixe} / {Leteleport}\n" f"Mail: {Lemail}\n" f"Adhésion: {Ladhesion}, Naissance: {Lanaissance}, Profession: {Laprofession}\n" f"Lettre: {Lalettre}, Nationalité: {Lanation}, (Cotisation: {Lacotisation}, Relance: {Larelance}\n" f"Statut: {Lestatut}, Observation: {Lobserv}" ) messagebox.showinfo("Pour info: Données saisies", message) #--- message pour OK saisie bloqué ---- #messagebox.showinfo( # "Succès", f"Adhérent: {Lenom} {Leprenom} enregistré avec succès." #) #------ 4. Effacer les champs après validation def effacer_formulaire(): """Efface tous les champs du formulaire""" entryCivilite.delete(0, tk.END) entryNom.delete(0, tk.END) entryPrenom.delete(0, tk.END) entryAdhesion.delete(0, tk.END) entryCotisation.delete(0, tk.END) entryStatut.delete(0, tk.END) entryAdresse1.delete(0, tk.END) entryAdresse2.delete(0, tk.END) entryCpostal.delete(0, tk.END) entryVille.delete(0, tk.END) entryMail.delete(0, tk.END) entryNaissance.delete(0, tk.END) entryLanation.delete(0, tk.END) entryProfession.delete(0, tk.END) entryObserv.delete(0, tk.END) entryRelance.delete(0, tk.END) entryTphfixe.delete(0, tk.END) entryTphport.delete(0, tk.END) entryLettre.delete(0, tk.END) # Réinitialiser les placeholders setup_placeholder(entryNom, "Entrez votre nom ici") setup_placeholder(entryPrenom, "Entrez votre prénom ici") #=============== Sous/Fonctions BOUTONS "VALIDER" du s/menu EFFECTIF ===================================== # 1 ouvrir le classeur --- def ouvrir_classeur(): wb = load_workbook('donnees.xlsm', keep_vba=True, read_only=False, data_only=False) sheet = wb["Effectif"] return wb, sheet #--------------------------------------------- # 2 Trouver la première ligne vide en parcourant la colonne A def dern_ligne(): wb = load_workbook("donnees.xlsm", keep_vba=True) sheet = wb["Effectif"] last_row = sheet.max_row while last_row > 1 and sheet.cell(row=last_row, column=2).value is None: last_row -= 1 last_row += 1 # ligne vide suivante x = last_row return wb, sheet, x # ---------------------------------------------- # 3 enregistrer dans le classeur Excel feuille EFFECTIF def enregistrer_dans_excel(): messagebox.showinfo("Enregistrement", "Patientez SVP") # 1. Récupération des données pour enregistrement Excel Lacivilite = entryCivilite.get() Lenom = entryNom.get() LenomM = Lenom.upper() # ---- passer en majuscule --- Leprenom = entryPrenom.get() LeprenomM = Leprenom.upper() # ---- passer en majuscule --- #==== changer string saisie en format Date ============= Ladhesion = entryAdhesion.get() Ladhesion2 = datetime.strptime(Ladhesion, "%d/%m/%Y").date() #------------ Lacotisation = entryCotisation.get() Lestatut = entryStatut.get() Ladresse1 = entryAdresse1.get() Ladresse1MI = Ladresse1.lower() Ladresse2 = entryAdresse2.get() Ladresse2MI = Ladresse2.lower() Lecodepostal = entryCpostal.get() Laville = entryVille.get() LavilleM = Laville.upper() # ---- passer en majuscule - Lemail = entryMail.get() #==== changer string saisie en format Date ============= Lanaissance = entryNaissance.get() Lanaissance2 = datetime.strptime(Lanaissance, "%d/%m/%Y").date() #------------ Lanation = entryLanation.get().upper() LanationM = Lanation.upper() # ---- passer en majuscule Laprofession = entryProfession.get() Lobserv=entryObserv.get() Larelance = entryRelance.get() LarelanceM = Larelance.upper() # ---- passer en majuscule --- if LarelanceM == "O": LarelanceM = "OUI" if LarelanceM == "N": LarelanceM = "NON" Letelefixe = entryTphfixe.get() Leteleport = entryTphport.get() Lalettre = entryLettre.get().upper() LalettreM = Lalettre.upper() # ---- passer en majuscule --- if LalettreM == "O": LalettreM = "OUI" if LalettreM == "N": LalettreM = "NON" wb, sheet, x = dern_ligne() #--- se positionne sur la première ligne vide à partir de colonne A "fonction dern_ligne" # Colonne A = 1 col = 1 sheet.cell(row=x, column=col).value = Lacivilite col = 2 sheet.cell(row=x, column=col).value = LenomM col = 3 sheet.cell(row=x, column=col).value = LeprenomM col = 4 sheet.cell(row=x, column=col).value = Ladhesion2 col = 5 sheet.cell(row=x, column=col).value = Lacotisation col = 6 sheet.cell(row=x, column=col).value = Lestatut col = 7 sheet.cell(row=x, column=col).value = Ladresse1MI col = 8 sheet.cell(row=x, column=col).value = Lecodepostal col = 9 sheet.cell(row=x, column=col).value = LavilleM col = 10 sheet.cell(row=x, column=col).value = Lemail col = 11 sheet.cell(row=x, column=col).value = Lanaissance2 col = 12 sheet.cell(row=x, column=col).value = LanationM col = 13 sheet.cell(row=x, column=col).value = Laprofession col = 14 sheet.cell(row=x, column=col).value = Lobserv col = 15 sheet.cell(row=x, column=col).value = LarelanceM col = 16 sheet.cell(row=x, column=col).value = Letelefixe col = 17 sheet.cell(row=x, column=col).value = Leteleport col = 18 sheet.cell(row=x, column=col).value = LalettreM wb.save("donnees.xlsm") # 4 ---------------------------------------- def trier_feuille(): fichier = "Donnees.xlsm" feuille = "Effectif" # Charger la feuille en gardant les dates en datetime df = pd.read_excel(fichier, sheet_name=feuille) # Trier sur la colonne B (index 1) df_trie = df.sort_values(by=df.columns[1], ascending=True) # Réécrire la feuille triée dans le fichier sans casser les macros with pd.ExcelWriter(fichier, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer: df_trie.to_excel(writer, sheet_name=feuille, index=False) # Appliquer le format de date français aux colonnes datetime workbook = writer.book worksheet = writer.sheets[feuille] for idx, col in enumerate(df_trie.columns, start=1): if pd.api.types.is_datetime64_any_dtype(df_trie[col]): for row in range(2, len(df_trie) + 2): # +2 car commence à 1 et header cell = worksheet.cell(row=row, column=idx) cell.number_format = 'DD/MM/YYYY' # 5 ------------------------------------------ def quitter_application(): messagebox.showinfo("Patientez", "Réorganisation des EFFECTIFS en cours") trier_feuille() # 1️⃣ On trie avant de quitter app.quit() # 2️⃣ On ferme lapplication #=============== Fonctions BOUTONS du s/menu EFFECTIF ======================================== # bouton EFFECTIF 1 . Enregistrer les données saisies dans "données" d'Excel def valider_et_ouvrir(): valider_donnees() #--- valide les données "fonction valider_donnees" # ouvrir_classeur() #--- ouvrir le classeur" dern_ligne() #--- positionner sur 1ère ligne vide enregistrer_dans_excel() #--- enregistre dans le classeur Excel # trier_feuille() #--- Trier la feuille à partir colonne B les noms effacer_formulaire() #--- réinitialise le formulaire de saisie fonction "effacer_formulaire" wb = load_workbook('donnees.xlsm', keep_vba=True, read_only=False, data_only=False) wb.close() # bouton EFFECTIF 2 . Modifierimer les données dans "données" d'Excel # bouton EFFECTIF 3 . Supprimer les données dans "données" d'Excel # bouton EFFECTIF 4 . Envoyer carte def envoyer_la_carte(): envoyer_carte() # bouton EFFECTIF 5 . Envoyer mail def envoyer_un_mail(): envoyer_mail() #------------------------ # 2. Modifier adhérent def modifier_donnees(): """Fonction pour modifier un adhérent (à implémenter)""" messagebox.showinfo("Modifier", "Fonction MODIFIER à implémenter") #------------------------ # 3. Supprimer adhérent def supprimer_donnees(): """Fonction pour supprimer un adhérent""" reponse = messagebox.askyesno( "Confirmation", "Voulez-vous vraiment supprimer cet adhérent ?" ) if reponse: effacer_formulaire() messagebox.showinfo("Suppression", "Adhérent supprimé") #------------------------ # 4. Envoyer la carte d'adhérent def envoyer_carte(): """Fonction pour modifier un adhérent (à implémenter)""" messagebox.showinfo("ENVOYER CARTE", "Fonction ENVOYER CARTE à implémenter") #------------------------ # 5. Envoyer la mail à l'adhérent def envoyer_mail(): """Fonction pour modifier un adhérent (à implémenter)""" messagebox.showinfo("Envoyer2", "Fonction ENVOYER MAIL à implémenter") #=========================================================================================== # Fonction helper pour créer des lignes avec label et entry (avec polices agrandies) def create_entry_row(parent, label_text, entry_width, row_num): """Crée une ligne avec label aligné à gauche et entry avec police agrandie""" # Label avec police de taille 12 pour meilleure lisibilité label = tk.Label(parent, text=label_text, anchor="w", bg="#f0f0f0", font=("Helvetica", 11)) label.grid(row=row_num, column=0, sticky="w", padx=10, pady=5) # Entry avec police de taille 12 et hauteur adaptée entry = tk.Entry(parent, width=entry_width, font=("Helvetica", 12)) entry.grid(row=row_num, column=1, sticky="w", padx=10, pady=5) return entry def create_tip_label(parent, text, row_num): """Crée un label de conseil en rouge avec police agrandie""" tip = tk.Label(parent, text=text, fg="red", font=("Helvetica", 10), bg="#f0f0f0") tip.grid(row=row_num, column=2, sticky="w", padx=5, pady=5) #========================= LE MENU PRINCIPAL ======================================== # ----------------- Configuration de la fenêtre principale ----------------- app = tk.Tk() app.title("Assistance Humanitaire Internationale") app.geometry("1400x800+10+10") # centrer la fenetre #app.geometry("1400x800") app.configure(bg="#e8f4f8") #Chargement de l'image photo = PhotoImage(file="D:\\Program\\IMAGE\\LOGO AHI.png") #---- photo = photo.zoom(4, 4)# Double la taille de l'image photo = photo.subsample(27, 27)# ----- Réduit la taille de moitié (facteur 2) labelIcone= tk.Label(app, image=photo) labelIcone.pack(side="top", pady=(10)) # pour INFO # Chargement de l'image (commenté pour éviter les erreurs si le fichier n'existe pas) # Vous pouvez décommenter ces lignes si votre image existe à cet emplacement # photo = PhotoImage(file="D:\Program\IMAGE\LOGO AHI.png") # photo = photo.subsample(27, 27) # labelIcone= tk.Label(app, image=photo) # labelIcone.pack(side="top", pady=(10)) # Polices titre_font = font.Font(family="Helvetica", size=20, weight="bold") section_font = font.Font(family="Helvetica", size=16, weight="bold") # Légèrement réduit pour l'équilibre # ----------------- En-tête avec titre ----------------- frame_header = tk.Frame(app, bg="#4a90e2", height=80) frame_header.pack(fill="x", pady=(0, 15)) labelTitre = tk.Label( frame_header, text="GESTION DES EFFECTIFS", fg="white", bg="#4a90e2", font=titre_font, pady=15 ) labelTitre.pack() # ======= CORRECTION: Affichage de la date système ======= # On récupère la date actuelle avec datetime.date.today() # puis on la formate en français avec strftime() #les lignes avec !!!! ont été isolées car conflit import datetime #======================================================================== #!!!! date_actuelle = datetime.date.today() # Formatage: %A = jour de la semaine, %d = jour, %B = mois, %Y = année # Note: par défaut, strftime() utilise les noms anglais. Pour le français, # vous pouvez utiliser locale ou formater manuellement #!!!! date_formatee = date_actuelle.strftime("%d/%m/%Y") # Alternative avec jour de la semaine en français (méthode simple) jours_fr = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"] mois_fr = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] #!!!!jour_semaine = jours_fr[date_actuelle.weekday()] #!!!! texte_date = f"Nous sommes le {jour_semaine} {date_actuelle.day} {mois_fr[date_actuelle.month-1]} {date_actuelle.year}" # Création du label avec la date formatée et police agrandie labelDate = tk.Label( frame_header, #text=texte_date, fg="white", bg="#4a90e2", font=("Helvetica", 14), # Police agrandie pour meilleure lisibilité pady=5 ) labelDate.pack() # # ----------------- Conteneur principal avec padding ----------------- main_container = tk.Frame(app, bg="#e8f4f8") main_container.pack(fill="both", expand=True, padx=20, pady=10) # ----------------- Frame pour les deux colonnes ----------------- content_frame = tk.Frame(main_container, bg="#e8f4f8") content_frame.pack(fill="both", expand=True) # ================== LA SAISIE ============================= # ========== COLONNE GAUCHE : COORDONNÉES ========== frame_coordonnees = tk.LabelFrame( content_frame, text="📋 Coordonnées", font=section_font, bg="lightgreen", relief="groove", borderwidth=2, padx=10, pady=10 ) frame_coordonnees.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) # Civilité entryCivilite = create_entry_row(frame_coordonnees, "Civilité", 4, 0) create_tip_label(frame_coordonnees, "ATTENTION: utiliser Mr ou Md ou Et", 0) # Nom et Prénom #labelNom = tk.Label(app, text="NOM", fg="blue", bg="#FF0") entryNom = create_entry_row(frame_coordonnees, "Nom :", 27, 1) setup_placeholder(entryNom, "Entrez votre nom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour noms composés", 1) entryPrenom = create_entry_row(frame_coordonnees, "Prénom :", 27, 2) setup_placeholder(entryPrenom, "Entrez votre prénom ici") create_tip_label(frame_coordonnees, "Utiliser le - pour prénoms composés", 2) # Séparateur tk.Frame(frame_coordonnees, height=2, bg="#d0d0d0").grid(row=3, column=0, columnspan=3, sticky="ew", pady=10, padx=10) # Adresses entryAdresse1 = create_entry_row(frame_coordonnees, "Adresse 1 :", 33, 4) entryAdresse2 = create_entry_row(frame_coordonnees, "Adresse 2 :", 33, 5) # Code Postal et Ville sur la même ligne (avec polices agrandies) entryCpostal = create_entry_row(frame_coordonnees, "Code Postal :", 7, 6) entryVille = create_entry_row(frame_coordonnees, "Ville :", 33,7) # Téléphones entryTphfixe = create_entry_row(frame_coordonnees, "Tél. fixe :", 20, 8) create_tip_label(frame_coordonnees, "Ex: 01 23 45 67 89 ou 0033 1 23 45 67 89", 8) entryTphport = create_entry_row(frame_coordonnees, "Tél. portable :", 20, 9) create_tip_label(frame_coordonnees, "Ex: 06 12 34 56 78 ou 0033 6 12 34 56 78", 9) # Mail entryMail = create_entry_row(frame_coordonnees, "Email :", 33, 10) # ========== COLONNE DROITE : INFORMATIONS ADHÉSION ========== frame_adhesion = tk.LabelFrame( content_frame, text="👤 Informations Adhésion", font=section_font, bg="lightgreen", relief="groove", borderwidth=2, padx=15, pady=15 ) frame_adhesion.grid(row=0, column=1, sticky="nsew", padx=(0, 0)) # Champs adhésion entryAdhesion = create_entry_row(frame_adhesion, "Date Adhésion :", 12, 0) create_tip_label(frame_adhesion, "Format: jj/mm/aaaa", 0) entryNaissance = create_entry_row(frame_adhesion, "Date de naissance :", 12, 1) create_tip_label(frame_adhesion, "Format: jj/mm/aaaa", 1) entryProfession = create_entry_row(frame_adhesion, "Profession :", 33, 2) entryLanation = create_entry_row(frame_adhesion, "Nationalité :", 3, 3) create_tip_label(frame_adhesion, "En 2 lettres seulement", 3) # Séparateur #tk.Frame(frame_adhesion, height=2, bg="#d0d0d0").grid(row=6, column=0, columnspan=3, sticky="ew", pady=50, padx=10) entryCotisation = create_entry_row(frame_adhesion, "Année Cotisation :", 5, 4) entryLettre = create_entry_row(frame_adhesion, "Lettre info :", 3, 5) create_tip_label(frame_adhesion, "Tapez O ou N", 5) entryRelance = create_entry_row(frame_adhesion, "Relance Cotisation :", 3, 6) entryStatut = create_entry_row(frame_adhesion, "Statut A.H.I", 33, 20) entryObserv = create_entry_row(frame_adhesion, "Observation", 33, 60) # Configuration des poids pour expansion content_frame.columnconfigure(0, weight=1) content_frame.columnconfigure(1, weight=1) # ----------------- Frame des Boutons ----------------- frame_boutons = tk.Frame(app, bg="#e8f4f8", pady=20) frame_boutons.pack(side="bottom", fill="x") # Style des boutons avec police agrandie button_style = {"font": ("Helvetica", 10, "bold"), "padx": 10, "pady": 10, "width": 20} # ---- command=renvoie à la fonction correspondante Button0 = tk.Button( frame_boutons, text="✓ VALIDER", command=valider_et_ouvrir, bg="#90EE90", **button_style ) Button0.pack(side="left", padx=15) Button1 = tk.Button( frame_boutons, text="✎ MODIFIER", command=modifier_donnees, bg="#87CEEB", **button_style ) Button1.pack(side="left", padx=15) Button2 = tk.Button( frame_boutons, text="✗ SUPPRIMER", command=supprimer_donnees, bg="#FFB6C1", **button_style ) Button2.pack(side="left", padx=15) Button3 = tk.Button( frame_boutons, text="✎ ENVOYER CARTE", command=envoyer_la_carte, bg="#F5F5DC", **button_style ) Button3.pack(side="left", padx=15) Button4 = tk.Button( frame_boutons, text="✎ ENVOYER UN MAIL", command=envoyer_un_mail, bg="#F5F5DC", **button_style ) Button4.pack(side="left", padx=15) Button5 = tk.Button( frame_boutons, text="⏻ QUITTER", command=quitter_application, # 👈 on appelle notre fonction personnalisée bg="#FFA07A", **button_style ) Button5.pack(side="right", padx=15) app.mainloop()
Ok supprimez la ligne import datetime
Je ne vois pas le conflit, mais j'imagine que la suite est le problème, par ex. sur cette ligne date_actuelle = datetime.date.today()
La solution est de là remplacer par date_actuelle = date.today()
Avec votre proposition sur date_actuelle = date.today cela ne plante plus mais la date ne s'affiche toujours pas en haut.
Avec date_actuelle = datetime.date.today() cela fonctionne mais si je réactive "import datetime" mais alors c'est le trie qui ne marche plus.
Le trie a besoin de: from datetime import date, datetime, timedelta
La date du menu a besoin de: import datetime
et les deux ne vont pas ensemble. Dilem
Voilà la partie de date au menu.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 # ======= CORRECTION: Affichage de la date système ======= #======================================================================== #---- date_actuelle = datetime.date.today() date_actuelle = date.today() date_formatee = date_actuelle.strftime("%d/%m/%Y") # Alternative avec jour de la semaine en français (méthode simple) jours_fr = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"] mois_fr = ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] jour_semaine = jours_fr[date_actuelle.weekday()] texte_date = f"Nous sommes le {jour_semaine} {date_actuelle.day} {mois_fr[date_actuelle.month-1]} {date_actuelle.year}" # Création du label avec la date formatée et police agrandie labelDate = tk.Label( frame_header, #text=texte_date, fg="white", bg="#4a90e2", font=("Helvetica", 14), # Police agrandie pour meilleure lisibilité pady=5 ) labelDate.pack()
Je ne sais pas, c'est vous qui codez, moi je corrige juste votre problème de conflits, et qui même si vous en faîtes la remarque visible concernant l'emplacement de la date, n'a rien à voir avec les importations...Citation:
Avec votre proposition sur date_actuelle = date.today cela ne plante plus mais la date ne s'affiche toujours pas en haut.
Avec date_actuelle = datetime.date.today() cela fonctionne mais si je réactive "import datetime" mais alors c'est le trie qui ne marche plus.
Le trie a besoin de: from datetime import date, datetime, timedelta
La date du menu a besoin de: import datetime
et les deux ne vont pas ensemble. Dilem
Cohérent et normalCitation:
Avec votre proposition sur date_actuelle = date.today cela ne plante plus mais la date ne s'affiche toujours pas en haut.
Vous utilisez l'objet Date : https://docs.python.org/3/library/da...ime.date.today
Code:
1
2
3
4
5 >>> from datetime import date >>> date.today() datetime.date(2025, 11, 7) >>> date.today().strftime("%d/%m/%Y") '07/11/2025'
incohérent, regardez la documentation, si import datetime est supprimé, vous ne l'utilisez pas !!! Apprenez python plutôt que de copier coller.Citation:
Avec date_actuelle = datetime.date.today() cela fonctionne mais si je réactive "import datetime" mais alors c'est le trie qui ne marche plus.
Vous utilisez date.today() car vous avez importé date.
Non ce ne sont que des modules que vous importez, pas la fonction de tri que vous implémentez...Citation:
Le trie a besoin de: from datetime import date, datetime, timedelta
Idem, vous dîtes n'importe quoi, regardez les documentations. D'ailleurs je ne vois pas de widget Menu dans votre code.Citation:
La date du menu a besoin de: import datetime
Certes, voilà pourquoi nous avons supprimé import datetime.Citation:
et les deux ne vont pas ensemble. Dilem