събота, 17 януари 2009 г.

Създаване на прозорчета в Python

„ Програмиране с Python“, ФМИ
Нено Ганчев
04.06.2007г.
Как да си създадем прозорци с Python?

* За щастие не се налага да си рисуваме сами прозорците по екрана, за това си има специализирани инструменти
* Има няколко инструмента за Python които ни позволяват да си правим шарени програмки
* Днес ще се запознаем с wxPython - порт на C++ библиотеката wxWidgets за Python

Откъде да започнем?

1. Първо да инсталираме магическата пръчица за прозорци - питонския модул wx
* сваляме си инсталацията, демотата и документацията от http://wxpython.org/
* инсталацията е в готови изпълними файлове, но за ентусиастите има и source-ове
2. Не е лоша идея преди да почнем да си правим собствено приложение първо да погледнем примерните програми и overview частта в документацията
3. Има и страхотно демо в което можете да видите примери за всички възможности които wxPython ви предоставя както в код, така и нагледно(незаменим помощник по време на разработка:)
4. Недостатък на документацията - има я само в C++ вариант със забележки за wxPython

Основни концепции в wxPython

1. Йерархия на класовете, представляващи прозорчетата по екрана
* Основни класове: wxApp, wxWindow, wxFrame, wxDialog, wxPanel
* Класове-контроли: всевъзможни бутончета, текстови полета, менюта и т.н.
2. Обработка на събития(event handling) и main loop

Основните класове отблизо: wxApp

wxApp има няколко метода които трябва да се дефинират или извикат:

1. OnInit()
* инициализира графичното ни приложение(играе ролята на конструктор)
* трябва да върне True, иначе wxPython няма да покаже приложението
2. SetTopWindow(window)
* Казва на програмата кой от всичките прозорци е главен
* Програмата може да има няколко главни прозореца
* Главните прозорци са специални
3. MainLoop()
* стартира главния цикъл за прехващане на събития
* без него главния прозорец може и да се покаже но ще стои "замръзнал" и няма да иска нито да си играе с нас, нито да си ходи
4. OnExit()
* правилното място за деинициализация на потребителски данни
* вика се след като потребителя е казал че ще затваря програмата, но преди вътрешните структури на wxPython да се деинициализират и да не можете да ги ползвате

Основните класове отблизо: wxWindow

* wxWindow e базовия клас на всички прозорци по екрана
* под прозорци се разбира всеки видим обект на екрана(менюта, бутони, диалогови прозорци, текстови полета, статични надписи, вдлъбнати линийки и т.н.)
* поема memory management`а на своите деца
* сам по себе си не предлага никаква полезна за диалог с потребителя функционалност
* не работите директно с обекти от този тип а с производните му wxFrame и wxDialog

Основните класове отблизо: wxFrame

* прозорец, чиито размери и положение могат да бъдат променяни от потребителя
* притежава "бордюр" и лента за заглавието(titlebar)
* може да съдържа всякакви прозорци с изключение на wxFrame и wxDialog(например лента с менюта, лента с инструменти(toolbar) и лента за състоянието(statusbar))

Основните класове отблизо: wxDialog

* прозорец с лента за заглавието и евентуално системно меню в нея
* може да бъде местен из екрана от потребителя
* може да съдържа всякакви контроли вътре в себе си
* обикновено се използва за предоставяне на възможност за избор на потребителя
* има едно специално свойство: може да блокира изпълнението на програмата и възможността на потребителя да взаимодейства с други прозорци докато не направи необходимия избор в диалоговия прозорец

Основните класове отблизо: wxPanel

* Прозорец в който могат да се слагат контроли
* обикновено се съдържа в wxFrame за да не се поставят контролите директно в него
* подобен по външен вид и функционалност на wxDialog, но с удобството че може да се съдържа във всякакъв вид прозорец

Малко практика

Да видим как изглежда казаното досега като код:

* Първо наследяваме wxApp:


class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(parent = None, title = "My own frame")
frame.Show()
self.SetTopWindow(frame)
return True


* След това си дефинираме MyFrame като наследник на wxFrame:


class MyFrame(wx.Frame):
def __init__(self, parent, title, id = -1):
wx.Frame.__init__(self, parent, id, title)
panel = wx.Panel(parent = self)
self.greeting = wx.StaticText(parent = panel, pos = (20, 20), label = "Hello pythoneers!")


* И накрая създаваме обект от MyApp:


app = MyApp()
app.MainLoop()


* Готово!

Малки по-надълбоко: обработване на събития

За да реагира на действията на потребителя, wxPython използва предварително зададени възможни събития( например натискане на бутон или преместване на прозореца) с които можете да асоциирате ваши функции които да обработват по желания от вас начин събитието. Това става като "свържете" някой прозорец с дадено събитие, което е възникнало в него, и с вашата функция която ще го обработи

За удобство събитията не се виждат само от прозореца в който са възникнали, а и от всички прозорци нагоре в родителското дърво. Това означава че ако в панел имате няколко бутона, не е нужно събитието "натискане на бутон" да се прехваща във всеки бутон, може директо да ги прехванете в панела.

Това става като извикате метода Bind на прозореца в който искате да прехванете събитието и му подадете типа събитие, функцията която ще се извика, и източника на събитието
Да си добавим меню в примера

В конструктора на MyFrame добавяме:


menuBar = wx.MenuBar()
menu = wx.Menu()
menu.Append(self.ID_Exit, "E&xit")
menuBar.Append(menu, "&File")
menu = wx.Menu()
menu.Append(self.ID_About, "&About")
menuBar.Append(menu, "&Help")
self.SetMenuBar(menuBar)


Свързваме менютата с наши функции които искаме да се изпълнят когато менюте се избере:


self.Bind(wx.EVT_MENU, self.OnExit, id = self.ID_Exit)
self.Bind(wx.EVT_MENU, self.OnAbout, id = self.ID_About)


И добавяме handler`ите ни:


def OnExit(self, event):
answer = wx.MessageBox("Do you really want to quit?", "Confirm", style = wx.YES_NO | wx.ICON_QUESTION)
if answer == wx.YES: self.Close()

def OnAbout(self, event):
wx.MessageBox("wxPython greeter program", "About", style = wx.OK)


Как да си подредим бутончетата в прозореца

При слагането на повече контроли в един прозорец става малко трудно да ги наредите всички така че да не си пречат. Указването на точна позиция(в координати) на всеки е дейност, достойна за наказание в програмисткия ад. Едно решение на проблема са така наречените sizers

Sizer-ите разчитат на това че в повечето случаи разположението на обектите в прозореца следва прости геометрични правила като например в колона, редица или таблица. Използването им е съвсем просто - при създаване на прозорец създавате и sizer който ще отговаря за него, добавяте всички деца на прозореца в sizer-а и накрая не забравяте да кажете на прозореца кой му е sizer-a. След това sizer-а автоматично поема отговорността за разположението на обектите в прозореца

При добавяне на обекти в sizer-а можете да укажете дали обекта ще се разширява при разширяване на целия прозорец и в какво съотношение в сравнение с останалите обекти в прозореца
Да позапълним прозореца

Засега прозореца в приложението ни е много пуст и на самотния низ му е много скучно, а и му е писнало да поздравява само едни и същи хора. Нека му разнообразим съществуването: ще добавим поле в което да се казва кого ще поздравява програмата ни и бутон с който да поздравим въведеното същество

За целта създаваме BoxSizer в конструктора на MyFrame и добавяме текста и новите контроли в него


self.textField = wx.TextCtrl(panel, -1)
self.greetButton = wx.Button(panel, ID_Greet, "Greet")
buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
buttonSizer.Add(wx.StaticText(parent = panel, label = "Enter name:"), 0, wx.ALL, 10)
buttonSizer.Add(self.textField, 1, wx.ALL | wx.EXPAND, 10)
buttonSizer.Add(self.greetButton, 0, wx.ALL, 10)
panelSizer = wx.BoxSizer(wx.VERTICAL)
panelSizer.Add(self.greeting, 1, wx.ALL | wx.EXPAND | wx.CENTRE, 10)
panelSizer.Add(buttonSizer, 0, wx.ALL | wx.EXPAND, 10)

panel.SetSizer(panelSizer)
panel.Fit()

self.Bind(wx.EVT_BUTTON, self.OnGreet, self.greetButton)

frameSizer = wx.BoxSizer(wx.VERTICAL)
frameSizer.Add(panel, 1, wx.EXPAND)
self.SetSizerAndFit(frameSizer)


Да позапълним прозореца 2

Остана да добавим handler-а за новия бутон:


def OnGreet(self, event):
name = self.textField.GetValue()
if len(name) == 0:
wx.MessageBox("Enter a name to greet first", "Error", style = wx.OK | wx.ICON_EXCLAMATION)
return
self.greeting.SetLabel("Hello %s!" % name)
self.Refresh()
self.Update()

Няма коментари:

Публикуване на коментар