برنامه نویس و مدرس پایتون
آموزش tkinter - اولین برنامه جمع دو عدد!
پس از نصب و راه اندازی tkinter در این پست نحوه نوشتن یک برنامه ساده رو می خوام آموزش بدم. برنامه بسیار ساده خواهد بود.
می خواهیم برنامه ای بنویسیم که دو عدد از کاربر دریافت کند و حاصل جمع آن را نمایش دهد.
ابتدا برنامه را به صورت کنسولی می نویسیم و سپس گرافیکی. هدف من از نوشتن هر دو نسخه برنامه به این دلیل است که شما خواننده محترم درک کنید که برنامه نویسی و یا حل مسئله هیچ ربطی به روش تعامل کاربر با برنامه نداره. کاربر می تواند یا از طریق ترمینال، یا از طریق یک صفحه تاچ یا از طریق نگاه کردن و یا به هر نحوی با برنامه ارتباط برقرار کند. در برنامه نویسی هدف اصلی ما حل مسئله بوده و هدف بعدی ما فراهم آوردن محیط مناسب برای کاربر می باشد.
بخش کنسولی برنامه
اگر فرض کنیم محیط تعامل برنامه کنسولی است برنامه دریافت دو عدد و جمع آن را با زبان پایتون می توانیم به صورت زیر بنویسیم:
number1 = int(input("Enter First number: "))
number2 = int(input("Enter Second number: "))
result = number1 + number2
print("Result is", result)
اگر در برنامه بالا اعداد ۲ و ۳ را به عنوان ورودی وارد کنیم خروجی به صورت Result is 5 چاپ خواهد شد. در این برنامه بدون اینکه درگیر ایجاد دکمه و برچسب و غیره باشیم به راحتی توانستیم دو عدد را دریافت و حاصل آن را نمایش دهیم و زیبای محیط کنسولی هم راحتی نوشتن برنامه برای این محیط است. در این محیط تمرکز ما بر روی حل مسئله است.
گرچه برنامه بالا به درستی کار می کند اما چند ایراد دارد.
- اگر کاربر به جای عدد حروف وارد کند خطای ValueError رخ می دهد پس ما باید این خطا را مدیریت کنیم
- کد بالا ماژولار نیست به این معنی که اگر بعدا بخواهیم این برنامه را گسترش دهیم و شبیه ماشین حساب کنیم که علاوه بر حاصل جمع، حاصل تفریق و ضرب و .. را محاسبه کند این امکان در نظر گرفته نشده است.
اگر در ابتدای کار زمان زیادی صرف ماژولار کردن کد کنیم ممکن است زمان زیادی از دست دهیم و از اصل مسئله دور شویم.
کد بالا را به صورت زیر ریفکتور می کنیم و با نام calc.py ذخیره می کنیم:
def add(x, y):
return x + y
def to_int(val):
try:
num = int(val)
except ValueError:
num = None
return num
- در کد بالا تابعی به نام add نوشتیم که وظیفه آن دریافت دو عدد و برگشت حاصل جمع آن می باشد.
- تابع to_int یک مقدار دریافت می کند و در بخش try تلاش می کند تا آن مقدار را تبدیل به عدد کند اگر موفق نشد مقدار None را به متغییر num انتساب داده و برگشت می دهد.
فایلی به نام console_ui.py ایجاد کرده و کدهای زیر داخل آن قرار می دهیم
# console_ui.py
from calc import to_int, add
def main():
number1 = to_int(input("Enter an integer number: "))
number2 = to_int(input("Enter an integer number: "))
if number1 is not None and number2 is not None:
print("Result is", add(number1, number2))
else:
print("Error! Numbers must be integers")
if __name__ == '__main__':
main()
- با ایجاد فایل console_ui کد و محیط تعاملی کاربر را از هم جدا کردیم
- در تابع main دو بار تابع to_int را فراخوانی کرده ایم تا مقداری که کاربر وارد می کند را به آن پاس دهیم. اگر آن مقدار قابل تبدیل به int بود که عدد برگشت داده می شود اگر مقدار عدد نبود مقدار None برگشت می خورد که می توانیم با توجه به آن خطایی به کاربر نمایش دهیم.
شاید کد بالا پیچیده و کار اضافی به نظر برسد، اما از همین کد در انواع محیط های تعاملی می توانیم استفاده کنیم. زیرا که مسئله ما جمع دو عدد است و نه محیط تعاملی کاربر
در حال حاضر برنامه دوم نصت به برنامه اول مزایای زیر را دارا می باشد:
- خوانایی کد افزایش یافته
- هر تابع مسئول انجام یک کار می باشد. اگر زمانی خطایی در هر بخش از برنامه به وجود بیاید می توانیم به راحتی به تابع آن مراجعه کنیم
- گسترش برنامه ساده تر شده. کافی است توابع دیگری برای تفریق و ... اضافه کنیم.
- بخش واسط کاربر و بخش کد از هم جدا شده اند که خطایابی و گسترش کد را ساده تر می کند.
بخش گرافیکی برنامه
در برنامه نویسی گرافیکی قبل از اینکه کد برنامه را بنویسیم باید محیط گرافیکی که قرار است کاربر با آن ارتباط برقرار کند را طراحی کنیم. پس می توانیم شماتیک محیط تعاملی برنامه را به شکل زیر داشته باشیم:
بعد از مشخص شدن شمای برنامه باید آن را با ویجیت هایی که tkinter در اختیار ما قرار میدهد تبدیل کرده و یک محیط تعاملی واقعی ایجاد کنیم.
ویجیت ها اجزایی هستند که توسط آنها برنامه گرافیکی پیاده سازی می شود. از جمله این ویجیت ها می توان به دکمه، برچسب، منو و ... اشاره کرد.
ویجیت هایی که در شمای بالا مورد استفاده قرار گرفته اند درشکل زیر مشخص شده اند:
بعد از اینکه مشخص شد از چه ویجیت هایی باید استفاده کنیم می توانیم با استفاده از پکیج tkinter شروع به پیاده سازی برنامه کنیم.
استفاده از پکیج tkinter
برای استفاده از پکیج tkinter کدهای زیر را وارد می کنیم: (کدهای زیر را در فایلی به نام tk_ui.py ذخیره کنید)
import tkinter as tk
from tkinter import ttk
پکیج tkinter دارای ماژولی به نام ttk می باشد که مخفف Themed tk می باشد. کار این ماژول این است که با توجه به سیستم عامل شما نحوه نمایش ویجیت ها را تغییر دهد. یعنی اگر برنامه شما در مک باشد دکمه به شکل دکمه سیستم عامل مک و اگر در ویندوز باشید دکمه مانند دکمه ویندوزی نمایش داده می شود. پس تا جایی که ممکن است و هر ویجیتی که نسخه ttk آن پیاده سازی شده باشد را استفاده می کنیم.
در تمامی برنامه های tkinter باید یک پنجره اصلی یا پنجره مادر وجود داشته باشد و همیشه باید یک عدد از این فرم یا پنجره وجود داشته باشد برای ایجاد و نمایش پنجره مادر کدهای زیر را وارد می کنیم
root = tk.Tk()
root.mainloop()
همین ۴ خط کد پنجره ای به شکل زیر برای ما ایجاد می کند:
متد mainloop در پنجره اصلی چرخه رخداد می باشد. یعنی این متد باعث می شود که این پنجره به شکل بی نهایتی در حال اجرا باشد و همین موضوع باعث می شود که ما این پنجره را ببینیم و با آن در تعامل باشیم. این چرخه رخداد علاوه بر اینکه پنجره والد را به ما نمایش می دهد بلکه تمامی تعاملات اعم از کلیک، جابجایی ، فشرده شدن هر دکمه ای را نیز رصد می کند و در صورتی که عملی به این رخدادها نسبت داده شده باشد به آنها پاسخ می دهد. پس تمامی کدهای ما قبل از mainloop باید نوشته شوند تا mainloop یا همان چرخه رخداد از آنها اطلاع داشته باشد و در صورت نیاز به آنها پاسخ دهد. این پنجره به صورت بی نهایت اجرا می شود تا هنگامی که رخداد خارج شدنی صادر شود.
ایجاد برچسب، دکمه و اینتری (Entry, Button, Label)
برای ایجاد برچسب و نمایش آنها در فرم root کد زیر را وارد می کنیم:
ttk.Label(root, text="1st Number").grid(row=0, column=0)
ttk.Label(root, text="2nd Number").grid(row=1, column=0)
ttk.Label(root, text="Result").grid(row=2, column=1)
در ایجاد ویجیت همیشه باید این نکته را در نظر داشته باشیم که اولین آرگومان هر ویجیت والد آن می باشد. در کد بالا والد هر سه برچسب پنجره root می باشد یعنی زمانی که برچسب ها را نمایش دادیم بر روی پنجره root نمایش داده خواهند شد.
ایجاد یک ویجیت باعث نمایش داده شده آن نمیشود. اینکه یک ویجیتی چطور و کجای فرم نمایش داده شود همیشه مبحث مهمی در تمامی پلتفرم ها می باشد. در این مثال از متد grid برای نمایش دادن ویجیت استفاده کرده ایم.
برای نمایش دادن ویجیت ها با استفاده از grid کافی است سطر و ستونی را که محل ویجیت هست را به متد grid پاس دهیم.
متد grid مانند یک جدول کار می کند به شکل زیر توجه کنید:
شکل بالا نشان می دهد که اگر بخواهیم از grid برای نمایش ویجیت ها استفاده کنیم باید چگونه عمل کنیم به عنوان مثال اگر بخواهیم بدانیم که دکمه را باید کجا قرار دهیم با توجه به شکل بالا می فهمیم که دکمه در سطر سوم و ستون اول نمایش داده می شود.
با توجه به شکل بالا کل کد برنامه به شکل زیر خواهد بود:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
ttk.Label(root, text="1st Number").grid(row=0, column=0)
ttk.Label(root, text="2nd Number").grid(row=1, column=0)
ttk.Label(root, text="Result").grid(row=2, column=1)
num1 = ttk.Entry(root)
num2 = ttk.Entry(root)
num1.grid(row=0, column=1)
num2.grid(row=1, column=1)
ttk.Button(root, text="Calculate").grid(row=3, column=1)
root.mainloop()
تنها نکته ای که در کد بالا وجود دارد مربوط می شود به Entry ها. چون ما بعدا مقادیر Entryها را برای محاسبه جمع آنها لازم داریم به همین خاطر آنها را به متغییر انتساب داده ایم. اما چون بعد از تعریف دکمه و برچسب کار دیگری با آنها نداریم به هیچ متغییر آنها را نسبت نداده ایم.
حاصل اجرای کد بالا پنجره زیر را نمایش خواهد داد:
تنها کاری که باقی مانده بدست آوردن مقداری است که در Entry ها وارد می شود. این مقادیر زمانی می خواهیم بدست آوریم که کاربر دکمه Calculate را فشرده است.
فشردن دکمه یک رخداد می باشد. در واقع هر گونه تعامل کاربر اعم از فشردن دکمه های کیبورد و یا ماوس یک رخداد یا Event می باشد. به هر رخداد یک تابع و یا یک متد پایتون پاسخ می دهد. یعنی اگر می خواهیم به کلیک بر روی دکمه Calculate پاسخ دهیم باید برای آن یک تابع بنویسیم و آن تابع را به رخداد کلیک دکمه وصل کنیم.
چون کلیک بر روی دکمه کار بسیار رایجی است کافی است آرگومان command دکمه را به یک تابع وصل کنیم.
ttk.Button(root, text="Calculate", command=calculate).grid(row=3, column=1)
در کد بالا کلیک بر روی دکمه را به تابعی به نام calculate وصل کرده ایم. دقت کنید که در اینجا تابع را فراخوانی نمی کنیم فقط آن را وصل می کنیم به رخداد کلیک دکمه. این تابع باید مقادیر داخل Entry ها را بیرون کشیده و عمل جمع بر روی آنها انجام دهد سپس مقدار برچسب Result را به روز رسانی کند. پس اینجا می فهمیم که باید برچسب result را به یک متغییرنسبت می دادیم زیرا بعدا قرار است مقدار آن را به روز رسانی کنیم پس کد کامل برنامه به صورت زیر خواهد بود:
#### tk_ui.py
import tkinter as tk
from tkinter import ttk
import calc
def calculate():
n1 = calc.to_int(num1.get())
n2 = calc.to_int(num2.get())
if n1 is not None and n2 is not None:
res = calc.add(n1, n2)
result_label["text"] = f"Result: {res}"
else:
result_label["text"] = "Error! Values must be integers"
root = tk.Tk()
ttk.Label(root, text="1st Number").grid(row=0, column=0)
ttk.Label(root, text="2nd Number").grid(row=1, column=0)
result_label = ttk.Label(root, text="Result")
result_label.grid(row=2, column=1)
num1 = ttk.Entry(root)
num2 = ttk.Entry(root)
num1.grid(row=0, column=1)
num2.grid(row=1, column=1)
ttk.Button(root, text="Calculate", command=calculate).grid(row=3, column=1)
root.mainloop()
چه در محیط کنسولی باشیم چه در محیط گرافیکی لازم است که مطمئن شویم کاربر یک عدد وارد کرده است که بتوانیم آنها را جمع کنیم. اگر خاطرتان باشد این کار را با تابع to_int در فایل calc.py انجام داده بوده ایم پس آن را import کرده و از آن استفاده کرده ایم.
برای دریافت مقدار یک Entry می توانیم از متد get آن استفاده کنیم.
برای تغییر دادن مقدار text و یا همان متن label هم می توانیم از خصیصه text آن استفاده کرده و آن را تغییر دهیم. تمامی خصیصه های یک ویجیت به صورت دیکشنری در دسترس هستند. پس برای تغییر دادن خصیصه result_label به صورت result_label["text"] = Value اقدام کرده ایم.
و باز دوباره برای محاسبه جمع دو عدد نیز از تابعی که در ماژول calc نوشته بوده ایم استفاده کرده ایم.
اگر برنامه بالا را اجرا کنیم خروجی به شکل زیر خواهد بود:
پایان و جمع بندی
تلاش من در این پست این بود که با جزئیات مناسب نه تنها نحوه نوشتن برنامه گرافیکی با پایتون و tkinter بلکه به نکاتی در مورد برنامه نویسی به صورت عمومی اشاره ای داشته باشم.
در این پست ما کدی نوشته ایم که الزما به محیط خاصی وابسته نیست و حتی شما می توانید یک محیط یا اینترفیس وب برای همین کد بنویسید. این کار را می توانید با ماژول flask انجام دهید.
اگر دقت کنید کد برنامه (calc.py) از UIهای برنامه یا واسطهای کاربری برنامه یعنی tk_ui و console_ui.py جدا می باشد. کد ما یعنی بخشی که مسئول محاسبه و پردازش است را معمولا Controller به UI معمولا View و اگر بانک اطلاعاتی نیز داشته باشیم به آن Model گفته می شود. باید به شما تبریک بگویم که در این برنامه کوچک نیز از الگوی طراحی Model View Controller یا MVC استفاده کرده اید.
در ادامه در مورد زیباسازی همین برنامه صحبت خواهیم کرد. اگر نظری در مورد این سری داشتید می تونید کامنت بذارید.
مطلبی دیگر از این انتشارات
تولید اعداد رندوم با پایتون و numpy
مطلبی دیگر از این انتشارات
مشکلات آموزش مجازی از دید یک دانش آموز
مطلبی دیگر از این انتشارات
آموزش سی شارپ مقدماتی (بخش پنجم - دستورات گرافیکی در محیط کنسول):