أدوات المستخدم

أدوات الموقع


docs:wxpython


~~ODT~~

تعليم البرمجة الرسومية في wxPython

  • مقالة من تأليف Jan Bodnar
  • ترجمة : حالياً بدري دركوش (Free_Programmer)
  • قيد الإنشاء

هذا مدخل إلى wxPython وهو يعد من أكبر الشروحات المتوفرة على الإنترنت وأكثرها تقدما. وهو يناسب المبتدئين والمتوسطين.

قد ترغب في إلقاء نظرة على المقالات

مقدمة في wxPython

التطبيق

التطبيق هو برنامج للكمبيوتر يقوم بمهمة معينة أو مجموعة من المهام . كمثال على التطبيق هو متصفح الويب و مشغل الوسائط و معالج النصوص . إن مصطلح أداة أو البرمجية خدمية يستخدم مع التطبيق الصغير أو البسيط و الذي يقوم بمهمة واحدة . ان برنامج cp في بيئة يونكس هو مثال على مثل هذه الأداة. كل هذه الأشياء تشكل ما يسمى برمجيات الحاسوب . أن مصطلح برمجيات الكمبيوتر هو الاسم الشائع لوصف نظام التشغيل و البيانات و برامج الكمبيوتر و التطبيقات و ملفات mp3 و ألعاب الكمبيوتر. إن التطبيقات ممكن أن تنشئ لأربع مجالات مختلفة.

Application areas

أن برنامج تسوق على الإنترنت و الويكي و المدونات هي مثال على تطبيقات ويب شائعة . حيث يتم الوصول إليها عن طريق متصفح الويب. الأمثلة عن تطبيقات سطح المكتب تتضمن مثلاً Maya و Opera و المكتب المفتوح و Winamp. إن حوسبة المشاريع هي مجال محدد . حيث تكون التطبيقات في هذا المجال معقدة و كبيرة . أما التطبيقات التي كتبت للأجهزة المحمولة تتضمن كل البرامج التي طُورت من أجل أجهزة الجوال و أجهزة الاتصالات و أجهزة (PDA) و ما يشبهه.

لغات البرمجة

هنالك حالياً العديد من لغات البرمجة المنتشرة , إن القائمة التالية تعتمد على TIOBE جدول مجتمع المبرمجين . هذه الأرقام تعود إلى شهر مايو 2007 .

ترتيب اللغات في 2008 TIOBE
المرتبة اللغة التقييم
1 Java 21.571%
2 C 16.178%
3 (Visual) Basic 10.857%
4

في wxPython لدينا العديد من الودجات (وادجيت) , و لكن يمكننا أن نصنفها إلى مجموعات منطقية :

الودجات الأساسية

توفر هذه الودجات العملانية للودجات المشتقة . و تسمى (ancestors) الأسلاف و هي لا تستخدم عادةً بشكل مباشر.

base widgets

ودجات المستوى الأعلى

هذه الودجات تتواجد بشكل مستقل عن بعضها الآخر .

toplevel widgets

الحاويات Containers

أما الحاويات فوظيفتها أن تحوي أو تستوعب الودجات الأُخرى .

containters

الودجات الديناميكية

هذه الودجات يمكن التعديل عليها من قبل المستخدمين النهائيين .

dynamic widgets

الودجات الثابتة

هذه الودجات تظهر المعلومات و لا يمكن التعديل عليها من قبل المستخدم.

static widgets

الودجات الأُخرى

هذه الودجات تمثل شريط الحالة (statusbar) و شريط الأدوات (toolbar و شريط الأدوات (menubar) في التطبيق .

other widgets

الوراثة Inheritance

هنالك علاقة معينة بين الودجات في wxPython , هذه العلاقة تتطور و تتوسع عن طريق الوراثة , إن الوراثة جزء هام جداً من البرمجة غرضية التوجه , إن الودجات تشكل هرمية و يمكن للودجات أن ترث عملانية أو وظائف من ودجات أُخرى . تسمى الأصناف القائمة بالأصناف الأساسية أو الأصناف الأباء أو الأسلاف . أما الودجات التي ترث فتسمى الودجات المشتقة أو الودجات الأبناء أو الأحفاد .إن المصطلحات مستعارة من علم الأحياء.

inheritance diagram

لنقل مثلاً أننا استخدمنا ودجة الزر (button) في تطبيقنا , أن الزر يرث من ثلاث أصناف أساسية . أقرب الأصناف هو صنف wx.Control . إن ودجة الزر نوع من النافذة الصغيرة , كل الودجات التي تظهر على الشاشة هي نوافذ , لذلك فهي ترث من صنف wx.Window . هتالك كائنات (opjects) خفية لا تظهر بشكل مباشر , مثل sizers و device context و local opject . هنالك أيضاً أيضاً أصتاف ظاهرة و لكنها ليست نوافذ مثل كائن اللون و كائن caret (مؤشر الكتابة) و كائن المؤشرة (cursor). ليس كل الودجات متحكمات , على سبيل المثال wx.Dialog ليست نوع من المتحكات , المتحكمات هي الودجات التي تتوضع فوق ودجات أُخرى تسمى الحاويات . لذلك لدينا صنف أساسي منفصل هو wx.Control.

كل النوافذ تستطيع أن نستجيب للأحداث , و كذلك تقوم ودجة الزر , عن طريق الضغط على الزر نقوم بتشغيل حدث wx.EVT_COMMAND_BUTTON_CLICKED . أن ودجة الزر ترث wx.EvtHandler عبر صنف wx.Window. كل ودجة تستجيب للأحداث يجب أن ترث من صنف wx.EvtHandler و في النهاية كل الكائنات و الأغراض ترث من صنف wx.Object و هو الأب لكل الأغراض في wxPython .

إعداد wxPython

في هذا القسم سوف نريك كيف يتم تنصيب و إعداد مكتبة wxPython , إن العملية سهلة جداً مقدمة لك حسب نظام التشغيل المناسب.

تنصيب wxPython على نظام windows XP

نستطيع تحميل الملفات التفيذية من موقع wxPython الرسمي على الانترنت site . يجب علك أن تختار النسخة الصحيحة حسب نسخة البايثون الموجودة لدينا .

هنالك خيارين أساسين هما :

  • win32-unicode
  • win32-ansi

عادة ما تكون نسخة اليونيكود هي الخير الأفضل حيث تدعم هذه النسخة اللغات غير الإنكليزية . يقوم برنامج التنصيب يإيجاد مفسر البايثون أتوماتيكياً , كل ما علينا فعله هو التأكد من أننا نوافق على الترخيص , هذا كل شيء !

xp setup

هنالك أيضاً حزمة منفردة , و تسمى win32-docs-demos و التي تحوي عينات الأمثلة الشهيرة و التوثيق و الأمثلة . هذا موجه نحو المطورين . ملاحظة مفيدة بخصوص مفسر البايثون في ويندوز , إذا ضغطنا على ملف سكربت بايثون فستظهر نافذة سطر أوامر , هذا هو التصرف الإفتراضي و لكن يمكننا أن نغير ذلك عن طريق إسناد برامج البايثون إلى ملف python.exe .اضغط بزر المؤشرة اليمين على أيقونة البايثون , اختر خصائص (properties) اختر زر (change) , ثم اختر pythonw.exe و هو موجود في مسار تنصيب بايثون , على جهازي مثلاً هو C:\Program Files\Python25\pythonw.exe .

تنصيب wxPython على أبونتو لينوكس

إن عملية التنصيب هنا حتى أسهل منها على ويندوز XP , أن أبونتو توزيعة لينوكس رائعة ,إن تنصيب wxPython سهل جدأ نقوم بالتنصيب باستخدام مدير الحزم ساينأبتك (Synaptic) , تجدها في القائمة System - Administration. نخنار حزمة wxPython و هي تسمى python-wxgtkx.x. يقوم مدير الحزم اتوماتيكياً بإختيار المتطلبات , نحدد الحزمة للتنصيب و نضغط على تطبيق أو Apply , سيقوم بتحميل مكتبة wxPython و تنصيبها على نظامك , الحزم في أبونتو حديثة تماماً , هنالك توزيعات لينوكس أُخرى قد تحوي حزماً أقدم , إن wxPython أداة تتطور بشدة , حتى نحصل على أحدث التحديثات قد نضطر إلى بناء wxPython من المصدر.

الخطوات الأُولى

مثال بسيط

سنبدأ مع مثال بسيط جداً , إن سكربتنا الأول سوف يظهر نافذة صغيرة , لن تقوم بالمزيد. سنقوم بتحليل السكربن سطراً سطراً . و ها هو الكود :

#!/usr/bin/python
 
# simple.py
 
import wx
 
app = wx.App()
 
frame = wx.Frame(None, -1, 'simple.py')
frame.Show()
 
app.MainLoop()
#!/usr/bin/python
 
# simple.py

السطر الأول يستورد الوحدات wxPython الأساسية هو إشارة # يليه المسار إلى مفسر البايثون . السطر الثاني تعليق , و هو يحوي اسم السكربت .

import wx

هذا السطر يقوم بإستيراد وحدات wxPython الأساسية , أي الوحدات core و controls و gdi و misc و windows . تقنياً تعتبر wx كفضاء تسمية , لذلك كل الوظائف و الكائنات من الوحدات الأساسية ستبدأ بالبادئة .wx . السطر الثاني من الشفرة (ال.كود) سيقوم بإنشاء كائن التطبيق .

app = wx.App()

كل برنامج wxPython يجب أن يحوي كائن التطبيق واحد على الأقل.

frame = wx.Frame(None, -1, 'simple.py')
frame.Show()

هنا قمنا بإنشاء كائن wx.Frame , إن ودجة wx.Frame تعتبر ودجة حاوية مهمة لذلك سنقوم بالتحدث عن هذه الودجة بالتفصيل بعد قليل. إن ودجة wx.Frame هي الودجة الأب للودجات الأُخرى , و ليس لها أب , إذا لم نحدد أي متغير للأب فنحن نقول أنه ليس لهذه الودجة أب , فهي تعتبر ودجة عليع في هرمية الودجات . بعد أن ننشئ ودجة wx.Frame يجب أن نستدعي الوظيفة Show() حتى نظهرا على الشاشة .

app.MainLoop()

يقوم السطر الأخير بإدخالنا إلى الحلقة الأساسية (mainloop) , الحلقة الأساسية هي دورة لا نهائية , تقوم بإلتقاط و إرسال كل حدث يتواجد أثناء تشغيل تطبيقنا.

ذلك كان مثال بسيط جداً , بالرغم من هذه البساطة نستطيع القيام يالكثير بهذه النافذة , نستطيع تعديل أبعاد النافذة و ملئ النافذة و تصغير النافذة , هذه العملية العملانية (الوظيفة) تتطلب الكثير من الكود , لكن ذلك كله متوفر و مخبئ عن طريق أدوات wxPython , لا حاجة لإختراع العجلة من جديد .

simple

ودجة wx.Frame

نعتبر ودجة wx.Frame من هم الودجات في wxPython , فهي ودجة حاوية , وهذا يعني أنها تحوي ودجات أُخرى . في الحقيقة هي تستطيع أن تحوي أي نافذة ليست frame أو صندوق حوار (dialog ) . تتكون wx.Frame من شريط العنوان و الحدود و المنطقة الحاوية المركزية . إن شريط العنوان و الحدود تعتبر اختيارية و يمكن ازالتها عن طريق الخيارات المتعددة.

إن لودجة wx.Frame البنية التالية , كما نرى إن لها سبعة متغيرات , لا يوجد للمتغير الأول قيمة افتراضية بينما يوجد للستة الآخرين قيمة افتراضية, المتغريات الثلاث الأولى إلزامية بينما الأربعة الأخرى اختيارية.

  wx.Frame(wx.Window parent, int id=-1, string title='', wx.Point pos = wx.DefaultPosition, 
  wx.Size size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, string name = "frame")

wx.DEFAULT_FRAME_STYLE is a set of default flags. wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN. عن طريق الجمع بين الأشكال المختلفة نستطيع الحصول أن نغير في شكل ودجة wx.Frame , في يلي مثال بسيط :

#!/usr/bin/python
 
# nominimizebox.py
 
import wx
 
app = wx.App()
window = wx.Frame(None, style=wx.MAXIMIZE_BOX | wx.RESIZE_BORDER 
	| wx.SYSTEM_MENU | wx.CAPTION |	 wx.CLOSE_BOX)
window.Show(True)
 
app.MainLoop()

في هذه المثال نريد أن نظهر النافذة بدون زر تكبير النافذة لذلك لم نقم بتحديد هذا المتغير ضمن متغيرات الشكل.

A window without a minimize box

المساحة والموضع

نستطيع ان نقوم بتحديد مساحة التطبيق بطريقتين 1- تحديد معامل المساحة فى المشيّد 2- استدعاء الطريقة SetSize

#!/usr/bin/python
 
# size.py
 
import wx
 
class Size(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 200))
 
        self.Show(True)
 
 
app = wx.App()
Size(None, -1, 'Size')
app.MainLoop()
 wx.Frame.__init__(self, parent, id, title, size=(250, 200))

فى المشيد نحدد العرض الى 250بكسل والطول الى 200 بكسل

بطريقة مشابهة نستطيع ان نموضع التطبيق على الشاشة، افتراضيا توضع النافذة بأعلى يسار الشاشة ولكن يمكن ان يختلف هذا تبع للمنصة او مدير النوافذ. يستطيع المبرمج التحكم فى الموضوع برمجيا.. لقد رأينا المعامل pos سابقا فى المشيّد للودجت wx.Frameوعبر تقدييم قيم اخرى غير الأفتراضية نستطيع التحكم فى الموضع بأنفسنا.

Method Description
‪ Move(wx.Point point) ‬ تنقل النافذة لموضع معين (بإستخدام كائن النقطة wx.Point)
‪ MoveXY(int x, int y) ‬ تنقل النافذة الى موضع (س، ص)
‪ SetPosition(wx.Point point) ‬ تحدد موضع النافذة
‪ SetDimensions(wx.Point point, wx.Size size) ‬ تحديد ابعاد النافذة.

يوجد العديد من الطرق لفعل هذا “القى قطعة نقود للإختيار:d”

#!/usr/bin/python
 
# move.py
 
import wx
 
class Move(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        self.Move((800, 250))
        self.Show(True)
 
 
app = wx.App()
Move(None, -1, 'Move')
app.MainLoop()

There is one particular situation. We might want to display our window maximized. In this case, the window is positioned at (0, 0) and takes the whole screen. wxPython internally calculates the screen coordinates. To maximize our wx.Frame, we call the Maximize() method. If we want to center our application on the screen, wxPython has a handy method. The Centre() method simply centers the window on the screen. No need to calculate the width and the height of the screen. Simply call the method.

#!/usr/bin/python
 
# centre.py
 
import wx
 
class Centre(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Centre(None, -1, 'Centre')
app.MainLoop()

ودجات تتواصل

مهم ان نعرف كيف يمكن ان تتواصل الودجات سويا.. تابع المثال التالى

#!/usr/bin/python
 
# communicate.py
 
import wx
 
 
class LeftPanel(wx.Panel):
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, style=wx.BORDER_SUNKEN)
 
        self.text = parent.GetParent().rightPanel.text
 
        button1 = wx.Button(self, -1, '+', (10, 10))
        button2 = wx.Button(self, -1, '-', (10, 60))
 
        self.Bind(wx.EVT_BUTTON, self.OnPlus, id=button1.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnMinus, id=button2.GetId())
 
    def OnPlus(self, event):
        value = int(self.text.GetLabel())
        value = value + 1
        self.text.SetLabel(str(value))
 
    def OnMinus(self, event):
        value = int(self.text.GetLabel())
        value = value - 1
        self.text.SetLabel(str(value))
 
 
class RightPanel(wx.Panel):
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, style=wx.BORDER_SUNKEN)
        self.text = wx.StaticText(self, -1, '0', (40, 60))
 
 
class Communicate(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(280, 200))
 
        panel = wx.Panel(self, -1)
        self.rightPanel = RightPanel(panel, -1)
 
        leftPanel = LeftPanel(panel, -1)
 
        hbox = wx.BoxSizer()
        hbox.Add(leftPanel, 1, wx.EXPAND | wx.ALL, 5)
        hbox.Add(self.rightPanel, 1, wx.EXPAND | wx.ALL, 5)
 
        panel.SetSizer(hbox) 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Communicate(None, -1, 'widgets communicate')
app.MainLoop()

فى المثال لدينا 2 panels واحدة على اليسار (تحوى زرين) والأخرى على اليمين (تحوى نص ساكن static text) . والزرين سيغيران الرقم الظاهر على النص الساكن.. السؤال الآن هو كيف نحصل على مرجع “reference” للنص الساكن ؟

اذا كانت كل الويدجات فى نفس الصف.. فهذا تافة ولكن اذا كانو فى صفوف مختلفة.. فنحصل على المراجع عن طريق الهيكلية.

 panel = wx.Panel(self, -1)
 self.rightPanel = RightPanel(panel, -1)
 
 leftPanel = LeftPanel(panel, -1)

لاحظ ان ال panel اليمنى يجب ان تعرف قبل اليسرى.. لأننا عند انشاء اليسرى نبحث عن تعريف النص الساكن اللذى تم تعريفه فى اليمنى. فمنطقيا لا نستطيع الحصول على مرجع لودجت غير موجود بعد!

 self.text = parent.GetParent().rightPanel.text

الإجابة هنا.. كل ويدجت لديه معامل للأب وفى مثالنا الأب هو panel نضع عليها ال panel اليمنى واليسرى.. بإستدعاء الطريقة GetParent نحصل على مرجع لل frame اللتى تحوى مرجع للpanel اليمنى والتى تحوى مرجع للنص الساكن.

how widgets communicate

القوائم و أشرطة الأدوات في wxPython

Creating a MenuBar

A menubar is one of the most visible parts of the GUI application. It is a group of commands located in various menus. While in console applications you had to remember all those arcane commands, here we have most of the commands grouped into logical parts. There are accepted standards that further reduce the amount of time spending to learn a new application. To implement a menubar in wxPython we need to have three things. A wx.MenuBar, a wx.Menu and a wx.MenuItem.

Menu objects

A Simple menu example

Creating a menubar in wxPython is very simple. Just a few lines of code.

#!/usr/bin/python
 
# simplemenu.py
 
import wx
 
class SimpleMenu(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        file.Append(-1, 'Quit', 'Quit application')
        menubar.Append(file, '&File')
        self.SetMenuBar(menubar)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
SimpleMenu(None, -1, 'simple menu example')
app.MainLoop()
 menubar = wx.MenuBar()

First we create a menubar object.

 file = wx.Menu()

Next we create a menu object.

 file.Append(-1, 'Quit', 'Quit application')

We append a menu item into the menu object. The first parameter is the id of the menu item. The second parameter is the name of the menu item. The last parameter defines the short helpstring that is displayed on the statusbar, when the menu item is selected. Here we did not create a wx.MenuItem explicitely. It was created by the Append() method behind the scenes. Later on, we will create a wx.MenuItem manually.

 menubar.Append(file, '&File')
 self.SetMenuBar(menubar)

After that, we append a menu into the menubar. The &amp character creates an accelerator key. The character that follows the &amp is underlined. This way the menu is accessible via the alt + F shortcut. In the end, we call the SetMenuBar() method. This method belongs to the wx.Frame widget. It sets up the menubar.

A simple menu example

A dockable menubar

Under Linux, we can create a dockable menubar. This feature is not commonly seen in applications. But similar thing can be seen on Mac OS. Mac users do not have a menubar in the toplevet application window. The menubar is implemented outside the main window.

#!/usr/bin/python
 
# dockable.py
 
import wx
 
class Dockable(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        menubar = wx.MenuBar(wx.MB_DOCKABLE)
        file = wx.Menu()
        edit = wx.Menu()
        view = wx.Menu()
        insr = wx.Menu()
        form = wx.Menu()
        tool = wx.Menu()
        help = wx.Menu()
 
        menubar.Append(file, '&File')
        menubar.Append(edit, '&Edit')
        menubar.Append(view, '&View')
        menubar.Append(insr, '&Insert')
        menubar.Append(form, '&Format')
        menubar.Append(tool, '&Tools')
        menubar.Append(help, '&Help')
        self.SetMenuBar(menubar)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
Dockable(None, -1, 'Dockable menubar')
app.MainLoop()
 menubar = wx.MenuBar(wx.MB_DOCKABLE)

We create a dockable menubar by providing a wx.MB_DOCKABLE flag to the constructor.

A dockable menubar

Icons, shortcuts, events

In the next section we will further enhance our menu example. We will see, how we can add icons to our menus. Icons make our applications more visually attractive. Further, they help us understand the menu commands. We will see, how we can add shortcuts to our menus. Shortcuts are not a relict from the past. They enable us to work more quickly with our applications. One of the most widely used shortcut is the Ctrl + S one. There are not many people, that would not know the meaning of this shortcut. It is more handy to press this shortcut, than to move a mouse pointer to the menubar, click a File menu and select the Save commnand. Shortcuts are a productivity boost to most users.

We will also briely touch events.

#!/usr/bin/python
 
# menuexample.py
 
import wx
 
class MenuExample(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        quit = wx.MenuItem(file, 1, '&Quit\tCtrl+Q')
        quit.SetBitmap(wx.Bitmap('icons/exit.png'))
        file.AppendItem(quit)
 
        self.Bind(wx.EVT_MENU, self.OnQuit, id=1)
 
        menubar.Append(file, '&File')
        self.SetMenuBar(menubar)
 
        self.Centre()
        self.Show(True)
 
    def OnQuit(self, event):
        self.Close()
 
app = wx.App()
MenuExample(None, -1, '')
app.MainLoop()
 quit = wx.MenuItem(file, 1, '&Quit\tCtrl+Q')
 quit.SetBitmap(wx.Bitmap('icons/exit.png'))
 file.AppendItem(quit)

If we want to add shortcuts and icons to our menus, we have to manually create a wx.MenuItem. So far we have created menuitems indirectly. The &amp character specifies an accelerator key. The following character is underlined. The actual shortcut is defined by the combination of characters. We have specified Ctrl + Q characters. So if we press Ctrl + Q, we close the application. We put a tab character between the &amp character and the shortcut. This way, we manage to put some space between them. To provide an icon for a menuitem, we call a SetBitmap() method. A manually created menuitem is appended to the menu by calling the AppendItem() method.

 self.Bind(wx.EVT_MENU, self.OnQuit, id=1)

If we select a quit menu item or press a keyboard shortcut, a wx.EVT_MENU event is generated. We bind an event handler to the event. The event handler is a method, that is being called. In our example, the OnQuit() method closes the application. There can be several menuitems, so we have to give a unique id to each of them. Working with events is very easy and straightforward in wxPython. We will talk about events in a separate chapter.

A menu example

Each menu can also have a submenu. This way we can group similar commands into groups. For example we can place commands that hide/show various toolbars like personal bar, address bar, status bar or navigation bar into a submenu called toolbars. Within a menu, we can seperate commands with a separator. It is a simple line. It is common practice to separate commands like new, open, save from commands like print, print preview with a single separator. In our example we will see, how we can create submenus and menu separators.

#!/usr/bin/python
 
# submenu.py
 
import wx
 
ID_QUIT = 1
 
class SubmenuExample(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 250))
 
        menubar = wx.MenuBar()
 
        file = wx.Menu()
        file.Append(-1, '&New')
        file.Append(-1, '&Open')
        file.Append(-1, '&Save')
        file.AppendSeparator()
 
        imp = wx.Menu()
        imp.Append(-1, 'Import newsfeed list...')
        imp.Append(-1, 'Import bookmarks...')
        imp.Append(-1, 'Import mail...')
 
        file.AppendMenu(-1, 'I&mport', imp)
 
        quit = wx.MenuItem(file, ID_QUIT, '&Quit\tCtrl+W')
        quit.SetBitmap(wx.Bitmap('icons/exit.png'))
        file.AppendItem(quit)
 
        self.Bind(wx.EVT_MENU, self.OnQuit, id=ID_QUIT)
 
        menubar.Append(file, '&File')
        self.SetMenuBar(menubar)
 
        self.Centre()
        self.Show(True)
 
    def OnQuit(self, event):
        self.Close()
 
app = wx.App()
SubmenuExample(None, -1, 'Submenu')
app.MainLoop()
 file.AppendSeparator()

A menu separator is appended with the AppendSeparator() method.

 imp = wx.Menu()
 imp.Append(-1, 'Import newsfeed list...')
 imp.Append(-1, 'Import bookmarks...')
 imp.Append(-1, 'Import mail...')
 
 file.AppendMenu(-1, 'I&mport', imp)

Creating a submenu is trivial. First, we create a menu. Then we append menu items. A submenu is created by calling the AppenMenu() on the menu object.

A submenu example

Various menu items

There are tree kinds of menu items.

  • normal item
  • check item
  • radio item
#!/usr/bin/python
 
# checkmenuitem.py
 
import wx
 
ID_STAT = 1
ID_TOOL = 2
 
class CheckMenuItem(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 250))
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        view = wx.Menu()
        self.shst = view.Append(ID_STAT, 'Show statubar', 'Show Statusbar', kind=wx.ITEM_CHECK)
        self.shtl = view.Append(ID_TOOL, 'Show toolbar', 'Show Toolbar', kind=wx.ITEM_CHECK)
        view.Check(ID_STAT, True)
        view.Check(ID_TOOL, True)
 
        self.Bind(wx.EVT_MENU, self.ToggleStatusBar, id=ID_STAT)
        self.Bind(wx.EVT_MENU, self.ToggleToolBar, id=ID_TOOL)
 
        menubar.Append(file, '&File')
        menubar.Append(view, '&View')
        self.SetMenuBar(menubar)
 
        self.toolbar = self.CreateToolBar()
        self.toolbar.AddLabelTool(3, '', wx.Bitmap('icons/quit.png'))
        self.toolbar.Realize()
 
        self.statusbar = self.CreateStatusBar()
        self.Centre()
        self.Show(True)
 
    def ToggleStatusBar(self, event):
        if self.shst.IsChecked():
            self.statusbar.Show()
        else:
            self.statusbar.Hide()
 
    def ToggleToolBar(self, event):
        if self.shtl.IsChecked():
            self.toolbar.Show()
        else:
            self.toolbar.Hide()
 
app = wx.App()
CheckMenuItem(None, -1, 'check menu item')
app.MainLoop()
 self.shst = view.Append(ID_STAT, 'Show statubar', 'Show Statusbar', kind=wx.ITEM_CHECK)
 self.shtl = view.Append(ID_TOOL, 'Show toolbar', 'Show Toolbar', kind=wx.ITEM_CHECK)

If we want to append a check menu item, we set a kind parameter to wx.ITEM_CHECK. The default parameter is wx.ITEM_NORMAL. The Append() method returns a wx.MenuItem.

 view.Check(ID_STAT, True)
 view.Check(ID_TOOL, True)

When the application starts, both statusbar and toolbar are visible. So we check both menu items with the Check() method.

 def ToggleStatusBar(self, event):
     if self.shst.IsChecked():
         self.statusbar.Show()
     else:
         self.statusbar.Hide()

We show or hide the statusbar according to the state of the check menu item. We find out the state of the check menu item with the IsChecked() method. Same with toolbar.

Check menu item

Context menu

It is a list of commands that appears under some context. For example, in a Firefox web browser, when we right click on a web page, we get a context menu. Here we can reload a page, go back or view page source. If we right click on a toolbar, we get another context menu for managing toolbars. Context menus are sometimes called popup menus.

#!/usr/bin/python
 
# contextmenu.py
 
import wx
 
 
class MyPopupMenu(wx.Menu):
    def __init__(self, parent):
        wx.Menu.__init__(self)
 
        self.parent = parent
 
        minimize = wx.MenuItem(self, wx.NewId(), 'Minimize')
        self.AppendItem(minimize)
        self.Bind(wx.EVT_MENU, self.OnMinimize, id=minimize.GetId())
 
        close = wx.MenuItem(self, wx.NewId(), 'Close')
        self.AppendItem(close)
        self.Bind(wx.EVT_MENU, self.OnClose, id=close.GetId())
 
 
    def OnMinimize(self, event):
        self.parent.Iconize()
 
    def OnClose(self, event):
        self.parent.Close()
 
 
class ContextMenu(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
 
        self.Center()
        self.Show()
 
    def OnRightDown(self, event):
        self.PopupMenu(MyPopupMenu(self), event.GetPosition())
 
 
app = wx.App()
frame = ContextMenu(None, -1, 'context menu')
app.MainLoop()
 class MyPopupMenu(wx.Menu):
     def __init__(self, parent):
         wx.Menu.__init__(self)

We create a separate wx.Menu class. Here we define two commands. Close and minimize window.

 self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

If we right click on the frame, we call the OnRightDown() method. For this, we use the wx.EVT_RIGHT_DOWN event binder.

 def OnRightDown(self, event):
     self.PopupMenu(MyPopupMenu(self), event.GetPosition())

In the OnRightDown() method, we call the PopupMenu() method. This method shows the context menu. The first parameter is the menu to be shown. The second parameter is the position, where the context menu appears. The context menus appear at the point of the mouse cursor. To get the actual mouse position, we call the GetPosition() menthod.

Toolbars

Menus group all commands that we can use in an application. Toolbars provide a quick access to the most frequently used commands.

 CreateToolBar(long style=-1, int winid=-1, String name=ToolBarNameStr)

To create a toolbar, we call the CreateToolBar() method of the frame widget.

#!/usr/bin/python
 
# simpletoolbar.py
 
import wx
 
class SimpleToolbar(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(300, 200))
 
        toolbar = self.CreateToolBar()
        toolbar.AddLabelTool(wx.ID_EXIT, '', wx.Bitmap('../icons/exit.png'))
	toolbar.Realize()
 
        self.Bind(wx.EVT_TOOL, self.OnExit, id=wx.ID_EXIT)
 
        self.Centre()
        self.Show(True)
 
    def OnExit(self, event):
        self.Close()
 
 
app = wx.App()
SimpleToolbar(None, -1, 'simple toolbar')
app.MainLoop()
 toolbar.AddLabelTool(wx.ID_EXIT, '', wx.Bitmap('../icons/exit.png'))

To create a toolbar button, we call the AddLabelTool() method.

 toolbar.Realize()

After we have put our items to the toolbar, we call the Realize() method. Calling this method is not obligatory on Linux. On windows it is.

 self.Bind(wx.EVT_TOOL, self.OnExit, id=wx.ID_EXIT)

To handle toolbar events, we use the wx.EVT_TOOL event binder.

simple toolbar

If we want to create more than one toolbars, we must do it differently.

#!/usr/bin/python
 
# toolbars.py
 
import wx
 
class Toolbars(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(300, 200))
 
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        toolbar1 = wx.ToolBar(self, -1)
        toolbar1.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/new.png'))
        toolbar1.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/open.png'))
        toolbar1.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/save.png'))
        toolbar1.Realize()
 
        toolbar2 = wx.ToolBar(self, -1)
        toolbar2.AddLabelTool(wx.ID_EXIT, '', wx.Bitmap('../icons/exit.png'))
        toolbar2.Realize()
 
        vbox.Add(toolbar1, 0, wx.EXPAND)
        vbox.Add(toolbar2, 0, wx.EXPAND)
 
        self.Bind(wx.EVT_TOOL, self.OnExit, id=wx.ID_EXIT)
 
        self.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
    def OnExit(self, event):
        self.Close()
 
 
app = wx.App()
Toolbars(None, -1, 'toolbars')
app.MainLoop()
 toolbar1 = wx.ToolBar(self, -1)
 ...
 toolbar2 = wx.ToolBar(self, -1)

We create two toolbar objects. And put them into a vertical box.

toolbars

Sometimes we need to create a vertical toolbar. Vertical toolbars are often seen in graphics applications like Inkscape or Xara Xtreme.

#!/usr/bin/python
 
# verticaltoolbar.py
 
import wx
 
class VerticalToolbar(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(240, 300))
 
        toolbar = self.CreateToolBar(wx.TB_VERTICAL)
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/select.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/freehand.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/shapeed.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/pen.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/rectangle.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/ellipse.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/qs.gif'))
        toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/text.gif'))
 
        toolbar.Realize()
 
 
        self.Centre()
        self.Show(True)
 
    def OnExit(self, event):
        self.Close()
 
 
app = wx.App()
VerticalToolbar(None, -1, 'vertical toolbar')
app.MainLoop()
 toolbar = self.CreateToolBar(wx.TB_VERTICAL)

Here we create a vertical toolbar.

 toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/select.gif'))
 toolbar.AddLabelTool(wx.ID_ANY, '', wx.Bitmap('../icons/freehand.gif'))
 ...

I have borrowed icons from the Xara Xtreme graphics application.

verticaltoolbar

In the following example, we will show, how we can enable and disable toolbar buttons. We will also see a separator line.

#!/usr/bin/python
 
# enabledisable.py
 
import wx
 
class EnableDisable(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        self.count = 5
 
        self.toolbar = self.CreateToolBar()
        self.toolbar.AddLabelTool(wx.ID_UNDO, '', wx.Bitmap('../icons/undo.png'))
        self.toolbar.AddLabelTool(wx.ID_REDO, '', wx.Bitmap('../icons/redo.png'))
        self.toolbar.EnableTool(wx.ID_REDO, False)
        self.toolbar.AddSeparator()
        self.toolbar.AddLabelTool(wx.ID_EXIT, '', wx.Bitmap('../icons/exit.png'))
        self.toolbar.Realize()
 
        self.Bind(wx.EVT_TOOL, self.OnExit, id=wx.ID_EXIT)
        self.Bind(wx.EVT_TOOL, self.OnUndo, id=wx.ID_UNDO)
        self.Bind(wx.EVT_TOOL, self.OnRedo, id=wx.ID_REDO)
 
        self.Centre()
        self.Show(True)
 
    def OnUndo(self, event):
        if self.count > 1 and self.count <= 5:
            self.count = self.count - 1
 
        if self.count == 1:
            self.toolbar.EnableTool(wx.ID_UNDO, False)
 
        if self.count == 4:
            self.toolbar.EnableTool(wx.ID_REDO, True)
 
    def OnRedo(self, event):
        if self.count < 5 and self.count >= 1:
            self.count = self.count + 1
 
        if self.count == 5:
            self.toolbar.EnableTool(wx.ID_REDO, False)
 
        if self.count == 2:
            self.toolbar.EnableTool(wx.ID_UNDO, True)
 
    def OnExit(self, event):
        self.Close()
 
app = wx.App()
EnableDisable(None, -1, 'enable disable')
app.MainLoop()

In our example, we have three toolbar buttons. One button is for exiting the application. The other two buttons are undo and redo buttons. They simulate undo/redo functionality in an application. (for a real example, see tips and tricks) We have 4 changes. The undo and redo butons are disabled accordingly.

 self.toolbar.EnableTool(wx.ID_REDO, False)
 self.toolbar.AddSeparator()

In the beginning, the redo button is disabled. We do it by calling the EnableTool() method. We can create some logical groups within a toolbar. We can separate various groups of buttons by a small vertical line. To do this, we call the AddSeparator() method.

 def OnUndo(self, event):
     if self.count > 1 and self.count <= 5:
         self.count = self.count - 1
 
     if self.count == 1:
         self.toolbar.EnableTool(wx.ID_UNDO, False)
 
     if self.count == 4:
         self.toolbar.EnableTool(wx.ID_REDO, True)

We simulate undo and redo functionality. We have 4 changes. If there is nothing left to undo, the undo button is disabled. After undoing the first change, we enable the redo button. Same logic applies for the OnRedo() method.

enabledisable example

</html>

ادارة المخططات فى wxPython

إن إدارة المخطط (توزيع المساحات في واجهة البرنامج) أمر مهم، فعن طريقها نقوم بتحديد طريقة توزيع الودجات على النافذة (مكانا وحجما).

نستطيع إدارتها بطريقين: الموقع المطلق أو استخدام احد صنوف التخطيط layout classes

الموضع المطلق

وهي أن يحدد المبرمج مكان وحجم كل ودجة بوحدة بكسل، عند استخدامك للتحديد المطلق يجب ان تعلم عدة اشياء:

  • عند اعادة تحجيم النافذة ، لا يغيّر مكان وحجم الودجة تبعا لذلك
  • التطبيق قد يختلف شكله على المنصات المختلفة
  • تغيّر الخطوط قد يفسد تصميم المخطط
  • إذا قررت تغيّر المخطط، فإنك ستحتاج الى إعادة تنظيمه بالكامل وهو أمر ممل ويأخذ وقتا

فى مثالنا ننشئ هيكل لمحرر نصوص.. اذا قمنا بإعادة تحجيم النافذة.. سنجد ان مساحة الودجت wx.TextCtrl لاتتعدل كما نتوقع

#!/usr/bin/python
 
# absolute.py
 
import wx
 
class Absolute(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 180))
        panel = wx.Panel(self, -1)
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        edit = wx.Menu()
        help = wx.Menu()
 
        menubar.Append(file, '&File')
        menubar.Append(edit, '&Edit')
        menubar.Append(help, '&Help')
        self.SetMenuBar(menubar)
 
        wx.TextCtrl(panel, -1, pos=(-1, -1), size=(250, 150))
 
        self.Centre()
        self.Show(True)
 
app = wx.App(0)
Absolute(None, -1, '')
app.MainLoop()
 wx.TextCtrl(panel, -1, pos=(-1, -1), size=(250, 150))

نقوم بتحديد الموضع فى المشيدّ الخاص بالودجت wx.TextCtrl. فى حالتنا نقوم بتحديد الموقع الإفتراضى. العرض 250 بكسل والإرتفاع 150 بكسل

Using sizers

المحجمات تخاطب تلك المشكلات اللتى ذكرناها فى الموضع المطلق.ومنها:

  • wx.BoxSizer
  • wx.StaticBoxSizer
  • wx.GridSizer
  • wx.FlexGridSizer
  • wx.GridBagSizer

#!/usr/bin/python
 
# sizer.py
 
import wx
 
class Sizer(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 180))
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        edit = wx.Menu()
        help = wx.Menu()
 
        menubar.Append(file, '&File')
        menubar.Append(edit, '&Edit')
        menubar.Append(help, '&Help')
        self.SetMenuBar(menubar)
 
        wx.TextCtrl(self, -1)
 
        self.Centre()
        self.Show(True)
 
app = wx.App(0)
Sizer(None, -1, '')
app.MainLoop()

حسنا، اتقول اننا لانرى المججمات فى المثال؟ همم ، هذا الكود مخادع قليلا. فى الواقع لقد وضعنا اداة wx.TextCtrl داخل wx.Frame -الذى يحوى محجّم داخلى- يسمح لنا بوضع ويدجت واحد فقط ليشغل كل المساحة داخل الحاوية wx.Frame

wx.BoxSizer

هذا المحجّم يسمح لنا بوضح عدة ويدجات فى صف او عمود. نستطيع ايضا ان نضيف محجّم داخل محجّم اخر مما يسمح لنا بإنشاء محططات معقدة جدا.

 box = wx.BoxSizer(integer orient)
 box.Add(wx.Window window, integer proportion=0, integer flag = 0, integer border = 0)

الإتجاة -orientation- ربما يكون رأسى wx.VERTICAL او افقى wx.HORIZONTAL واضافة الويدجات الى المحجم يتم عن طريق الطريقة Add -واللتى لفهمها سنحتاج القاء نظره على معاملاتها

معامل proportion بيحدد النسبة اللتى بناء عليها سيتم تقسيم المساحة. على فرض لدينا 3 ازرار بقيم 0و 1 و 2 لل proportion وسيتم اضافتهم لصندوق افقى الزر صاحب القيمة 0 لن يتم حدوث اى تغيير له فعند التغيير سيتغير صاحب القيمة 2 بضعف صاحب القيمة 1 فى الأفقى

مع المعامل flag نستطيع ان نحدد سلوك الويدجات داخل المحجّم. نستطيع تحديد الحدود بين الويدجات. نضيف بعض المساحة بين الويدجات -بالبكسل- ولتطبيق الحدود يجب ان نضيفها بالمعامل | ك wx.LEFT|wx.BOTTOM ونستطيع اختيار قيم من:

  • wx.LEFT
  • wx.RIGHT
  • wx.BOTTOM
  • wx.TOP
  • wx.ALL

#!/usr/bin/python
 
# border.py
 
import wx
 
class Border(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 200))
 
        panel = wx.Panel(self, -1)
        panel.SetBackgroundColour('#4f5049')
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        midPan = wx.Panel(panel, -1)
        midPan.SetBackgroundColour('#ededed')
 
        vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 20)
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
app = wx.App()
Border(None, -1, '')
app.MainLoop()
vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 20)

فى المثال اضفنا حدود بمقدار 20 بكسل حول البانل midPan. وwx.ALL تطبق الحد على الأربع جوانب.

نستخدم wx.EXPAND حتى يأخذ الويدجت كل المساحة المخصصة له. اخيرا نستطيع تعريف المحاذاة للويدجات عن طريق :

  • wx.ALIGN_LEFT
  • wx.ALIGN_RIGHT
  • wx.ALIGN_TOP
  • wx.ALIGN_BOTTOM
  • wx.ALIGN_CENTER_VERTICAL
  • wx.ALIGN_CENTER_HORIZONTAL
  • wx.ALIGN_CENTER

Go To Class

فى المثال التالى سنقدم بعض الأفكار الهامة.

#!/usr/bin/python
 
# gotoclass.py
 
import wx
 
class GoToClass(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(390, 350))
        panel = wx.Panel(self, -1)
 
        font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
        font.SetPointSize(9)
 
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        st1 = wx.StaticText(panel, -1, 'Class Name')
        st1.SetFont(font)
        hbox1.Add(st1, 0, wx.RIGHT, 8)
        tc = wx.TextCtrl(panel, -1)
        hbox1.Add(tc, 1)
        vbox.Add(hbox1, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
 
        vbox.Add((-1, 10))
 
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        st2 = wx.StaticText(panel, -1, 'Matching Classes')
        st2.SetFont(font)
        hbox2.Add(st2, 0)
        vbox.Add(hbox2, 0, wx.LEFT | wx.TOP, 10)
 
        vbox.Add((-1, 10))
 
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        tc2 = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
        hbox3.Add(tc2, 1, wx.EXPAND)
        vbox.Add(hbox3, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)
 
        vbox.Add((-1, 25))
 
        hbox4 = wx.BoxSizer(wx.HORIZONTAL)
        cb1 = wx.CheckBox(panel, -1, 'Case Sensitive')
        cb1.SetFont(font)
        hbox4.Add(cb1)
        cb2 = wx.CheckBox(panel, -1, 'Nested Classes')
        cb2.SetFont(font)
        hbox4.Add(cb2, 0, wx.LEFT, 10)
        cb3 = wx.CheckBox(panel, -1, 'Non-Project classes')
        cb3.SetFont(font)
        hbox4.Add(cb3, 0, wx.LEFT, 10)
        vbox.Add(hbox4, 0, wx.LEFT, 10)
 
        vbox.Add((-1, 25))
 
        hbox5 = wx.BoxSizer(wx.HORIZONTAL)
        btn1 = wx.Button(panel, -1, 'Ok', size=(70, 30))
        hbox5.Add(btn1, 0)
        btn2 = wx.Button(panel, -1, 'Close', size=(70, 30))
        hbox5.Add(btn2, 0, wx.LEFT | wx.BOTTOM , 5)
        vbox.Add(hbox5, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)
 
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
 
app = wx.App()
GoToClass(None, -1, 'Go To Class')
app.MainLoop()

المخطط مباشر. قمنا بإنشاء محجّم رأسى ووضعنا فيه 5 محجّمات افقية.

 font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
 font.SetPointSize(9)

الخط الإفتراضى المستخدم 10 بكسل، قمت بتعديله ل 9 حتى يناسبنى اكثر

 vbox.Add(hbox3, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)
 
 vbox.Add((-1, 25))

نعلم اننا نستطيع التحكم فى المسافة بين الودجات بدمج المعامل flag بمعامل الحدود border

فى الطريقة Add نستطيع ان نحدد border واحد لكل الجوانب.. فى مثالنا اعطينا 10 بكسل لليسار واليمين.. ولكن لانستطيع اعطاء 25 للأسفل.. نستطيع استخدام الطريقة Add لإضافة ويدجات و“مسافات” ايضا!

vbox.Add(hbox5, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)

We place the two buttons on the right side of the window. How do we do it? Three things are important to achieve this. The proportion, the align flag and the wx.EXPAND flag. The proportion must be zero. The buttons should not change their size, when we resize our window. We must not specify wx.EXPAND flag. The buttons occopy only the area that has been alotted to it. And finally, we must specify the wx.ALIGN_RIGHT flag. The horizontal sizer spreads from the left side of the window to the right side. So if we specify wx.ALIGN_RIGHT flag, the buttons are placed to the right side. Exactly, as we wanted.

Find/Replace Dialog

فى مثالنا سننشئ صندوق حوارى find/replace –من ذلك النوع اللذى قد تجده فى Eclipse IDE

#!/usr/bin/python
 
# Find/Replace Dialog
 
import wx
 
class FindReplace(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(255, 365))
 
        vbox_top = wx.BoxSizer(wx.VERTICAL)
        panel = wx.Panel(self, -1)
 
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        # panel1
 
        panel1 = wx.Panel(panel, -1)
        grid1 = wx.GridSizer(2, 2)
        grid1.Add(wx.StaticText(panel1, -1, 'Find: ', (5, 5)), 0,  wx.ALIGN_CENTER_VERTICAL)
        grid1.Add(wx.ComboBox(panel1, -1, size=(120, -1)))
        grid1.Add(wx.StaticText(panel1, -1, 'Replace with: ', (5, 5)), 0, wx.ALIGN_CENTER_VERTICAL)
        grid1.Add(wx.ComboBox(panel1, -1, size=(120, -1)))
 
        panel1.SetSizer(grid1)
        vbox.Add(panel1, 0, wx.BOTTOM | wx.TOP, 9)
 
        # panel2
 
        panel2 = wx.Panel(panel, -1)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
 
        sizer21 = wx.StaticBoxSizer(wx.StaticBox(panel2, -1, 'Direction'), orient=wx.VERTICAL)
        sizer21.Add(wx.RadioButton(panel2, -1, 'Forward', style=wx.RB_GROUP))
        sizer21.Add(wx.RadioButton(panel2, -1, 'Backward'))
        hbox2.Add(sizer21, 1, wx.RIGHT, 5)
 
        sizer22 = wx.StaticBoxSizer(wx.StaticBox(panel2, -1, 'Scope'), orient=wx.VERTICAL)
        # we must define wx.RB_GROUP style, otherwise all 4 RadioButtons would be mutually exclusive
        sizer22.Add(wx.RadioButton(panel2, -1, 'All', style=wx.RB_GROUP))
        sizer22.Add(wx.RadioButton(panel2, -1, 'Selected Lines'))
        hbox2.Add(sizer22, 1)
 
        panel2.SetSizer(hbox2)
        vbox.Add(panel2, 0, wx.BOTTOM, 9)
 
        # panel3
 
        panel3 = wx.Panel(panel, -1)
        sizer3 = wx.StaticBoxSizer(wx.StaticBox(panel3, -1, 'Options'), orient=wx.VERTICAL)
        vbox3 = wx.BoxSizer(wx.VERTICAL)
        grid = wx.GridSizer(3, 2, 0, 5)
        grid.Add(wx.CheckBox(panel3, -1, 'Case Sensitive'))
        grid.Add(wx.CheckBox(panel3, -1, 'Wrap Search'))
        grid.Add(wx.CheckBox(panel3, -1, 'Whole Word'))
        grid.Add(wx.CheckBox(panel3, -1, 'Incremental'))
        vbox3.Add(grid)
        vbox3.Add(wx.CheckBox(panel3, -1, 'Regular expressions'))
        sizer3.Add(vbox3, 0, wx.TOP, 4)
 
        panel3.SetSizer(sizer3)
        vbox.Add(panel3, 0, wx.BOTTOM, 15)
 
        # panel4
 
        panel4 = wx.Panel(panel, -1)
        sizer4 = wx.GridSizer(2, 2, 2, 2)
        sizer4.Add(wx.Button(panel4, -1, 'Find', size=(120, -1)))
        sizer4.Add(wx.Button(panel4, -1, 'Replace/Find', size=(120, -1)))
        sizer4.Add(wx.Button(panel4, -1, 'Replace', size=(120, -1)))
        sizer4.Add(wx.Button(panel4, -1, 'Replace All', size=(120, -1)))
 
        panel4.SetSizer(sizer4)
        vbox.Add(panel4, 0, wx.BOTTOM, 9)
 
        # panel5
 
        panel5 = wx.Panel(panel, -1)
        sizer5 = wx.BoxSizer(wx.HORIZONTAL)
        sizer5.Add((191, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT)
        sizer5.Add(wx.Button(panel5, -1, 'Close', size=(50, -1)))
 
        panel5.SetSizer(sizer5)
        vbox.Add(panel5, 1, wx.BOTTOM, 9)
 
        vbox_top.Add(vbox, 1, wx.LEFT, 5)
        panel.SetSizer(vbox_top)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
 
app = wx.App()
FindReplace(None, -1, 'Find/Replace')
app.MainLoop()

(Remark for Windows users, put self.SetClientSize(panel.GetBestSize()) line before the ShowModal() method.)

قبل ان نكتب المخطط، يجب ان نعرف كيف سنحقق ذلك الهدف.. رسم مبسط للنافذة او الصندوق الحوار ربما يساعد. اذا نظرنا للصندوق الحوارى نجد اننا يمكن ان نقسمه ل 5 اقسام. زر close سيكون له panel منفصلة. كل من الأجزاء هو كائن فريد من wx.Panel ومعا يكون لدينا 6 كائنات من wx.Panel اولهم هى الأب التى ستحوى باقى ال 5

ال 5 سيقعو فى عمود واحد.. لذا الpanel الأب ستحوى محجّم رأسى. -بعيدا عن المحجّم الرأسى.. سنستخدم wx.GridSizer –سنشرحة فى القسم القادم- ليس هناك الكثير حوله لأنه مباشر جدا

 sizer4 = wx.GridSizer(2, 2, 2, 2)
 sizer4.Add(wx.Button(panel4, -1, 'Find', size=(120, -1)))
 sizer4.Add(wx.Button(panel4, -1, 'Replace/Find', size=(120, -1)))
 sizer4.Add(wx.Button(panel4, -1, 'Replace', size=(120, -1)))
 sizer4.Add(wx.Button(panel4, -1, 'Replace All', size=(120, -1)))

فى مثالنا wx.GridSizer مفيد جدا .. لدينا 4 ازرار على panel معينة ونريد تقسيمهم فى خلايا شبكة “صفوف واعمدة” كل من تلك الخلايا سيكون لها نفس المساحة والعرض

wx.GridSizer

ذلك المجّم هو جدول -شبكة “صفوف وأعمدة”- كل خليه منه ذات حجم ثابت

 wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)

المشيد يأخذ عدد الصفوف والأعمدة والمسافات الرأسية والأفقية بين الخلايا

فى مثالنا ننشئ هيكل آلة حاسبة

#!/usr/bin/python
 
# gridsizer.py
 
import wx
 
class GridSizer(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(300, 250))
 
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        file.Append(1, '&Quit', 'Exit Calculator')
        menubar.Append(file, '&File')
        self.SetMenuBar(menubar)
 
        self.Bind(wx.EVT_MENU, self.OnClose, id=1)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.display = wx.TextCtrl(self, -1, '',  style=wx.TE_RIGHT)
        sizer.Add(self.display, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 4)
        gs = wx.GridSizer(4, 4, 3, 3)
 
        gs.AddMany( [(wx.Button(self, -1, 'Cls'), 0, wx.EXPAND),
            (wx.Button(self, -1, 'Bck'), 0, wx.EXPAND),
            (wx.StaticText(self, -1, ''), 0, wx.EXPAND),
            (wx.Button(self, -1, 'Close'), 0, wx.EXPAND),
            (wx.Button(self, -1, '7'), 0, wx.EXPAND),
            (wx.Button(self, -1, '8'), 0, wx.EXPAND),
            (wx.Button(self, -1, '9'), 0, wx.EXPAND),
            (wx.Button(self, -1, '/'), 0, wx.EXPAND),
            (wx.Button(self, -1, '4'), 0, wx.EXPAND),
            (wx.Button(self, -1, '5'), 0, wx.EXPAND),
            (wx.Button(self, -1, '6'), 0, wx.EXPAND),
            (wx.Button(self, -1, '*'), 0, wx.EXPAND),
            (wx.Button(self, -1, '1'), 0, wx.EXPAND),
            (wx.Button(self, -1, '2'), 0, wx.EXPAND),
            (wx.Button(self, -1, '3'), 0, wx.EXPAND),
            (wx.Button(self, -1, '-'), 0, wx.EXPAND),
            (wx.Button(self, -1, '0'), 0, wx.EXPAND),
            (wx.Button(self, -1, '.'), 0, wx.EXPAND),
            (wx.Button(self, -1, '='), 0, wx.EXPAND),
            (wx.Button(self, -1, '+'), 0, wx.EXPAND) ])
 
        sizer.Add(gs, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.Centre()
        self.Show(True)
 
    def OnClose(self, event):
        self.Close()
 
app = wx.App()
GridSizer(None, -1, 'GridSizer')
app.MainLoop()

لاحظ كيف وضعنا مسافة بين Bck و Close .. قمنا بوضع wx.StaticText فارغ

فى مثالنا استخدمنا الطريقة AddMany –وهى طريقة مريحة لإضافة مجموعة من الودجات مرة واحدة.

 AddMany(list items)

الودجات توضع فى الجدول بالترتيب.. الصف الأول يملء اولا ثم الثانى.. الخ.

wx.FlexGridSizer

This sizer is similar to wx.GridSizer. It does also lay out it's widgets in a two dimensional table. It adds some flexibility to it. wx.GridSizer cells are of the same size. All cells in wx.FlexGridSizer have the same height in a row. All cells have the same width in a column. But all rows and columns are not necessarily the same height or width.

 wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)

rows and cols specify the number of rows and columns in a sizer. vgap and hgap add some space between widgets in both directions.

Many times developers have to develop dialogs for data input and modification. I find wx.FlexGridSizer suitable for such a task. A developer can easily set up a dialog window with this sizer. It is also possible to accomplish this with wx.GridSizer, but it would not look nice, because of the constraint that each cell has the same size.

#!/usr/bin/python
 
# flexgridsizer.py
 
import wx
 
class FlexGridSizer(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(290, 250))
 
        panel = wx.Panel(self, -1)
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        fgs = wx.FlexGridSizer(3, 2, 9, 25)
 
        title = wx.StaticText(panel, -1, 'Title')
        author = wx.StaticText(panel, -1, 'Author')
        review = wx.StaticText(panel, -1, 'Review')
 
        tc1 = wx.TextCtrl(panel, -1)
        tc2 = wx.TextCtrl(panel, -1)
        tc3 = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
 
        fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), (tc2, 1, wx.EXPAND),
            (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])
 
        fgs.AddGrowableRow(2, 1)
        fgs.AddGrowableCol(1, 1)
 
        hbox.Add(fgs, 1, wx.ALL | wx.EXPAND, 15)
        panel.SetSizer(hbox)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
FlexGridSizer(None, -1, 'FlexGridSizer')
app.MainLoop()
 hbox = wx.BoxSizer(wx.HORIZONTAL)
 ...
 hbox.Add(fgs, 1, wx.ALL | wx.EXPAND, 15)

We create a horizontal box sizer in order to put some space (15px) around the table of widgets.

 fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), (tc2, 1, wx.EXPAND),
      (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])

We add widgets to the sizer with the AddMany() method. Both wx.FlexGridSizer and wx.GridSizer share this method.

 fgs.AddGrowableRow(2, 1)
 fgs.AddGrowableCol(1, 1)

We make the third row and second column growable. This way we let the text controls grow, when the window is resized. The first two text controls will grow in horizontal direction, the third one will grow in both direction. We must not forget to make the widgets expandable (wx.EXPAND) in order to make it really work.

wx.GridBagSizer

The most complicated sizer in wxPython. Many programmer find it difficult to use. This kind of sizer is not typical only for wxPython. We can find it in other toolkits as well. There is no magic in using this sizer. Even though it is more complicated, it is certainly not rocket science. All we have to do is to create several layouts with it. Find all the quirks. Play with it a bit. There are more difficult things in programming. Believe me.

This sizer enables explicit positioning of items. Items can also optionally span more than one row and/or column. wx.GridBagSizer has a simple constructor.

 wx.GridBagSizer(integer vgap, integer hgap)

The vertical and the horizontal gap defines the space in pixels used among all children. We add items to the grid with the Add() method.

 Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, integer border=0, userData=None)

Item is a widget that you insert into the grid. pos specifies the position in the virtual grid. The topleft cell has pos of (0, 0). span is an optional spanning of the widget. e.g. span of (3, 2) spans a widget across 3 rows and 2 columns. flag and border were discussed earlier by wx.BoxSizer.

The items in the grid can change their size or keep the default size, when the window is resized. If you want your items to grow and shrink, you can use these two methods.

 AddGrowableRow(integer row)
 AddGrowableCol(integer col)

Rename dialog

The first example is intentionally a very simple one. So that it could be easily understood. There is no need to be afraid of wx.GridBagSizer. Once you understand it's logic, it is quite simple to use it. In our example, we will create a rename dialog. It will have one wx.StaticText, one wx.TextCtrl and two wx.Button-s.

#!/usr/bin/python
 
# rename.py
 
import wx
 
class Rename(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(320, 130))
 
        panel = wx.Panel(self, -1)
        sizer = wx.GridBagSizer(4, 4)
 
        text = wx.StaticText(panel, -1, 'Rename To')
        sizer.Add(text, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 
        tc = wx.TextCtrl(panel, -1)
        sizer.Add(tc, (1, 0), (1, 5), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 
        buttonOk = wx.Button(panel, -1, 'Ok', size=(90, 28))
        buttonClose = wx.Button(panel, -1, 'Close', size=(90, 28))
        sizer.Add(buttonOk, (3, 3))
        sizer.Add(buttonClose, (3, 4), flag=wx.RIGHT | wx.BOTTOM, border=5)
 
        sizer.AddGrowableCol(1)
        sizer.AddGrowableRow(2)
        panel.SetSizerAndFit(sizer)
        self.Centre()
        self.Show(True)
 
 
app = wx.App()
Rename(None, -1, 'Rename Dialog')
app.MainLoop()

We must look at the dialog window as a one big grid table.

 text = wx.StaticText(panel, -1, 'Rename To')
 sizer.Add(text, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)

The text 'Rename to' goes to the left upper corner. So we specify the (0, 0) position. Plus we add some space to the bottom, left and bottom.

 tc = wx.TextCtrl(panel, -1)
 sizer.Add(tc, (1, 0), (1, 5), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)

The wx.TextCtrl goes to the beginning of the second row (1, 0). Remember, that we count from zero. It expands 1 row and 5 columns. (1, 5). Plus we put 5 pixels of space to the left and to the right of the widget.

 sizer.Add(buttonOk, (3, 3))
 sizer.Add(buttonClose, (3, 4), flag=wx.RIGHT | wx.BOTTOM, border=5)

We put two buttons into the fourth row. The third row is left empty, so that we have some space between the wx.TextCtrl and the buttons. We put the ok button into the fourth column and the close button into the fifth one. Notice that once we apply some space to one widget, it is applied to the whole row. That's why I did not specify bottom space for the ok button. A careful reader might notice, that we did not specify any space between the two buttons. e.g. we did not put any space to the right of the ok button, or to the right of the close button. In the constructor of the wx.GridBagSizer, we put some space between all widgets. So there is some space already.

 sizer.AddGrowableCol(1)
 sizer.AddGrowableRow(2)

The last thing we must do, is to make our dialog resizable. We make the second column and the third row growable. Now we can expand or shrink our window. Try to comment those two lines and see what happens.

Open Resource

The next example will be a bit more complicated. We will create an Open Resource window. This example will show a layout of a very handy dialog which you can find in Eclipse IDE.

#!/usr/bin/python
 
# openresource.py
 
import wx
 
class OpenResource(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(400, 500))
 
        panel = wx.Panel(self, -1)
        sizer = wx.GridBagSizer(4, 4)
 
        text1 = wx.StaticText(panel, -1, 'Select a resource to open')
        sizer.Add(text1, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 
        tc = wx.TextCtrl(panel, -1)
        sizer.Add(tc, (1, 0), (1, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 
        text2 = wx.StaticText(panel, -1, 'Matching resources')
        sizer.Add(text2, (2, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 
        list1 = wx.ListBox(panel, -1, style=wx.LB_ALWAYS_SB)
        sizer.Add(list1, (3, 0), (5, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 
        text3 = wx.StaticText(panel, -1, 'In Folders')
        sizer.Add(text3, (8, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 
        list2 = wx.ListBox(panel, -1, style=wx.LB_ALWAYS_SB)
        sizer.Add(list2, (9, 0), (3, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 
        cb = wx.CheckBox(panel, -1, 'Show derived resources')
        sizer.Add(cb, (12, 0), flag=wx.LEFT | wx.RIGHT, border=5)
 
        buttonOk = wx.Button(panel, -1, 'OK', size=(90, 28))
        buttonCancel = wx.Button(panel, -1, 'Cancel', size=(90, 28))
        sizer.Add(buttonOk, (14, 1))
        sizer.Add(buttonCancel, (14, 2), flag=wx.RIGHT | wx.BOTTOM, border=5)
 
        help = wx.BitmapButton(panel, -1, wx.Bitmap('icons/help16.png'), style=wx.NO_BORDER)
        sizer.Add(help, (14, 0), flag=wx.LEFT, border=5)
 
        sizer.AddGrowableCol(0)
        sizer.AddGrowableRow(3)
        sizer.AddGrowableRow(9)
        sizer.SetEmptyCellSize((5, 5))
        panel.SetSizer(sizer)
 
        self.Centre()
        self.Show(True)
 
app = wx.App()
OpenResource(None, -1, 'Open Resource')
app.MainLoop()
 sizer.AddGrowableRow(3)
 sizer.AddGrowableRow(9)

We want to have both wx.ListBox-es growable. So we make the first row of each wx.ListBox growable.

Create new class

newclass.py example is a type of a window, that I found in JDeveloper. It is a dialog window for creating a new class in Java.

#!/usr/bin/python
 
# newclass.py
 
import wx
 
class NewClass(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        panel = wx.Panel(self, -1)
        sizer = wx.GridBagSizer(0, 0)
 
        text1 = wx.StaticText(panel, -1, 'Java Class')
        sizer.Add(text1, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15)
 
        icon = wx.StaticBitmap(panel, -1, wx.Bitmap('icons/exec.png'))
        sizer.Add(icon, (0, 4), flag=wx.LEFT,  border=45)
 
        line = wx.StaticLine(panel, -1 )
        sizer.Add(line, (1, 0), (1, 5), wx.TOP | wx.EXPAND, -15)
 
        text2 = wx.StaticText(panel, -1, 'Name')
        sizer.Add(text2, (2, 0), flag=wx.LEFT, border=10)
 
        tc1 = wx.TextCtrl(panel, -1, size=(-1, 30))
        sizer.Add(tc1, (2, 1), (1, 3), wx.TOP | wx.EXPAND, -5)
 
        text3 = wx.StaticText(panel, -1, 'Package')
        sizer.Add(text3, (3, 0), flag= wx.LEFT | wx.TOP, border=10)
 
        tc2 = wx.TextCtrl(panel, -1)
        sizer.Add(tc2, (3, 1), (1, 3), wx.TOP | wx.EXPAND, 5)
 
        button1 = wx.Button(panel, -1, 'Browse...', size=(-1, 30))
        sizer.Add(button1, (3, 4), (1, 1), wx.TOP | wx.LEFT | wx.RIGHT , 5)
 
        text4 = wx.StaticText(panel, -1, 'Extends')
        sizer.Add(text4, (4, 0), flag=wx.TOP | wx.LEFT, border=10)
 
        combo = wx.ComboBox(panel, -1, )
        sizer.Add(combo, (4, 1), (1, 3), wx.TOP | wx.EXPAND,  5)
 
        button2 = wx.Button(panel, -1, 'Browse...', size=(-1, 30))
        sizer.Add(button2, (4, 4), (1, 1), wx.TOP | wx.LEFT | wx.RIGHT , 5)
 
        sb = wx.StaticBox(panel, -1, 'Optional Attributes')
        boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
        boxsizer.Add(wx.CheckBox(panel, -1, 'Public'), 0, wx.LEFT | wx.TOP, 5)
        boxsizer.Add(wx.CheckBox(panel, -1, 'Generate Default Constructor'), 0,  wx.LEFT, 5)
        boxsizer.Add(wx.CheckBox(panel, -1, 'Generate Main Method'), 0, wx.LEFT | wx.BOTTOM, 5)
        sizer.Add(boxsizer, (5, 0), (1, 5), wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT , 10)
        button3 = wx.Button(panel, -1, 'Help', size=(-1, 30))
        sizer.Add(button3, (7, 0), (1, 1),  wx.LEFT, 10)
 
        button4 = wx.Button(panel, -1, 'Ok', size=(-1, 30))
        sizer.Add(button4, (7, 3), (1, 1),  wx.LEFT, 10)
 
        button5 = wx.Button(panel, -1, 'Cancel', size=(-1, 30))
        sizer.Add(button5, (7, 4), (1, 1),  wx.LEFT | wx.BOTTOM | wx.RIGHT, 10)
 
        sizer.AddGrowableCol(2)
        sizer.Fit(self)
        panel.SetSizer(sizer)
        self.Centre()
        self.Show(True)
 
 
app = wx.App()
NewClass(None, -1, 'Create Java Class')
app.MainLoop()
 line = wx.StaticLine(panel, -1 )
 sizer.Add(line, (1, 0), (1, 5), wx.TOP | wx.EXPAND, -15)

Notice, that we have used negative number for setting the top border. We could use wx.BOTTOM with border 15. We would get the same result.

 icon = wx.StaticBitmap(panel, -1, wx.Bitmap('icons/exec.png'))
 sizer.Add(icon, (0, 4), flag=wx.LEFT,  border=45)

We put an wx.StaticBitmap into the first row of the grid. We place it on the right side of the row. By using images we can make our applications look better.

sizer.Fit(self)

We did not set the size of the window explicitly. If we call Fit() method, the size of the window will exactly cover all widgets available. Try to comment this line and see what happens.

</html>

Events in wxPython

الأحداث هى جزء هام في أي تطبيق رسومى، وهى تحدث من المستخدم او من النظام. عندما نستدعى MainLoop‎ يدخل التطبيق فى الحلقة الأساسية main loop. وهي بدورها تستخرج الأحداث وترسلها إلى الكائنات.

Events are integral part of every GUI application. All GUI applications are event-driven. An application reacts to different event types which are generated during its life. Events are generated mainly by the user of an application. But they can be generated by other means as well. e.g. internet connection, window manager, timer. So when we call MainLoop() method, our application waits for events to be generated. The MainLoop() method ends when we exit the application.

تعريفات

الحدث هو جزء من المعلومات على مستوى التطبيق حلقة الأحداث تنتظر لحدوث حدث او وصول رسالة للبرنامج ليتم معالجته التوزيع عملية بربط الأحداث بمعالجى الأحداث معالج الحدث هى طريقة نقوم بتعريفها للتفاعل مع حدوث حدث معين كائن الحدث هو كائن يرتبط بالحدث وغالبا هو نافذة نوع الحدث هو حدث فريد يتم توليده رابط الحدث هو كائن يقوم بربط الحدث بمعالج الحدث

مثال بسيط

فى الجزء التالى سنتكلم عن حدث بسيط هو حدث الإنتقال move

حدث الإنتقال move event ينتج عندما ننقل نافذة لموضع جديد وهو من النوع wx.MoveEvent ورابط الحدث له هو wx.EVT_MOVE

#!/usr/bin/python
 
# moveevent.py
 
import wx
 
class MoveEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 180))
 
        wx.StaticText(self, -1, 'x:', (10,10))
        wx.StaticText(self, -1, 'y:', (10,30))
        self.st1 = wx.StaticText(self, -1, '', (30, 10))
        self.st2 = wx.StaticText(self, -1, '', (30, 30))
 
        self.Bind(wx.EVT_MOVE, self.OnMove)
 
        self.Centre()
        self.Show(True)
 
    def OnMove(self, event):
        x, y = event.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))
 
 
app = wx.App()
MoveEvent(None, -1, 'move event')
app.MainLoop()

المثال يعرض الموضع الحالى للنافذة.

 self.Bind(wx.EVT_MOVE, self.OnMove)

Here we bind the wx.EVT_MOVE event binder to the OnMove() method.

 def OnMove(self, event):
     x, y = event.GetPosition()

المعامل event فى الطريقة OnMove هو كائن ينتمى لنوع حدث معين وفى حالتنا هذه هو wx.MoveEvent ويحمل هذا الكائن معلومات عن الحدث مثل الموضع وكائن الحدث –فى حالتنا هو wx.Frame– للحصول على الموضع الحالى نستدعى الطريقة GetPosition للحدث

zetcode.com_wxpython_images_moveevent.jpg

ربط الحدث

العمل مع الأحداث مباشر فى wxPython ويتم على 3 خطوات

1- التعرف على اسم رابط الحدث wx.EVT_SIZE او wx.EVT_CLOSE ..الخ 2-انشاء معالج الحدث ليتم استدعاءه عند حدوث الحدث 3- ربط الحدث بمعالج الحدث

لربط الحدث نستطيع استخدام الطريقة Bind والتى تعريفها كالتالى

 Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

حيث: * ال event هو احد كائنات EVT_* لتحديد نوع الحدث * ال handler وهو المعالج للحدث * ال source للتفريق بين الحدث من الودجات المختلفة * ال id يستخدم عندما يكون لدينا عدة ازرار، عناصر قوائم .. الخ للتفريق بينهم

الطريقة Bind معرفة فى الصف EvtHandler وهو الصف اللذى ترث منه wx.Window لاحظ ان wx.Window هى الصف الأب لمعظم الويدجات

هناك ايضا عملية عكسية عن طريق الطريقة Unbind ولها نفس معاملات Bind

  • event is one of EVT_* objects. It specifies the type of the event.
  • handler is an object to be called. In other words, it is a method, that a programmer binds to an event.
  • source parameter is used when we want to differentiate between the same event type from different widgets.
  • id parameter is used, when we have multiple buttons, menu items etc. The id is used to differentiate among them.
  • id2 is used when it is desirable to bind a handler to a range of ids, such as with EVT_MENU_RANGE.

Note that method Bind() is defined in class EvtHandler. It is the class, from which wx.Window inherits. wx.Window is a base class for most widgets in wxPython. There is also a reverse process. If we want to unbind a method from an event, we call the Unbind() method. It has the same paremeters as the above one.

ايقاف معالجة الحدث

فى بعض الأحيان نريد ايقاف معالجة حدث معين. يتم ذلك عن طريق استدعاء الطريقة Veto

#!/usr/bin/python
 
# veto.py
 
import wx
 
class Veto(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 200))
 
 
        self.Bind(wx.EVT_CLOSE, self.OnClose)
 
        self.Centre()
        self.Show(True)
 
    def OnClose(self, event):
 
        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        ret = dial.ShowModal()
        if ret == wx.ID_YES:
            self.Destroy()
        else:
            event.Veto()
 
 
app = wx.App()
Veto(None, -1, 'Veto')
app.MainLoop()

فى مثالنا نعالج حدث من النوع wx.CloseEvent ويحدث عندما نغلق النافذة سواء بالضغط على علامة X فى شريط العنوان او بأى طريقة اخرى. فى كثير من التطبيقات نرغب فى منع اغلاق النافذة بالخطأ. لذا يجب علينا القيام ببعض التعديلات ، وذلك عن طريق ربط الحدث wx.EVT_CLOSE

 dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
     wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
 ret = dial.ShowModal()

عند محاولة الإغلاق نعرض صندوق استفهامى

 if ret == wx.ID_YES:
     self.Destroy()
 else:
     event.Veto()

وبناء على القيمة العائدة نحدد اما ان ندمر النافذة او نوقف معالجة الحدث.

لاحظ لإعلاق النافذةيجب علينا استدعاء Destroy

لاحظ ان بإستدعاء الطريقة Close سندخل فى حلقة لانهائية :)

انتشار الحدث

هناك نوعين من الأحداث، احداث اساسية او اولية واحداث الأوامر وبيختلفا فى عملية الإنتشار

*انتشار الحدث* انتقال الأحداث من الأبناء للأباء ثم للأجداد.. الخ الأحداث الأوليه لاتنتشر بعكس احداث الأوامر. على سبيل المثال الحدث wx.CloseEvent هو حدث اولى لاينتشر.. ليس منطقيا ان ينتشر هذا الأحدث للأباء اليس كذلك ؟

افتراضيا، عند معالجة حدث ما فإنه يتوقف عن الإنتشار، ولإكمال عملية الإنتشار يجب استدعاء الطريقة Skip

#!/usr/bin/python
 
# propagate.py
 
import wx
 
 
class MyPanel(wx.Panel):
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id)
 
        self.Bind(wx.EVT_BUTTON, self.OnClicked)
 
    def OnClicked(self, event):
        print 'event reached panel class'
        event.Skip()
 
 
class MyButton(wx.Button):
    def __init__(self, parent, id, label, pos):
        wx.Button.__init__(self, parent, id, label, pos)
 
        self.Bind(wx.EVT_BUTTON, self.OnClicked)
 
    def OnClicked(self, event):
        print 'event reached button class'
        event.Skip()
 
 
class Propagate(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        panel = MyPanel(self, -1)
 
        MyButton(panel, -1, 'Ok', (15, 15))
 
        self.Bind(wx.EVT_BUTTON, self.OnClicked)
 
        self.Centre()
        self.Show(True)
 
    def OnClicked(self, event):
        print 'event reached frame class'
        event.Skip()
 
 
app = wx.App()
Propagate(None, -1, 'Propagate')
app.MainLoop()

فى مثالنا لدينا زر على بانل موضوعة على frame

قمنا بتعريف معالج لكل الودجات

 event reached button class
 event reached panel class
 event reached frame class

لتفهم هذا، عندما نضغط على الزر فإن الحدث ينتقل من الزر الى البانل ثم الى ال frame

جرب حذف بعض استدعاءات Skip ولاحظ ماذا سيحدث

Window identifiers

Window identifiers are integers that uniquely determine the window identity in the event system. There are three ways to create window id's.

  • let the system automatically create an id
  • use standard identifiers
  • create your own id

Each widget has an id parameter. This is a unique number in the event system. If we work with multiple widgets, we must differantiate among them.

 wx.Button(parent, -1)
 wx.Button(parent, wx.ID_ANY)

If we provide -1 or wx.ID_ANY for the id parameter, we let the wxPython automatically create an id for us. The automatically created id's are always negative, whereas user specified id's must always be positive. We usually use this option when we do not need to change the widget state. For example a static text, that will never be changed during the life of the application. We can still get the id, if we want. There is a method GetId(), which will determine the id for us.

#!/usr/bin/python
 
# automaticids.py
 
import wx
 
class AuIds(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(170, 100))
 
        panel = wx.Panel(self, -1)
        exit = wx.Button(panel, -1, 'Exit', (10, 10))
 
        self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exit.GetId())
 
        self.Centre()
        self.Show(True)
 
    def OnExit(self, event):
        self.Close()
 
 
app = wx.App()
AuIds(None, -1, '')
app.MainLoop()

In this example, we do not care about the actual id value.

 self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exit.GetId())

We get the automatically generated id by calling the GetId() method.

Standard identifiers should be used whenever possible. The identifiers can provide some standard graphics or behaviour on some platforms.

#!/usr/bin/python
 
# identifiers.py
 
import wx
 
class Identifiers(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(200, 150))
 
        panel = wx.Panel(self, -1)
        grid = wx.GridSizer(3, 2)
 
        grid.AddMany([(wx.Button(panel, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
            (wx.Button(panel, wx.ID_DELETE), 0, wx.TOP, 9),
            (wx.Button(panel, wx.ID_SAVE), 0, wx.LEFT, 9),
            (wx.Button(panel, wx.ID_EXIT)),
            (wx.Button(panel, wx.ID_STOP), 0, wx.LEFT, 9),
            (wx.Button(panel, wx.ID_NEW))])
 
 
        self.Bind(wx.EVT_BUTTON, self.OnQuit, id=wx.ID_EXIT)
 
        panel.SetSizer(grid)
        self.Centre()
        self.Show(True)
 
    def OnQuit(self, event):
        self.Close()
 
app = wx.App()
Identifiers(None, -1, '')
app.MainLoop()

In our example we use standard identifiers on buttons. On linux, the buttons have small icons.

zetcode.com_wxpython_images_identifiers.jpg

The last option is to use own identifiers. We define our own global ids.

Miscellaneous events

Focus event

The focus indicates the currently selected widget in application. The text entered from the keyboard or pasted from the clipboard is sent to the widget, which has the focus. There are two event types concerning focus. The wx.EVT_SET_FOCUS event, which is generated when a widget receives focus. The wx.EVT_KILL_FOCUS is generated, when the widget looses focus. The focus is changed by clicking or by a keybord key. Usually Tab/Shift+Tab.

#!/usr/bin/python
 
# focusevent.py
 
import wx
 
 
class MyWindow(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
 
        self.color = '#b3b3b3'
 
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
 
    def OnPaint(self, event):
        dc = wx.PaintDC(self)
 
        dc.SetPen(wx.Pen(self.color))
        x, y = self.GetSize()
        dc.DrawRectangle(0, 0, x, y)
 
    def OnSize(self, event):
        self.Refresh()
 
    def OnSetFocus(self, event):
        self.color = '#0099f7'
        self.Refresh()
 
    def OnKillFocus(self, event):
        self.color = '#b3b3b3'
        self.Refresh()
 
class FocusEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 250))
 
        grid = wx.GridSizer(2, 2, 10, 10)
        grid.AddMany([(MyWindow(self), 1, wx.EXPAND|wx.TOP|wx.LEFT,9),
            (MyWindow(self), 1, wx.EXPAND|wx.TOP|wx.RIGHT, 9), 
            (MyWindow(self), 1, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9), 
            (MyWindow(self), 1, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)])
 
 
        self.SetSizer(grid)
        self.Centre()
        self.Show(True)
 
app = wx.App()
FocusEvent(None, -1, 'focus event')
app.MainLoop()

In our example, we have four panels. The panel with focus is highlighted.

zetcode.com_wxpython_images_focusevent.jpg

ScrollEvent

The following code is an example of a wx.ScrollWinEvent. This event is generated, when we click on a built in Scrollbar. Built-in Scrollbar is activated with the SetScrollbar() method call. For stand-alone Scrollbars, there is another event type, namely wx.ScrollEvent.

#!/usr/bin/python
 
# myscrollwinevent.py
 
import wx
 
class ScrollWinEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
        panel = wx.Panel(self, -1)
        self.st = wx.StaticText(panel, -1, '0', (30,0))
        panel.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
        panel.SetScrollbar(wx.VERTICAL, 0, 6, 50);
        self.Centre()
        self.Show(True)
 
    def OnScroll(self, evt):
        y = evt.GetPosition()
        self.st.SetLabel(str(y))
 
app = wx.App()
ScrollWinEvent(None, -1, 'scrollwinevent.py')
app.MainLoop()

SizeEvent

A wx.SizeEvent is generated, when our window is resized. In our example, we show the size of the window in the titlebar.

#!/usr/bin/python
 
# sizeevent.py
 
import wx
 
class SizeEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Centre()
        self.Show(True)
 
    def OnSize(self, event):
        self.SetTitle(str(event.GetSize()))
 
 
app = wx.App()
SizeEvent(None, 1, 'sizeevent.py')
app.MainLoop()
 self.SetTitle(str(event.GetSize()))

To get the current size of the window, we call the GetSize() method of the event object.

PaintEvent

A paint event is generated when a window is redrawn. This happens when we resize a window or when we maximize it. A paint event can be generated programatically as well. For example, when we call SetLabel() method to change a wx.StaticText widget. Note that when we minimize a window, no paint event is generated.

#!/usr/bin/python
 
# paintevent.py
 
import wx
 
class PaintEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        self.count = 0
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Centre()
        self.Show(True)
 
    def OnPaint(self, event):
        self.count = self.count + 1
        print self.count
 
 
app = wx.App()
PaintEvent(None, -1, 'paintevent.py')
app.MainLoop()

In our example we print the number of paint events generated into the console.

KeyEvent

When we press a key on our keyboard, wx.KeyEvent is generated. This event is sent to the widget that has currently focus.

There are three different key handlers:

  • wx.EVT_KEY_DOWN
  • wx.EVT_KEY_UP
  • wx.EVT_CHAR

A common request is to close application, when Esc key is pressed.

#!/usr/bin/python
 
# keyevent.py
 
import wx
 
class KeyEvent(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        panel = wx.Panel(self, -1)
        panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        panel.SetFocus()
 
        self.Centre()
        self.Show(True)
 
 
    def OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_ESCAPE:
            ret  = wx.MessageBox('Are you sure to quit?', 'Question', 
		wx.YES_NO | wx.NO_DEFAULT, self)
            if ret == wx.YES:
                self.Close()
        event.Skip()
 
 
app = wx.App()
KeyEvent(None, -1, 'keyevent.py')
app.MainLoop()
 keycode = event.GetKeyCode()

Here we get the key code of the pressed key.

 if keycode == wx.WXK_ESCAPE:

We check the key code. The Esc key has wx.WXK_ESCAPE code.

</html>

صناديق الحوار

نوافذ(صناديق)الحوار هى جزء مهم فى التطبيقات الرسومية، الحوار هو محادثة بين شخصين او اكثر، هنا المحادثة (المستخدم والتطبيق) وسيلة لإيصال معلومة للتطبيق. يمكن استخدامها فى اضافة بيانات ، تعديلها، او التحكم فى اعدادات البرنامج.. الخ.

يوجد نوعان (نافذة حوار معرفة مسبقا ، او واحدة اعدّت خصيصا)

رسالة بسيطة

صندوق الرسالة بيقدم معلومات قصيرة للمستخدم، على سبيل المثال برنامج حرق الإسطوانات.. بعد انتهاء عملية الحرق تظهر رسالة للمستخدم تعلمه بذلك.

#!/usr/bin/python
 
# message.py
 
import wx
 
class MessageDialog(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        wx.FutureCall(5000, self.ShowMessage)
 
        self.Centre()
        self.Show(True)
 
    def ShowMessage(self):
        wx.MessageBox('Download completed', 'Info')
 
 
app = wx.App()
MessageDialog(None, -1, 'MessageDialog')
app.MainLoop()
wx.FutureCall(5000, self.ShowMessage)

wx.FutureCall تستدعى دالة بعد 5 ثوانى -القيمة معطاه بالملى ثانية-.

 def ShowMessage(self):
     wx.MessageBox('Download completed', 'Info')

wx.MessageBox يعرض صندوق صغير يشمل (نص الرسالة، عنوان الرسالة، وزر)

صناديق حوارية جاهزة

تأتى لنا wxPython بعدة صناديق حوارية جاهزة لأكثر العمليات الشائعة (عرض رسالة، ادخال قيمة، فتح وحفظ ملفات.. الخ)

Message dialogs

صناديق الرسائل الحوارية تستخدم لعرض رسائل للمستخدم وهى امرن من صناديق الرسائل البسيطة ونستطيع فيها ان نغير الأيكونات والازرار المعروضة عليها.

 wx.MessageDialog(wx.Window parent, string message, string caption=wx.MessageBoxCaptionStr, 
  long style=wx.OK | wx.CANCEL | wx.CENTRE, wx.Point pos=(-1, -1))
flag meaning
wx.OK تعرض زر OK
wx.CANCEL تعرض زر Cancel
wx.YES_NO تعرض ازرار Yes, No
wx.YES_DEFAULT تجعل الزر Yes هو الإفتراضى
wx.NO_DEFAULT تجعل الزر No هو الإفتراضى
wx.ICON_EXCLAMATION تعرض ايكون الإنذار
wx.ICON_ERROR تعرض ايكون الخطأ
wx.ICON_HAND مثل السابقة
wx.ICON_INFORMATION تعرض ايكون معلومات
wx.ICON_QUESTION تعرض ايكون سؤال
#!/usr/bin/python
 
# messages.py
 
import wx
 
class Messages(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))
 
        panel = wx.Panel(self, -1)
 
        hbox = wx.BoxSizer()
        sizer = wx.GridSizer(2, 2, 2, 2)
 
        btn1 = wx.Button(panel, -1, 'Info')
        btn2 = wx.Button(panel, -1, 'Error')
        btn3 = wx.Button(panel, -1, 'Question')
        btn4 = wx.Button(panel, -1, 'Alert')
 
        sizer.AddMany([btn1, btn2, btn3, btn4])
 
        hbox.Add(sizer, 0, wx.ALL, 15)
        panel.SetSizer(hbox)
 
 
        btn1.Bind(wx.EVT_BUTTON, self.ShowMessage1)
        btn2.Bind(wx.EVT_BUTTON, self.ShowMessage2)
        btn3.Bind(wx.EVT_BUTTON, self.ShowMessage3)
        btn4.Bind(wx.EVT_BUTTON, self.ShowMessage4)
 
        self.Centre()
        self.Show(True)
 
    def ShowMessage1(self, event):
        dial = wx.MessageDialog(None, 'Download completed', 'Info', wx.OK)
        dial.ShowModal()
 
    def ShowMessage2(self, event):
        dial = wx.MessageDialog(None, 'Error loading file', 'Error', wx.OK | 
            wx.ICON_ERROR)
        dial.ShowModal()
 
    def ShowMessage3(self, event):
        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', 
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        dial.ShowModal()
 
    def ShowMessage4(self, event):
        dial = wx.MessageDialog(None, 'Unallowed operation', 'Exclamation', wx.OK | 
            wx.ICON_EXCLAMATION)
        dial.ShowModal()
 
app = wx.App()
Messages(None, -1, 'Messages')
app.MainLoop()

فى مثالنا انشئنا 4 ازرار ووضعناهم فى شبكة، كل منها يعرض صندوق حوارى مختلف.

 dial = wx.MessageDialog(None, 'Error loading file', 'Error', wx.OK | 
     wx.ICON_ERROR)
 dial.ShowModal()

انشاء صندوق حوارى امر سهل. نحدد ان ال dialog ليس له اب وذلك بتمرير None ويليه سلسلتين نصيتين تحويان محتوى الرسالة وعنوان الصندوق.ونعرض زر OK وايكون الخطأ وذلك عن طريق wx.OK, wx.ICON_ERROR

ولعرض الصندوق على الشاشة نستدعى الطريقة ShowModal

Info dialog

Question dialog

Alert dialog

Error dialog

About dialog box

Almost every application has a typical about dialog box. It is usually placed in the Help menu. The purpose of this dialog is to give the user the basic information about the name and the version of the application. In the past, these dialogs used to be quite brief. These days most of these boxes provide additional information about the authors. They give credits to additional programmers or documentation writers. They also provide information about the application licence. These boxes can show the logo of the company or the application logo. Some of the more capable about boxes show animation. wxPython has a special about dialog box starting from 2.8.x series. It is not rocket science to make such a dialog manually. But it makes a programmer's life easier.

The dialog box is located in the Misc module. In order to create an about dialog box we must create two objects. A wx.AboutDialogInfo and a wx.AboutBox.

 wx.AboutDialogInfo()

We will call the following methods upon a wx.AboutDialogInfo object in our example. These methods are self-exlanatory.

Method Description
‪ SetName(string name) ‬ set the name of the program
‪ SetVersion(string version) ‬ set the version of the program
‪ SetDescription(string desc) ‬ set the description of the program
‪ SetCopyright(string copyright) ‬ set the copyright fo the program
‪ SetLicence(string licence) ‬ set the licence of the program
‪ SetIcon(wx.Icon icon) ‬ set the icon to be show
‪ SetWebSite(string URL) ‬ set the website of the program
‪ SetLicence(string licence) ‬ set the licence of the program
‪ AddDeveloper(string developer) ‬ add a developer to the developer's list
‪ AddDocWriter(string docwirter) ‬ add a docwriter to the docwriter's list
‪ AddArtist(string artist) ‬ add an artist to the artist's list
‪ AddTranslator(string developer) ‬ add a developer to the translator's list

The constructor of the wx.AboutBox is as follows. It takes a wx.AboutDialogInfo as a parameter.

 wx.AboutBox(wx.AboutDialogInfo info)

wxPython can display two kinds of About boxes. It depends on which platform we use and which methods we call. It can be a native dialog or a wxPython generic dialog. Windows native about dialog box cannot display custom icons, licence text nor the url's. If we omit these three fields, wx.Python will show a native dialog. Otherwise it will resort to a generic one. It is advised to provide licence information in a separate menu item, if we want to stay as native as possible. GTK+ can show all these fields.

#!/usr/bin/python
 
# aboutbox.py
 
import wx
 
ID_ABOUT = 1
 
class AboutDialogBox(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(260, 200))
 
        menubar = wx.MenuBar()
        help = wx.Menu()
        help.Append(ID_ABOUT, '&About')
        self.Bind(wx.EVT_MENU, self.OnAboutBox, id=ID_ABOUT)
        menubar.Append(help, '&Help')
        self.SetMenuBar(menubar)
 
        self.Centre()
        self.Show(True)
 
    def OnAboutBox(self, event):
        description = """File Hunter is an advanced file manager for the Unix operating 
system. Features include powerful built-in editor, advanced search capabilities,
powerful batch renaming, file comparison, extensive archive handling and more.
"""
 
        licence = """File Hunter is free software; you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by the Free Software Foundation; 
either version 2 of the License, or (at your option) any later version.
 
File Hunter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
See the GNU General Public License for more details. You should have received a copy of 
the GNU General Public License along with File Hunter; if not, write to 
the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA"""
 
 
        info = wx.AboutDialogInfo()
 
        info.SetIcon(wx.Icon('icons/hunter.png', wx.BITMAP_TYPE_PNG))
        info.SetName('File Hunter')
        info.SetVersion('1.0')
        info.SetDescription(description)
        info.SetCopyright('(C) 2007 jan bodnar')
        info.SetWebSite('http://www.zetcode.com')
        info.SetLicence(licence)
        info.AddDeveloper('jan bodnar')
        info.AddDocWriter('jan bodnar')
        info.AddArtist('The Tango crew')
	info.AddTranslator('jan bodnar')
 
        wx.AboutBox(info)
 
 
app = wx.App()
AboutDialogBox(None, -1, 'About dialog box')
app.MainLoop()
        description = """File Hunter is an advanced file manager for the Unix operating 
system. Features include powerful built-in editor, advanced search capabilities,
powerful batch renaming, file comparison, extensive archive handling and more.
"""

It is not the best idea to put too much text into the code of the application. I don't want to make the example too complex, so I put all the text into the code. But in real world programs, the text should be placed separately inside a file. It helps us with the maintenace of our application. For example, if we want to translate our application to other languages.

 info = wx.AboutDialogInfo()

The first thing to do is to create a wx.AboutDialogInfo object. The constructor is empty. It does not taky any parameters.

 info.SetIcon(wx.Icon('icons/hunter.png', wx.BITMAP_TYPE_PNG))
 info.SetName('File Hunter')
 info.SetVersion('1.0')
 info.SetDescription(description)
 info.SetCopyright('(C) 2007 jan bodnar')
 info.SetWebSite('http://www.zetcode.com')
 info.SetLicence(licence)
 info.AddDeveloper('jan bodnar')
 info.AddDocWriter('jan bodnar')
 info.AddArtist('The Tango crew')
 info.AddTranslator('jan bodnar')

The next thing to do is to call all necessary methods upon the created wx.AboutDialogInfo object.

 wx.AboutBox(info)

In the end we create a wx.AboutBox widget. The only parameter it takes is the wx.AboutDialogInfo object.

And of course, if we want to have an animation or some other eye candy, we must implement our about dialog manually.

About dialog box

A custom dialog

In the next example we create a custom dialog. An image editing application can change a color depth of a picture. To provide this funcionality, we could create a suitable dialog.

#!/usr/bin/python
 
# colordepth.py
 
import wx
 
ID_DEPTH = 1
 
class ChangeDepth(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(250, 210))
 
        panel = wx.Panel(self, -1)
        vbox = wx.BoxSizer(wx.VERTICAL)
 
        wx.StaticBox(panel, -1, 'Colors', (5, 5), (240, 150))
        wx.RadioButton(panel, -1, '256 Colors', (15, 30), style=wx.RB_GROUP)
        wx.RadioButton(panel, -1, '16 Colors', (15, 55))
        wx.RadioButton(panel, -1, '2 Colors', (15, 80))
        wx.RadioButton(panel, -1, 'Custom', (15, 105))
        wx.TextCtrl(panel, -1, '', (95, 105))
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(self, -1, 'Ok', size=(70, 30))
        closeButton = wx.Button(self, -1, 'Close', size=(70, 30))
        hbox.Add(okButton, 1)
        hbox.Add(closeButton, 1, wx.LEFT, 5)
 
        vbox.Add(panel)
        vbox.Add(hbox, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 10)
 
        self.SetSizer(vbox)
 
 
class ColorDepth(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 220))
 
        toolbar = self.CreateToolBar()
        toolbar.AddLabelTool(ID_DEPTH, '', wx.Bitmap('icons/color.png'))
 
        self.Bind(wx.EVT_TOOL, self.OnChangeDepth, id=ID_DEPTH)
 
        self.Centre()
        self.Show(True)
 
    def OnChangeDepth(self, event):
        chgdep = ChangeDepth(None, -1, 'Change Color Depth')
        chgdep.ShowModal()
        chgdep.Destroy()
 
app = wx.App()
ColorDepth(None, -1, '')
app.MainLoop()
 class ChangeDepth(wx.Dialog):
     def __init__(self, parent, id, title):
         wx.Dialog.__init__(self, parent, id, title, size=(250, 210))

In our code example we create a custom ChangeDepth dialog. We inherit from a wx.Dialog widget.

 chgdep = ChangeDepth(None, -1, 'Change Color Depth')
 chgdep.ShowModal()
 chgdep.Destroy()

We instantiate a ChangeDepth class. Then we call the ShowModal() dialog. We must not forget to destroy our dialog. Notice the visual difference between the dialog and the top level window. The dialog in the following figure has been activated. We cannot work with the toplevel window until the dialog is destroyed. There is a clear difference in the titlebar of the windows.

</html>

Core Widgets

In this section, we will introduce basic widgets in wxPython. Each widget will have a small code example.

  • wx.Button
  • wx.ToggleButton
  • wx.BitmapButton
  • wx.StaticLine
  • wx.StaticText
  • wx.StaticBox
  • wx.ComboBox
  • wx.CheckBox
  • wx.StatusBar
  • wx.RadioButton
  • wx.Gauge
  • wx.Slider
  • wx.ListBox
  • wx.SpinCtrl
  • wx.SplitterWindow
  • wx.ScrolledWindow
  • wx.Notebook
  • wx.Panel

#!/usr/bin/python
 
# buttons.py
 
import wx
import random
 
APP_SIZE_X = 300
APP_SIZE_Y = 200
 
class MyButtons(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(APP_SIZE_X, APP_SIZE_Y))
 
        wx.Button(self, 1, 'Close', (50, 130))
        wx.Button(self, 2, 'Random Move', (150, 130), (110, -1))
 
        self.Bind(wx.EVT_BUTTON, self.OnClose, id=1)
        self.Bind(wx.EVT_BUTTON, self.OnRandomMove, id=2)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def OnClose(self, event):
        self.Close(True)
 
    def OnRandomMove(self, event):
        screensize = wx.GetDisplaySize()
        randx = random.randrange(0, screensize.x - APP_SIZE_X)
        randy = random.randrange(0, screensize.y - APP_SIZE_Y)
        self.Move((randx, randy))
 
app = wx.App(0)
MyButtons(None, -1, 'buttons.py')
app.MainLoop()

#!/usr/bin/python
 
# togglebuttons.py
 
import wx
 
class ToggleButtons(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(300, 200))
 
        self.colour = wx.Colour(0, 0, 0)
 
        wx.ToggleButton(self, 1, 'red', (20, 25))
        wx.ToggleButton(self, 2, 'green', (20, 60))
        wx.ToggleButton(self, 3, 'blue', (20, 100))
 
        self.panel  = wx.Panel(self, -1, (150, 20), (110, 110), style=wx.SUNKEN_BORDER)
        self.panel.SetBackgroundColour(self.colour)
 
        self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleRed, id=1)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleGreen, id=2)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleBlue, id=3)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def ToggleRed(self, event):
        green = self.colour.Green()
        blue = self.colour.Blue()
        if  self.colour.Red():
            self.colour.Set(0, green, blue)
        else:
            self.colour.Set(255, green, blue)
        self.panel.SetBackgroundColour(self.colour)
 
    def ToggleGreen(self, event):
        red = self.colour.Red()
        blue = self.colour.Blue()
        if  self.colour.Green():
            self.colour.Set(red, 0, blue)
        else:
            self.colour.Set(red, 255, blue)
        self.panel.SetBackgroundColour(self.colour)
 
    def ToggleBlue(self, event):
        red = self.colour.Red()
        green = self.colour.Green()
        if  self.colour.Blue():
            self.colour.Set(red, green, 0)
        else:
            self.colour.Set(red, green, 255)
        self.panel.SetBackgroundColour(self.colour)
 
 
app = wx.App()
ToggleButtons(None, -1, 'togglebuttons.py')
app.MainLoop()

zetcode.com_wxpython_images_player.jpg

#!/usr/bin/python
 
# player.py
 
import wx
 
class Player(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 300))
        panel = wx.Panel(self, -1)
 
        pnl1 = wx.Panel(self, -1)
        pnl1.SetBackgroundColour(wx.BLACK)
        pnl2 = wx.Panel(self, -1 )
 
        menubar = wx.MenuBar()
        file = wx.Menu()
        play = wx.Menu()
        view = wx.Menu()
        tools = wx.Menu()
        favorites = wx.Menu()
        help = wx.Menu()
 
        file.Append(101, '&quit', 'Quit application')
 
        menubar.Append(file, '&File')
        menubar.Append(play, '&Play')
        menubar.Append(view, '&View')
        menubar.Append(tools, '&Tools')
        menubar.Append(favorites, 'F&avorites')
        menubar.Append(help, '&Help')
 
        self.SetMenuBar(menubar)
 
        slider1 = wx.Slider(pnl2, -1, 0, 0, 1000)
        pause = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-pause.png'))
        play  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-play.png'))
        next  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-next.png'))
        prev  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-prev.png'))
        volume = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/volume.png'))
        slider2 = wx.Slider(pnl2, -1, 0, 0, 100, size=(120, -1))
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
 
        hbox1.Add(slider1, 1)
        hbox2.Add(pause)
        hbox2.Add(play, flag=wx.RIGHT, border=5)
        hbox2.Add(next, flag=wx.LEFT, border=5)
        hbox2.Add(prev)
        hbox2.Add((-1, -1), 1)
        hbox2.Add(volume)
        hbox2.Add(slider2, flag=wx.TOP | wx.LEFT, border=5)
 
        vbox.Add(hbox1, flag=wx.EXPAND | wx.BOTTOM, border=10)
        vbox.Add(hbox2, 1, wx.EXPAND)
        pnl2.SetSizer(vbox)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(pnl1, 1, flag=wx.EXPAND)
        sizer.Add(pnl2, flag=wx.EXPAND | wx.BOTTOM | wx.TOP, border=10)
 
        self.SetMinSize((350, 300))
        self.CreateStatusBar()
        self.SetSizer(sizer)
 
        self.Centre()
        self.Show()
 
 
app = wx.App()
Player(None, -1, 'Player')
app.MainLoop()
 pause = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-pause.png'))

The creation of the wx.BitmapButton is self explanatory.

 hbox2.Add(prev)
 hbox2.Add((-1, -1), 1)
 hbox2.Add(volume)

Here we put some space between the previous button and the volume button. The proportion is set to 1. This means, that the space will grow, when we resize the window.

 self.SetMinSize((350, 300))

Here we set the minimum size of the player. It does not make much sense to shrink the window below some value.

wx.StaticLine

This widget displays a simple line on the window. It can be horizontal or vertical. centraleurope.py script displays central european countries and their population. The wx.StatLine makes it look more visually attractive.

wx.StaticLine styles

  • wx.LI_HORIZONTAL
  • wx.LI_VERTICAL

#!/usr/bin/python
 
# centraleurope.py
 
import wx
 
class CentralEurope(wx.Dialog):
    def __init__ (self, parent, ID, title):
        wx.Dialog.__init__(self, parent, ID, title, size=(360, 370))
 
        font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD)
        heading = wx.StaticText(self, -1, 'The Central Europe', (130, 15))
        heading.SetFont(font)
 
        wx.StaticLine(self, -1, (25, 50), (300,1))
 
        wx.StaticText(self, -1, 'Slovakia', (25, 80))
        wx.StaticText(self, -1, 'Hungary', (25, 100))
        wx.StaticText(self, -1, 'Poland', (25, 120))
        wx.StaticText(self, -1, 'Czech Republic', (25, 140))
        wx.StaticText(self, -1, 'Germany', (25, 160))
        wx.StaticText(self, -1, 'Slovenia', (25, 180))
        wx.StaticText(self, -1, 'Austria', (25, 200))
        wx.StaticText(self, -1, 'Switzerland', (25, 220))
 
        wx.StaticText(self, -1, '5 379 000', (250, 80))
        wx.StaticText(self, -1, '10 084 000', (250, 100))
        wx.StaticText(self, -1, '38 635 000', (250, 120))
        wx.StaticText(self, -1, '10 240 000', (250, 140))
        wx.StaticText(self, -1, '82 443 000', (250, 160))
        wx.StaticText(self, -1, '2 001 000', (250, 180))
        wx.StaticText(self, -1, '8 032 000', (250, 200))
        wx.StaticText(self, -1, '7 288 000', (250, 220))
 
        wx.StaticLine(self, -1, (25, 260), (300,1))
 
        sum = wx.StaticText(self, -1, '164 102 000', (240, 280))
        sum_font = sum.GetFont()
        sum_font.SetWeight(wx.BOLD)
        sum.SetFont(sum_font)
 
        wx.Button(self, 1, 'Ok', (140, 310), (60, 30))
 
        self.Bind(wx.EVT_BUTTON, self.OnOk, id=1)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def OnOk(self, event):
        self.Close()
 
app = wx.App()
CentralEurope(None, -1, 'centraleurope.py')
app.MainLoop()

#!/usr/bin/python
 
# statictext.py
 
import wx
 
class StaticText(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        lyrics1 = '''I'm giving up the ghost of love
in the shadows cast on devotion
She is the one that I adore
creed of my silent suffocation
Break this bittersweet spell on me
lost in the arms of destiny'''
 
        lyrics2 = '''There is something in the way
You're always somewhere else
Feelings have deserted me
To a point of no return
I don't believe in God
But I pray for you'''
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        panel = wx.Panel(self, -1)
        st1 = wx.StaticText(panel, -1, lyrics1, style=wx.ALIGN_CENTRE)
        st2 = wx.StaticText(panel, -1, lyrics2, style=wx.ALIGN_CENTRE)
        vbox.Add(st1, 1, wx.EXPAND |  wx.TOP | wx.BOTTOM, 15)
        vbox.Add(st2, 1, wx.EXPAND |  wx.TOP | wx.BOTTOM, 15)
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
 
app = wx.App()
StaticText(None, -1, 'statixtext.py')
app.MainLoop()

#!/usr/bin/python
 
# staticbox.py
 
import wx
 
class StaticBox(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(250, 230))
 
        wx.StaticBox(self, -1, 'Personal Info', (5, 5), size=(240, 170))
        wx.CheckBox(self, -1 ,'Male', (15, 30))
        wx.CheckBox(self, -1 ,'Married', (15, 55))
        wx.StaticText(self, -1, 'Age', (15, 95))
        wx.SpinCtrl(self, -1, '1', (55, 90), (60, -1), min=1, max=120)
        wx.Button(self, 1, 'Ok', (90, 185), (60, -1))
 
        self.Bind(wx.EVT_BUTTON, self.OnClose, id=1)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def OnClose(self, event):
        self.Close()
 
app = wx.App()
StaticBox(None, -1, 'staticbox.py')
app.MainLoop()

wx.ComboBox

wx.ComboBox is a combination of a single line text field, a button with a down arrow image and a listbox. When you press the button, a listbox appears. User can select only one option from the supplied string list.

wx.ComboBox has the following constructor:

 wx.ComboBox(int id, string value='', wx.Point pos=wx.DefaultPosition, wx.Size size=wx.DefaultSize,
    wx.List choices=wx.EmptyList, int style=0, wx.Validator validator=wx.DefaultValidator,
    string name=wx.ComboBoxNameStr)

wx.ComboBox styles

  • wx.CB_DROPDOWN
  • wx.CB_READONLY
  • wx.CB_SORT

#!/usr/bin/python
 
# combobox.py
 
import wx
 
class ComboBox(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(250, 270))
 
        panel = wx.Panel(self, -1, (75, 20), (100, 127), style=wx.SUNKEN_BORDER)
        self.picture = wx.StaticBitmap(panel)
        panel.SetBackgroundColour(wx.WHITE)
 
        self.images = ['tolstoy.jpg', 'feuchtwanger.jpg', 'balzac.jpg', 'pasternak.jpg',
                    'galsworthy.jpg', 'wolfe.jpg', 'zweig.jpg']
        authors = ['Leo Tolstoy', 'Lion Feuchtwanger', 'Honore de Balzac',
		'Boris Pasternak', 'John Galsworthy', 'Tom Wolfe', 'Stefan Zweig']
 
        wx.ComboBox(self, -1, pos=(50, 170), size=(150, -1), choices=authors, 
					style=wx.CB_READONLY)
        wx.Button(self, 1, 'Close', (80, 220))
 
        self.Bind(wx.EVT_BUTTON, self.OnClose, id=1)
        self.Bind(wx.EVT_COMBOBOX, self.OnSelect)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def OnClose(self, event):
        self.Close()
 
    def OnSelect(self, event):
        item = event.GetSelection()
        self.picture.SetFocus()
        self.picture.SetBitmap(wx.Bitmap('images/' + self.images[item]))
 
 
app = wx.App()
ComboBox(None, -1, 'combobox.py')
app.MainLoop()

#!/usr/bin/python
 
# checkbox.py
 
import wx
 
class CheckBox(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 170))
 
        panel = wx.Panel(self, -1)
        self.cb = wx.CheckBox(panel, -1, 'Show Title', (10, 10))
        self.cb.SetValue(True)
 
        wx.EVT_CHECKBOX(self, self.cb.GetId(), self.ShowTitle)
 
        self.Show()
        self.Centre()
 
    def ShowTitle(self, event):
        if self.cb.GetValue():
            self.SetTitle('checkbox.py')
        else: self.SetTitle('')
 
 
app = wx.App()
CheckBox(None, -1, 'checkbox.py')
app.MainLoop()

#!/usr/bin/python
 
# statusbar.py
 
import wx
 
class Statusbar(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 200), 
	style=wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX)
 
        panel = wx.Panel(self, 1)
 
        button = wx.Button(panel, 2, 'Button', (20, 20))
        text = wx.CheckBox(panel, 3, 'CheckBox', (20, 90))
        combo = wx.ComboBox(panel, 4, '', (120, 22))
        slider = wx.Slider(panel, 5, 6, 1, 10, (120, 90), (110, -1))
 
        panel.Bind(wx.EVT_ENTER_WINDOW, self.EnterPanel, id=1)
        button.Bind(wx.EVT_ENTER_WINDOW, self.EnterButton, id=2)
        text.Bind(wx.EVT_ENTER_WINDOW, self.EnterText, id=3)
        combo.Bind(wx.EVT_ENTER_WINDOW, self.EnterCombo, id=4)
        slider.Bind(wx.EVT_ENTER_WINDOW, self.EnterSlider, id=5)
 
        self.sb = self.CreateStatusBar()
        self.SetMaxSize((250, 200))
        self.SetMinSize((250, 200))
        self.Show(True)
        self.Centre()
 
    def EnterButton(self, event):
        self.sb.SetStatusText('Button widget')
        event.Skip()
 
    def EnterPanel(self, event):
        self.sb.SetStatusText('Panel widget')
        event.Skip()
 
    def EnterText(self, event):
        self.sb.SetStatusText('CheckBox widget')
        event.Skip()
 
    def EnterCombo(self, event):
        self.sb.SetStatusText('ComboBox widget')
        event.Skip()
 
    def EnterSlider(self, event):
        self.sb.SetStatusText('Slider widget')
        event.Skip()
 
app = wx.App()
Statusbar(None, -1, 'statusbar.py')
app.MainLoop()

wx.RadioButton

wx.RadioButton is a widget that allows the user to select a single exclusive choice from a group of options. A group of radio buttons is defined by having the first RadioButton in the group contain the wx.RB_GROUP style. All other RadioButtons defined after the first RadioButton with this style flag is set will be added to the function group of the first RadioButton. Declaring another RadioButton with the wx.RB_GROUP flag will start a new radio button group.

wx.RadioButton Styles

  • wx.RB_GROUP
  • wx.RB_SINGLE
  • wx.CB_SORT

#!/usr/bin/python
 
# radiobuttons.py
 
import wx
 
class RadioButtons(wx.Frame):
    def __init__(self, parent, id, title, size=(210, 150)):
        wx.Frame.__init__(self, parent, id, title)
        panel = wx.Panel(self, -1)
        self.rb1 = wx.RadioButton(panel, -1, 'Value A', (10, 10), style=wx.RB_GROUP)
        self.rb2 = wx.RadioButton(panel, -1, 'Value B', (10, 30))
        self.rb3 = wx.RadioButton(panel, -1, 'Value C', (10, 50))
 
        self.Bind(wx.EVT_RADIOBUTTON, self.SetVal, id=self.rb1.GetId())
        self.Bind(wx.EVT_RADIOBUTTON, self.SetVal, id=self.rb2.GetId())
        self.Bind(wx.EVT_RADIOBUTTON, self.SetVal, id=self.rb3.GetId())
 
        self.statusbar = self.CreateStatusBar(3)
        self.SetVal(True)
        self.Centre()
        self.Show(True)
 
    def SetVal(self, event):
        state1 = str(self.rb1.GetValue())
        state2 = str(self.rb2.GetValue())
        state3 = str(self.rb3.GetValue())
 
        self.statusbar.SetStatusText(state1, 0)
        self.statusbar.SetStatusText(state2, 1)
        self.statusbar.SetStatusText(state3, 2)
 
 
app = wx.App()
RadioButtons(None, -1, 'radiobuttons.py')
app.MainLoop()

# gauge.py
 
import wx
 
class Gauge(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(300, 200))
 
        self.timer = wx.Timer(self, 1)
        self.count = 0
 
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
 
        panel = wx.Panel(self, -1)
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
 
        self.gauge = wx.Gauge(panel, -1, 50, size=(250, 25))
        self.btn1 = wx.Button(panel, wx.ID_OK)
        self.btn2 = wx.Button(panel, wx.ID_STOP)
        self.text = wx.StaticText(panel, -1, 'Task to be done')
 
        self.Bind(wx.EVT_BUTTON, self.OnOk, self.btn1)
        self.Bind(wx.EVT_BUTTON, self.OnStop, self.btn2)
 
        hbox1.Add(self.gauge, 1, wx.ALIGN_CENTRE)
        hbox2.Add(self.btn1, 1, wx.RIGHT, 10)
        hbox2.Add(self.btn2, 1)
        hbox3.Add(self.text, 1)
        vbox.Add((0, 30), 0)
        vbox.Add(hbox1, 0, wx.ALIGN_CENTRE)
        vbox.Add((0, 20), 0)
        vbox.Add(hbox2, 1, wx.ALIGN_CENTRE)
        vbox.Add(hbox3, 1, wx.ALIGN_CENTRE)
 
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
    def OnOk(self, event):
        if self.count >= 50:
            return
        self.timer.Start(100)
        self.text.SetLabel('Task in Progress')
 
    def OnStop(self, event):
        if self.count == 0 or self.count >= 50 or not self.timer.IsRunning():
            return
        self.timer.Stop()
	    self.text.SetLabel('Task Interrupted')
        wx.Bell()
 
    def OnTimer(self, event):
        self.count = self.count +1
        self.gauge.SetValue(self.count)
        if self.count == 50:
            self.timer.Stop()
            self.text.SetLabel('Task Completed')
 
app = wx.App()
Gauge(None, -1, 'gauge.py')
app.MainLoop()

#!/usr/bin/python
 
# slider.py
 
import wx
 
class Slider(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(300, 150))
 
        panel = wx.Panel(self, -1)
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 
        self.sld = wx.Slider(panel, -1, 200, 150, 500, (-1, -1), (250, -1), 
		wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
        btn1 = wx.Button(panel, 1, 'Adjust')
        btn2 = wx.Button(panel, 2,  'Close')
 
        wx.EVT_BUTTON(self, 1, self.OnOk)
        wx.EVT_BUTTON(self, 2, self.OnClose)
 
        vbox.Add(self.sld, 1, wx.ALIGN_CENTRE)
        hbox.Add(btn1, 1, wx.RIGHT, 10)
        hbox.Add(btn2, 1)
        vbox.Add(hbox, 0, wx.ALIGN_CENTRE | wx.ALL, 20)
        panel.SetSizer(vbox)
        self.Centre()
        self.Show(True)
 
    def OnOk(self, event):
        val = self.sld.GetValue()
        self.SetSize((val*2, val))
 
    def OnClose(self, event):
        self.Close()
 
app = wx.App()
Slider(None, -1, 'slider.py')
app.MainLoop()

wx.ListBox

wx.Listbox is a widget that consists of a scrolling box and a list of items. User can select one or more items from that list. It depends on whether it is created as a single or multiple selection box. Selected items are marked.

listbox.py example consists of four different widgets. wx.Listbox, wx.TextCtrl, wx.StaticText and wx.Button. Widgets are organized with sizer-s. wx.Listbox has a list of six different world times. These abbreviations are explained in the second wx.TextCtrl. Current time is displayed in the wx.StaticText widget. wx.Timer widget is used to update the time every 100 miliseconds.

#!/usr/bin/python
 
# listbox.py
 
import wx
from time import *
 
class Listbox(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(550, 350))
 
        zone_list = ['CET', 'GMT', 'MSK', 'EST', 'PST', 'EDT']
 
        self.full_list = {
            'CET': 'Central European Time',
            'GMT': 'Greenwich Mean Time',
            'MSK': 'Moscow Time',
            'EST': 'Eastern Standard Time',
            'PST': 'Pacific Standard Time',
            'EDT': 'Eastern Daylight Time'
        }
 
        self.time_diff = {
            'CET' : 1,
            'GMT' : 0,
            'MSK': 3,
            'EST': -5,
            'PST': -8,
            'EDT': -4
        }
 
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
 
        self.timer = wx.Timer(self, 1)
        self.diff = 0
 
        panel = wx.Panel(self, -1)
 
        self.time_zones = wx.ListBox(panel, 26, (-1, -1), (170, 130), 
		zone_list, wx.LB_SINGLE)
        self.time_zones.SetSelection(0)
        self.text = wx.TextCtrl(panel, -1, 'Central European Time', 
		size=(200, 130), style=wx.TE_MULTILINE)
        self.time = wx.StaticText(panel, -1, '')
        btn = wx.Button(panel, wx.ID_CLOSE, 'Close')
        hbox1.Add(self.time_zones, 0, wx.TOP, 40)
        hbox1.Add(self.text, 1, wx.LEFT | wx.TOP, 40)
        hbox2.Add(self.time, 1, wx.ALIGN_CENTRE)
        hbox3.Add(btn, 0, wx.ALIGN_CENTRE)
        vbox.Add(hbox1, 0, wx.ALIGN_CENTRE)
        vbox.Add(hbox2, 1, wx.ALIGN_CENTRE)
        vbox.Add(hbox3, 1, wx.ALIGN_CENTRE)
 
        panel.SetSizer(vbox)
 
        self.timer.Start(100)
 
        wx.EVT_BUTTON(self, wx.ID_CLOSE, self.OnClose)
        wx.EVT_LISTBOX(self, 26, self.OnSelect)
        wx.EVT_TIMER(self, 1, self.OnTimer)
 
        self.Show(True)
        self.Centre()
 
    def OnClose(self, event):
        self.Close()
 
    def OnSelect(self, event):
        index = event.GetSelection()
        time_zone = self.time_zones.GetString(index)
        self.diff = self.time_diff[time_zone]
        self.text.SetValue(self.full_list[time_zone])
 
    def OnTimer(self, event):
        ct = gmtime()
        print_time = (ct[0], ct[1], ct[2], ct[3]+self.diff, 
			ct[4], ct[5], ct[6], ct[7], -1)
        self.time.SetLabel(strftime("%H:%M:%S", print_time))
 
 
app = wx.App()
Listbox(None, -1, 'listbox.py')
app.MainLoop()

#!/usr/bin/python
 
# spinctrl.py
 
import wx
 
class Converter(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size=(350, 310))
 
        wx.StaticText(self, -1, 'Convert Fahrenheit temperature to Celsius', (20,20))
        wx.StaticText(self, -1, 'Fahrenheit: ', (20, 80))
        wx.StaticText(self, -1, 'Celsius: ', (20, 150))
        self.celsius =  wx.StaticText(self, -1, '', (150, 150))
        self.sc = wx.SpinCtrl(self, -1, '',  (150, 75), (60, -1))
        self.sc.SetRange(-459, 1000)
        self.sc.SetValue(0)
        compute_btn = wx.Button(self, 1, 'Compute', (70, 250))
        compute_btn.SetFocus()
        clear_btn = wx.Button(self, 2, 'Close', (185, 250))
 
        wx.EVT_BUTTON(self, 1, self.OnCompute)
        wx.EVT_BUTTON(self, 2, self.OnClose)
        wx.EVT_CLOSE(self, self.OnClose)
 
        self.Centre()
        self.ShowModal()
        self.Destroy()
 
    def OnCompute(self, event):
        fahr = self.sc.GetValue()
        cels = round((fahr-32)*5/9.0, 2)
        self.celsius.SetLabel(str(cels))
 
    def OnClose(self, event):
        self.Destroy()
 
app = wx.App()
Converter(None, -1, 'Converter')
app.MainLoop()

#!/usr/bin/python
 
# splitterwindow.py
 
import wx
 
class Splitterwindow(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(350, 300))
 
        quote = '''Whether you think that you can, or that you can't, you are
usually right'''
 
        splitter = wx.SplitterWindow(self, -1)
        panel1 = wx.Panel(splitter, -1)
        wx.StaticText(panel1, -1, quote, (100, 100), style=wx.ALIGN_CENTRE)
 
        panel1.SetBackgroundColour(wx.LIGHT_GREY)
        panel2 = wx.Panel(splitter, -1)
        panel2.SetBackgroundColour(wx.WHITE)
        splitter.SplitVertically(panel1, panel2)
        self.Centre()
        self.Show(True)
 
app = wx.App()
Splitterwindow(None, -1, 'splitterwindow.py')
app.MainLoop()

wx.ScrolledWindow

This is one of the container widgets. It can be useful, when we have a larger area than a window can display. In our example, we demonstrate such a case. We place a large image into our window. When the window is smaller than our image, Scrollbars are displayed automatically.

zetcode.com_wxpython_images_scrolledwindow.jpg

#!/usr/bin/python
 
# scrolledwindow.py
 
import wx
 
class ScrolledWindow(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(500, 400))
 
        sw = wx.ScrolledWindow(self)
        bmp = wx.Image('images/aliens.jpg',wx.BITMAP_TYPE_JPEG).ConvertToBitmap()
        wx.StaticBitmap(sw, -1, bmp)
        sw.SetScrollbars(20, 20, 55, 40)
        sw.Scroll(50,10)
        self.Centre()
        self.Show()
 
app = wx.App()
ScrolledWindow(None, -1, 'Aliens')
app.MainLoop()

#!/usr/bin/python
 
# notebook.py
 
import wx
import wx.lib.sheet as sheet
 
 
class MySheet(sheet.CSheet):
    def __init__(self, parent):
        sheet.CSheet.__init__(self, parent)
        self.SetNumberRows(50)
        self.SetNumberCols(50)
 
class Notebook(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(600, 500))
        menubar = wx.MenuBar()
        file = wx.Menu()
        file.Append(101, 'Quit', '' )
        menubar.Append(file, '&File')
        self.SetMenuBar(menubar)
 
        wx.EVT_MENU(self, 101, self.OnQuit)
 
        nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM)
        self.sheet1 = MySheet(nb)
        self.sheet2 = MySheet(nb)
        self.sheet3 = MySheet(nb)
 
        nb.AddPage(self.sheet1, 'Sheet1')
        nb.AddPage(self.sheet2, 'Sheet2')
        nb.AddPage(self.sheet3, 'Sheet3')
 
        self.sheet1.SetFocus()
        self.StatusBar()
        self.Centre()
        self.Show()
 
    def StatusBar(self):
        self.statusbar = self.CreateStatusBar()
 
    def OnQuit(self, event):
        self.Close()
 
app = wx.App()
Notebook(None, -1, 'notebook.py')
app.MainLoop()

#!/usr/bin/python
 
# panels.py
 
import wx
 
 
class Panels(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)
 
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        splitter = wx.SplitterWindow(self, -1)
 
        vbox1 = wx.BoxSizer(wx.VERTICAL)
        panel1 = wx.Panel(splitter, -1)
        panel11 = wx.Panel(panel1, -1, size=(-1, 40))
        panel11.SetBackgroundColour('#53728c')
        st1 = wx.StaticText(panel11, -1, 'Feeds', (5, 5))
        st1.SetForegroundColour('WHITE')
 
        panel12 = wx.Panel(panel1, -1, style=wx.BORDER_SUNKEN)
        panel12.SetBackgroundColour('WHITE')
 
        vbox1.Add(panel11, 0, wx.EXPAND)
        vbox1.Add(panel12, 1, wx.EXPAND)
 
        panel1.SetSizer(vbox1)
 
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        panel2 = wx.Panel(splitter, -1)
        panel21 = wx.Panel(panel2, -1, size=(-1, 40), style=wx.NO_BORDER)
        st2 = wx.StaticText(panel21, -1, 'Articles', (5, 5))
        st2.SetForegroundColour('WHITE')
 
        panel21.SetBackgroundColour('#53728c')
        panel22 = wx.Panel(panel2, -1, style=wx.BORDER_RAISED)
 
        panel22.SetBackgroundColour('WHITE')
        vbox2.Add(panel21, 0, wx.EXPAND)
        vbox2.Add(panel22, 1, wx.EXPAND)
 
        panel2.SetSizer(vbox2)
 
        toolbar = self.CreateToolBar()
        toolbar.AddLabelTool(1, 'Exit', wx.Bitmap('icons/stock_exit.png'))
        toolbar.Realize()
        self.Bind(wx.EVT_TOOL, self.ExitApp, id=1)
 
        hbox.Add(splitter, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
        self.SetSizer(hbox)
        self.CreateStatusBar()
        splitter.SplitVertically(panel1, panel2)
        self.Centre()
        self.Show(True)
 
 
    def ExitApp(self, event):
        self.Close()
 
 
app = wx.App()
Panels(None, -1, 'Panels')
app.MainLoop()
 hbox = wx.BoxSizer(wx.HORIZONTAL)
 splitter = wx.SplitterWindow(self, -1)

The wx.SplitterWindow will split our window into two parts.

One panel will be placed on the left and one on the right side. Each one will have other two panels. One will create a header and the other one will take up the rest of the parent panel. Together we will use six panels.

 panel11 = wx.Panel(panel1, -1, size=(-1, 40))
 panel11.SetBackgroundColour('#53728c')
 st1 = wx.StaticText(panel11, -1, 'Feeds', (5, 5))
 st1.SetForegroundColour('WHITE')
 ... 
 vbox1.Add(panel11, 0, wx.EXPAND)

Here we create the header panel. The header height is 40px. The color is set to dark blue. (#53728c) We put a wx.StaticText inside the header panel. The position is 5px from left and 5px from top so that we have some space between the panel and the static text. The color of the static text is set to white. In the end, we make the panel11 expandable and set the proportion to 0.

 panel12 = wx.Panel(panel1, -1, style=wx.BORDER_SUNKEN)
 panel12.SetBackgroundColour('WHITE')
 ...
 vbox1.Add(panel12, 1, wx.EXPAND)

The bottom panel is created with wx.BORDER_SUNKEN style. The color is set to white. We make it expandable and set the proportion parameter to 1.

</html>

Advanced widgets in wxPython

In the following chapters we will talk about advanced widgets. A big advantage of wxPython over a competing PyGTK is the availability of a huge amount of advanced widgets. PyGTK is a layer over a C based GKT+ toolkit. It does not provide new widgets. In contrast, wxPython is a layer over wxWidgets a C++ based toolkit. wxWidgets consists of a large group of widgets. All this widgets are created in C++. wxPython is a glue that combines python language with this toolkit. If we want to have a grid widget in our application using PyGTK, we have to create it ourselves. Such a widget is quite complicated. Not to mention the speed penalty. Dynamic languages like Python, PERL or Ruby are not suitable for such tasks. Dynamic languages are great in various areas. They are simple to use. They are great for prototyping, in house developing or for studying computer programming. If we need a quick solution or we need an application, that will change rapidly over a short period of time, dynamic languages are superior to compiled languages. On the other hand, if we develop resource intensive applications, games, high quality multimedia applications, there is no competition to C++. wxPython has several well known advanced widgets. For example a tree widget, an html window, a grid widget, a listbox widget, a list widget or an editor with advanced styling capabilities. wxPython and wxWidgets are being developed all the time. New widgets and features emerge with every major release. At the time when I write these words a wxPython 2.8.3.0 has been released just two days ago. (22-Mar-2007). A //wx.ListBox// widget is used for displaying and working with a list of items. As it's name indicates, it is a rectangle that has a list of strings inside. We could use it for displaying a list of mp3 files, book names, module names of a larger project or names of our friends. A //wx.ListBox// can be created in two different states. In a single selection state or a multiple selection state. The single selection state is the default state. There are two significant events in //wx.ListBox//. The first one is the //wx.EVT_COMMAND_LISTBOX_SELECTED// event. This event is generated when we select a string in a //wx.ListBox//. The second one is the //wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED// event. It is generated when we double click an item in a //wx.ListBox//. The number of elements inside a wx.ListBox is limited on GTK platform. According to the documentation, it is currently around 2000 elements. Quite enough, I think. The elements are numbered from zero. Scrollbars are displayed automatically if needed. The constructor of a wx.ListBox widget is as follows: <code python> wx.ListBox(wx.Window parent, int id=-1, wx.Point pos=wx.DefaultPosition, wx.Size size=wx.DefaultSize, list choices=[], long style=0, wx.Validator validator=wx.DefaultValidator, string name=wx.ListBoxNameStr) </code> There is a choices parameter. If we put some values there, they will be displayed from the construction of the widget. This parameter is empty by default. In our code example we have a listbox and four buttons. Each of them calls a different method of our listbox. If we want to append a new item, we call the //Append()// method. If we want to delete an item, we call the //Delete()// method. To clear all strings in a listbox, we call the Clear() method. <code python> #!/usr/bin/python # listbox.py import wx ID_NEW = 1 ID_RENAME = 2 ID_CLEAR = 3 ID_DELETE = 4 class ListBox(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 220)) panel = wx.Panel(self, -1) hbox = wx.BoxSizer(wx.HORIZONTAL) self.listbox = wx.ListBox(panel, -1) hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20) btnPanel = wx.Panel(panel, -1) vbox = wx.BoxSizer(wx.VERTICAL) new = wx.Button(btnPanel, ID_NEW, 'New', size=(90, 30)) ren = wx.Button(btnPanel, ID_RENAME, 'Rename', size=(90, 30)) dlt = wx.Button(btnPanel, ID_DELETE, 'Delete', size=(90, 30)) clr = wx.Button(btnPanel, ID_CLEAR, 'Clear', size=(90, 30)) self.Bind(wx.EVT_BUTTON, self.NewItem, id=ID_NEW) self.Bind(wx.EVT_BUTTON, self.OnRename, id=ID_RENAME) self.Bind(wx.EVT_BUTTON, self.OnDelete, id=ID_DELETE) self.Bind(wx.EVT_BUTTON, self.OnClear, id=ID_CLEAR) self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename) vbox.Add((-1, 20)) vbox.Add(new) vbox.Add(ren, 0, wx.TOP, 5) vbox.Add(dlt, 0, wx.TOP, 5) vbox.Add(clr, 0, wx.TOP, 5) btnPanel.SetSizer(vbox) hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20) panel.SetSizer(hbox) self.Centre() self.Show(True) def NewItem(self, event): text = wx.GetTextFromUser('Enter a new item', 'Insert dialog') if text != '': self.listbox.Append(text) def OnRename(self, event): sel = self.listbox.GetSelection() text = self.listbox.GetString(sel) renamed = wx.GetTextFromUser('Rename item', 'Rename dialog', text) if renamed != '': self.listbox.Delete(sel) self.listbox.Insert(renamed, sel) def OnDelete(self, event): sel = self.listbox.GetSelection() if sel != -1: self.listbox.Delete(sel) def OnClear(self, event): self.listbox.Clear() app = wx.App() ListBox(None, -1, 'ListBox') app.MainLoop() </code> <code python> self.listbox = wx.ListBox(panel, -1) hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20) </code> We create an empty //wx.ListBox//. We put a 20px border around the listbox. <code python> self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename) </code> We bind a //wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED// event type with the //OnRename()// method using the //wx.EVT_LISTBOX_DCLICK// event binder. This way we show a rename dialog if we double click on a specific element in the listbox. <code python> def NewItem(self, event): text = wx.GetTextFromUser('Enter a new item', 'Insert dialog') if text != '': self.listbox.Append(text) </code> We call the //NewItem()// method by clicking on the New button. This method shows a //wx.GetTextFromUser// dialog window. The text that we enter is returned to the text variable. If the text is not empty, we append it to the listbox with the //Append()// method. <code python> def OnDelete(self, event): sel = self.listbox.GetSelection() if sel != -1: self.listbox.Delete(sel) </code> Deleting an item is done in two steps. First we find the index of the selected item by calling the //GetSelection()// method. Then we delete the item with the //Delete()// method. The parametor to the //Delete()// method is the selected index. <code python> self.listbox.Delete(sel) self.listbox.Insert(renamed, sel) </code> Notice, how we managed to rename a string. //wx.ListBox// widget has no Rename() method. We did this functionality by deleting the previously selected string and inserting a new string into the predecessor's position. <code python> def OnClear(self, event): self.listbox.Clear() </code> The easiest thing is to clear the whole listbox. We simply call the //Clear()// method. {{ http://zetcode.com/wxpython/images/listbox2.png }} The //wx.html.HtmlWindow// widget displays html pages. It is not a full-fledged browser. We can do interesting things with //wx.html.HtmlWindow// widget. **Special formatting** For example in the following script we will create a window, that will display basic statistics. This formatting would be very hard if possible to create without //wx.html.HtmlWindow// widget. <code python> #!/usr/bin/python import wx import wx.html as html ID_CLOSE = 1 page = '&lt;html&gt;&lt;body bgcolor="#8e8e95"&gt;&lt;table cellspacing="5" border="0" width="250"> \ &lt;tr width="200" align="left"&gt; \ &lt;td bgcolor="#e7e7e7">&amp;nbsp;&amp;nbsp;Maximum&lt;/td&gt; \ &lt;td bgcolor="#aaaaaa"&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;9000&lt;/b&gt;&lt;/td&gt; \ &lt;/tr&gt; \ &lt;tr align="left"&gt; \ &lt;td bgcolor="#e7e7e7"&gt;&amp;nbsp;&amp;nbsp;Mean&lt;/td&gt; \ &lt;td bgcolor="#aaaaaa">&amp;nbsp;&amp;nbsp;&lt;b&gt;6076&lt;/b&gt;&lt;/td&gt; \ &lt;/tr&gt; \ &lt;tr align="left"> \ &lt;td bgcolor="#e7e7e7">&amp;nbsp;&amp;nbsp;Minimum&lt;/td&gt; \ &lt;td bgcolor="#aaaaaa"&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;3800&lt;/b&gt;&lt;/td&gt; \ &lt;/tr&gt; \ &lt;tr align="left"&gt; \ &lt;td bgcolor="#e7e7e7"&gt;&amp;nbsp;&amp;nbsp;Median&lt;/td&gt; \ &lt;td bgcolor="#aaaaaa"&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;6000&lt;/b&gt;&lt;/td&gt; \ &lt;/tr&gt; \ &lt;tr align="left"&gt; \ &lt;td bgcolor="#e7e7e7"&gt;&amp;nbsp;&amp;nbsp;Standard Deviation&lt;/td&gt; \ &lt;td bgcolor="#aaaaaa"&gt;&amp;nbsp;&amp;nbsp;&lt;b&gt;6076&lt;/b&gt;&lt;/td&gt; \ &lt;/tr&gt; \ &lt;/body&gt;&lt;/table&gt;&lt;/html&gt;' class MyFrame(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(400, 290)) panel = wx.Panel(self, -1) vbox = wx.BoxSizer(wx.VERTICAL) hbox = wx.BoxSizer(wx.HORIZONTAL) htmlwin = html.HtmlWindow(panel, -1, style=wx.NO_BORDER) htmlwin.SetBackgroundColour(wx.RED) htmlwin.SetStandardFonts() htmlwin.SetPage(page) vbox.Add((-1, 10), 0) vbox.Add(htmlwin, 1, wx.EXPAND | wx.ALL, 9) bitmap = wx.StaticBitmap(panel, -1, wx.Bitmap('images/newt.png')) hbox.Add(bitmap, 1, wx.LEFT | wx.BOTTOM | wx.TOP, 10) buttonOk = wx.Button(panel, ID_CLOSE, 'Ok') self.Bind(wx.EVT_BUTTON, self.OnClose, id=ID_CLOSE) hbox.Add((100, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT) hbox.Add(buttonOk, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10) vbox.Add(hbox, 0, wx.EXPAND) panel.SetSizer(vbox) self.Centre() self.Show(True) def OnClose(self, event): self.Close() app = wx.App(0) MyFrame(None, -1, 'Basic Statistics') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/basicw.jpg }} **Help window** We can use //wx.html.HtmlWindow// to provide help in our application. We can create a standalone window or we can create a window, that is going to be a part of the application. The following script will create a help window using the latter idea. <code python> #!/usr/bin/python # helpwindow.py import wx import wx.html as html class HelpWindow(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(570, 400)) toolbar = self.CreateToolBar() toolbar.AddLabelTool(1, 'Exit', wx.Bitmap('icons/exit.png')) toolbar.AddLabelTool(2, 'Help', wx.Bitmap('icons/help.png')) toolbar.Realize() self.splitter = wx.SplitterWindow(self, -1) self.panelLeft = wx.Panel(self.splitter, -1, style=wx.BORDER_SUNKEN) self.panelRight = wx.Panel(self.splitter, -1) vbox2 = wx.BoxSizer(wx.VERTICAL) header = wx.Panel(self.panelRight, -1, size=(-1, 20)) header.SetBackgroundColour('#6f6a59') header.SetForegroundColour('WHITE') hbox = wx.BoxSizer(wx.HORIZONTAL) st = wx.StaticText(header, -1, 'Help', (5, 5)) font = st.GetFont() font.SetPointSize(9) st.SetFont(font) hbox.Add(st, 1, wx.TOP | wx.BOTTOM | wx.LEFT, 5) close = wx.BitmapButton(header, -1, wx.Bitmap('icons/fileclose.png', wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER) close.SetBackgroundColour('#6f6a59') hbox.Add(close, 0) header.SetSizer(hbox) vbox2.Add(header, 0, wx.EXPAND) help = html.HtmlWindow(self.panelRight, -1, style=wx.NO_BORDER) help.LoadPage('help.html') vbox2.Add(help, 1, wx.EXPAND) self.panelRight.SetSizer(vbox2) self.panelLeft.SetFocus() self.splitter.SplitVertically(self.panelLeft, self.panelRight) self.splitter.Unsplit() self.Bind(wx.EVT_BUTTON, self.CloseHelp, id=close.GetId()) self.Bind(wx.EVT_TOOL, self.OnClose, id=1) self.Bind(wx.EVT_TOOL, self.OnHelp, id=2) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) self.CreateStatusBar() self.Centre() self.Show(True) def OnClose(self, event): self.Close() def OnHelp(self, event): self.splitter.SplitVertically(self.panelLeft, self.panelRight) self.panelLeft.SetFocus() def CloseHelp(self, event): self.splitter.Unsplit() self.panelLeft.SetFocus() def OnKeyPressed(self, event): keycode = event.GetKeyCode() if keycode == wx.WXK_F1: self.splitter.SplitVertically(self.panelLeft, self.panelRight) self.panelLeft.SetFocus() app = wx.App() HelpWindow(None, -1, 'HelpWindow') app.MainLoop() </code> The help window is hidden in the beginning. We can show it by clicking on the help button on the toolbar or by pressing F1. The help window appears on the right side of the application. To hide the help window, we click on the close button. <code python> self.splitter.SplitVertically(self.panelLeft, self.panelRight) self.splitter.Unsplit() </code> We create left a right panels and split them vertically. After that, we call the //Unsplit()// method. By default the method hides the right or bottom panes. We divide the right panel into two parts. The header and the body of the panel. The header is an adjusted //wx.Panel//. The header consists of a static text and a bitmap button. We put //wx.html.Window// into the body of the panel. <code python> close = wx.BitmapButton(header, -1, wx.Bitmap('icons/fileclose.png', wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER) close.SetBackgroundColour('#6f6a59') </code> The bitmap button style is set to //wx.NO_BORDER//. The background color is set to the color of the header panel. This is done in order to make the button appear as a part of the header. <code python> help = html.HtmlWindow(self.panelRight, -1, style=wx.NO_BORDER) help.LoadPage('help.html') </code> We create a <a>wx.html.HtmlWindow</a> widget on the right panel. We have our html code in a separate file. This time we call the //LoadPage()// method to obtain the html code. <code python> self.panelLeft.SetFocus() </code> We set focus on the left panel. We can launch the help window with the F1 key. In order to control a window with a keyboard, it must have the focus. If we did not set the focus, we would have to first click on the panel and only then we could launch the help window with the F1 key press. <code python> def OnHelp(self, event): self.splitter.SplitVertically(self.panelLeft, self.panelRight) self.panelLeft.SetFocus() </code> To show the help window, we call the //OnHelp()// method. It splits the two panels vertically. We must not forget to set the focus again, because the initial focus is lost by splitting. The following is the html file, that we load in our application. <code python> &lt;html&gt; &lt;body bgcolor="#ababab"&gt; &lt;h4&gt;Table of Contents&lt;/h4&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="#basic"&gt;Basic statistics&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#advanced"&gt;Advanced statistics&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#intro"&gt;Introducing Newt&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#charts"&gt;Working with charts&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#pred"&gt;Predicting values&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#neural"&gt;Neural networks&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="#glos"&gt;Glossary&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt; &lt;a name="basic"&gt; &lt;h6&gt;Basic Statistics&lt;/h6&gt; Overview of elementary concepts in statistics. Variables. Correlation. Measurement scales. Statistical significance. Distributions. Normality assumption. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="advanced"&gt; &lt;h6&gt;Advanced Statistics&lt;/h6&gt; Overview of advanced concepts in statistics. Anova. Linear regression. Estimation and hypothesis testing. Error terms. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="intro"&gt; &lt;h6&gt;Introducing Newt&lt;/h6&gt; Introducing the basic functionality of the Newt application. Creating sheets. Charts. Menus and Toolbars. Importing data. Saving data in various formats. Exporting data. Shortcuts. List of methods. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="charts"&gt; &lt;h6&gt;Charts&lt;/h6&gt; Working with charts. 2D charts. 3D charts. Bar, line, box, pie, range charts. Scatterplots. Histograms. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="pred"&gt; &lt;h6&gt;Predicting values&lt;/h6&gt; Time series and forecasting. Trend Analysis. Seasonality. Moving averages. Univariate methods. Multivariate methods. Holt-Winters smoothing. Exponential smoothing. ARIMA. Fourier analysis. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="neural"&gt; &lt;h6&gt;Neural networks&lt;/h6&gt; Overview of neural networks. Biology behind neural networks. Basic artificial Model. Training. Preprocessing. Postprocessing. Types of neural networks. &lt;/a&gt; &lt;/p&gt; &lt;p&gt; &lt;a name="glos"&gt; &lt;h6&gt;Glossary&lt;/h6&gt; Terms and definitions in statistics. &lt;/a&gt; &lt;/p&gt; &lt;/body&gt; &lt;/html&gt; </code> <code python> &lt;li&gt;&lt;a href="#basic"&gt;Basic statistics&lt;/a&gt;&lt;/li&gt; ... &lt;a name="basic"&gt; </code> Normally I would write &lt;div id="basic"&gt; ... &lt;/div&gt;. Both are correct html notations. But //wx.html.HtmlWindow// supports only the first one. //wx.html.HtmlWindow// supports only a subset of the html markup language. {{ http://zetcode.com/wxpython/images/helpwindow.png }} A //wx.ListCtrl// is a graphical representation of a list of items. A //wx.ListBox// can only have one column. //wx.ListCtrl// can have more than one column. //wx.ListCtrl// is a very common and useful widget. For example a file manager uses a //wx.ListCtrl// to display directories and files on the file system. A cd burner application displays files to be burned inside a //wx.ListCtrl//. A //wx.ListCtrl// can be used in three different formats. In a list view, report view or a icon view. These formats are controled by the //wx.ListCtrl// window styles. //wx.LC_REPORT//, //wx.LC_LIST// and //wx.LC_ICON//. <code python> wx.ListCtrl(wx.Window parent, int id, wx.Point pos = (-1, -1), wx.Size size = (-1, -1), int style = wx.LC_ICON, wx.Validator validator = wx.DefaultValidator, string name = wx.ListCtrlNameStr) </code> **wx.ListCtrl styles ** * wx.LC_LIST * wx.LC_REPORT * wx.LC_VIRTUAL * wx.LC_ICON * wx.LC_SMALL_ICON * wx.LC_ALIGN_LEFT * wx.LC_EDIT_LABELS * wx.LC_NO_HEADER * wx.LC_SORT_ASCENDING * wx.LC_SORT_DESCENDING * wx.LC_HRULES * wx.LC_VRULES **Simple example** In the first example we will introduce basic functionality of a //wx.ListCtrl//. <code python> #!/usr/bin/python # actresses.py import wx import sys packages = [('jessica alba', 'pomona', '1981'), ('sigourney weaver', 'new york', '1949'), ('angelina jolie', 'los angeles', '1975'), ('natalie portman', 'jerusalem', '1981'), ('rachel weiss', 'london', '1971'), ('scarlett johansson', 'new york', '1984' )] class Actresses(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT) self.list.InsertColumn(0, 'name', width=140) self.list.InsertColumn(1, 'place', width=130) self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) for i in packages: index = self.list.InsertStringItem(sys.maxint, i[0]) self.list.SetStringItem(index, 1, i[1]) self.list.SetStringItem(index, 2, i[2]) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True) app = wx.App() Actresses(None, -1, 'actresses') app.MainLoop() </code> <code python> self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT) </code> We create a //wx.ListCtrl// with a wx.LC_REPORT style. <code python> self.list.InsertColumn(0, 'name', width=140) self.list.InsertColumn(1, 'place', width=130) self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) </code> We insert three columns. We can specify the //width// of the column and the //format// of the column. The default format is //wx.LIST_FORMAT_LEFT//. <code python> for i in packages: index = self.list.InsertStringItem(sys.maxint, i[0]) self.list.SetStringItem(index, 1, i[1]) self.list.SetStringItem(index, 2, i[2]) </code> We insert data into the //wx.ListCtrl// using two methods. Each row begins with a //InsertStringItem()// method. The first parameter of the method specifies the row number. By giving a sys.maxint we ensure, that each call will insert data after the last row. The method returns the row index. The //SetStringItem()// method adds data to the consecutive columns of the current row. **Mixins** Mixins are classes that further enhance the functionality of a //wx.ListCtrl//. Mixin classes are so called helper classes. They are located in //wx.lib.mixins.listctrl// module. In order to use them, the programmer has to inherit from these classes. There are five available mixins. As of 2.8.1.1. * wx.ColumnSorterMixin * wx.ListCtrlAutoWidthMixin * wx.ListCtrlSelectionManagerMix * wx.TextEditMixin * wx.CheckListCtrlMixin //wx.ColumnSorterMixin// is a mixin that enables sorting of columns in a report view. //wx.ListCtrlAutoWidthMixin// class automatically resizes the last column to the end of the //wx.ListCtrl//. By default, the last column does not take the remaining space. See the previous example. //wx.ListCtrlSelectionManagerMix// defines platform independent selection policy. //wx.TextEditMixin// enables text to be edited. //wx.CheckListCtrlMixin// adds a check box to each row. This way we can control rows. We can set every row to be checked or unchecked. The following code shows, how we can use //ListCtrlAutoWidthMixin// <code python> #!/usr/bin/python # autowidth.py import wx import sys from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin actresses = [('jessica alba', 'pomona', '1981'), ('sigourney weaver', 'new york', '1949'), ('angelina jolie', 'los angeles', '1975'), ('natalie portman', 'jerusalem', '1981'), ('rachel weiss', 'london', '1971'), ('scarlett johansson', 'new york', '1984' )] class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) ListCtrlAutoWidthMixin.__init__(self) class Actresses(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) self.list = AutoWidthListCtrl(panel) self.list.InsertColumn(0, 'name', width=140) self.list.InsertColumn(1, 'place', width=130) self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) for i in actresses: index = self.list.InsertStringItem(sys.maxint, i[0]) self.list.SetStringItem(index, 1, i[1]) self.list.SetStringItem(index, 2, i[2]) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True) app = wx.App() Actresses(None, -1, 'actresses') app.MainLoop() </code> We change the previous example a bit. <code python> from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin </code> Here we import the mixin. <code python> class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) ListCtrlAutoWidthMixin.__init__(self) </code> We create a new //AutoWidthListCtrl// class. This class will inherit from //wx.ListCtrl// and //ListCtrlAutoWidthMixin//. This is called **multiple inheritance**. The last column will automatically resize to take up the remaining width of a //wx.ListCtrl//. {{ http://zetcode.com/wxpython/images/autowidthl.jpg }} {{ http://zetcode.com/wxpython/images/autowidthw.jpg }} In the following example we will show, how we can create sortable columns. If we click on the column header, the corresponding rows in a column are sorted. <code python> #!/usr/bin/python # sorted.py import wx import sys from wx.lib.mixins.listctrl import ColumnSorterMixin actresses = { 1 : ('jessica alba', 'pomona', '1981'), 2 : ('sigourney weaver', 'new york', '1949'), 3 : ('angelina jolie', 'los angeles', '1975'), 4 : ('natalie portman', 'jerusalem', '1981'), 5 : ('rachel weiss', 'london', '1971'), 6 : ('scarlett johansson', 'new york', '1984') } class SortedListCtrl(wx.ListCtrl, ColumnSorterMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) ColumnSorterMixin.__init__(self, len(actresses)) self.itemDataMap = actresses def GetListCtrl(self): return self class Actresses(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) self.list = SortedListCtrl(panel) self.list.InsertColumn(0, 'name', width=140) self.list.InsertColumn(1, 'place', width=130) self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) items = actresses.items() for key, data in items: index = self.list.InsertStringItem(sys.maxint, data[0]) self.list.SetStringItem(index, 1, data[1]) self.list.SetStringItem(index, 2, data[2]) self.list.SetItemData(index, key) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True) app = wx.App() Actresses(None, -1, 'actresses') app.MainLoop() </code> We will again use the example with actresses. <code python> ColumnSorterMixin.__init__(self, len(actresses)) </code> The //ColumnSorterMixin// accepts one argument. It is the number of columns to be sorted. <code python> self.itemDataMap = actresses </code> We must map our data to be displayed in a list control to the //itemDataMap// attribute. The data must be in a dictionary data type. <code python> def GetListCtrl(self): return self </code> We must create a //GetListCtrl()// method. This method returns the //wx.ListCtrl// widget that is going to be sorted. <code python> self.list.SetItemData(index, key) </code> We must assosiate each row with a special index. This is done with the //SetItemData// method. **Reader** A reader is a complex example showing two list controls in a report view. <code python> #!/usr/bin/python # reader.py import wx articles = [['Mozilla rocks', 'The year of the Mozilla', 'Earth on Fire'], ['Gnome pretty, Gnome Slow', 'Gnome, KDE, Icewm, XFCE', 'Where is Gnome heading?'], ['Java number one language', 'Compiled languages, intrepreted Languages', 'Java on Desktop?']] class ListCtrlLeft(wx.ListCtrl): def __init__(self, parent, id): wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_NO_HEADER | wx.LC_SINGLE_SEL) images = ['icons/java.png', 'icons/gnome.png', 'icons/mozilla.png'] self.parent = parent self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelect) self.il = wx.ImageList(32, 32) for i in images: self.il.Add(wx.Bitmap(i)) self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) self.InsertColumn(0, '') for i in range(3): self.InsertStringItem(0, '') self.SetItemImage(0, i) def OnSize(self, event): size = self.parent.GetSize() self.SetColumnWidth(0, size.x-5) event.Skip() def OnSelect(self, event): window = self.parent.GetGrandParent().FindWindowByName('ListControlOnRight') index = event.GetIndex() window.LoadData(index) def OnDeSelect(self, event): index = event.GetIndex() self.SetItemBackgroundColour(index, 'WHITE') def OnFocus(self, event): self.SetItemBackgroundColour(0, 'red') class ListCtrlRight(wx.ListCtrl): def __init__(self, parent, id): wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_NO_HEADER | wx.LC_SINGLE_SEL) self.parent = parent self.Bind(wx.EVT_SIZE, self.OnSize) self.InsertColumn(0, '') def OnSize(self, event): size = self.parent.GetSize() self.SetColumnWidth(0, size.x-5) event.Skip() def LoadData(self, index): self.DeleteAllItems() for i in range(3): self.InsertStringItem(0, articles[index][i]) class Reader(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) hbox = wx.BoxSizer(wx.HORIZONTAL) splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE|wx.SP_NOBORDER) vbox1 = wx.BoxSizer(wx.VERTICAL) panel1 = wx.Panel(splitter, -1) panel11 = wx.Panel(panel1, -1, size=(-1, 40)) panel11.SetBackgroundColour('#53728c') st1 = wx.StaticText(panel11, -1, 'Feeds', (5, 5)) st1.SetForegroundColour('WHITE') panel12 = wx.Panel(panel1, -1, style=wx.BORDER_SUNKEN) vbox = wx.BoxSizer(wx.VERTICAL) list1 = ListCtrlLeft(panel12, -1) vbox.Add(list1, 1, wx.EXPAND) panel12.SetSizer(vbox) panel12.SetBackgroundColour('WHITE') vbox1.Add(panel11, 0, wx.EXPAND) vbox1.Add(panel12, 1, wx.EXPAND) panel1.SetSizer(vbox1) vbox2 = wx.BoxSizer(wx.VERTICAL) panel2 = wx.Panel(splitter, -1) panel21 = wx.Panel(panel2, -1, size=(-1, 40), style=wx.NO_BORDER) st2 = wx.StaticText(panel21, -1, 'Articles', (5, 5)) st2.SetForegroundColour('WHITE') panel21.SetBackgroundColour('#53728c') panel22 = wx.Panel(panel2, -1, style=wx.BORDER_RAISED) vbox3 = wx.BoxSizer(wx.VERTICAL) list2 = ListCtrlRight(panel22, -1) list2.SetName('ListControlOnRight') vbox3.Add(list2, 1, wx.EXPAND) panel22.SetSizer(vbox3) panel22.SetBackgroundColour('WHITE') vbox2.Add(panel21, 0, wx.EXPAND) vbox2.Add(panel22, 1, wx.EXPAND) panel2.SetSizer(vbox2) toolbar = self.CreateToolBar() toolbar.AddLabelTool(1, 'Exit', wx.Bitmap('icons/stock_exit.png')) toolbar.Realize() self.Bind(wx.EVT_TOOL, self.ExitApp, id=1) hbox.Add(splitter, 1, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) self.SetSizer(hbox) self.CreateStatusBar() splitter.SplitVertically(panel1, panel2) self.Centre() self.Show(True) def ExitApp(self, event): self.Close() app = wx.App() Reader(None, -1, 'Reader') app.MainLoop() </code> The previous example showed a //wx.ListCtrl// in a report view. With no headers. We shall create our own headers. We show two //wx.ListCtrl// widgets. One is on the right side and the other one on the left side of the application. <code python> splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE|wx.SP_NOBORDER) ... splitter.SplitVertically(panel1, panel2) </code> The splitter will split the main window into two vertical parts. The splitter will show two panels. Those two panels will have another two panels. They create //Feeds// and //Articles// headers. The rest of the space will be occupied by our two //wx.ListCtrl// widgets. <code python> list2 = ListCtrlRight(panel22, -1) list2.SetName('ListControlOnRight') </code> When we create //ListCtrlRight// object, we give it a name //ListControlOnRight//. This is because we need //ListCtrlRight// and //ListCtrlLeft// two widgets to communicate. <code python> def OnSelect(self, event): window = self.parent.GetGrandParent().FindWindowByName('ListControlOnRight') index = event.GetIndex() window.LoadData(index) </code> This code is in //ListCtrlLeft// class. Here we locate the //ListCtrlRight// object and call it's //LoadData()// method. <code python> def LoadData(self, index): self.DeleteAllItems() for i in range(3): self.InsertStringItem(0, articles[index][i]) </code> The //LoadData()// method first clears all items. Then it inserts the article names from the globally defined articles list. The index has been passed. <code python> def OnSize(self, event): size = self.parent.GetSize() self.SetColumnWidth(0, size.x-5) event.Skip() </code> Both //wx.ListCtrl//s have only one column. Here we ensure that the size of the column equals to size of the parent panel. The application would not look nice otherwise. Why do we extract 5px? This number is a kind of magic number. If we extract exactly 5px, the horizotal scrollbars do not appear. On other platforms, the number might be different. {{ http://zetcode.com/wxpython/images/reader.png }} **CheckListCtrl** It is quite common to see applications having check boxes inside list controls. For example a packaging application like Synaptic or KYUM. From the programmer's point of view, those checkboxes are simple images. There are two states. Checked and unchecked. For both situations we have a unique image. We do not have to implement the functionality. It has been already coded. The code is in //CheckListCtrlMixin//. <code python> #!/usr/bin/python # repository.py import wx import sys from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin packages = [('abiword', '5.8M', 'base'), ('adie', '145k', 'base'), ('airsnort', '71k', 'base'), ('ara', '717k', 'base'), ('arc', '139k', 'base'), ('asc', '5.8M', 'base'), ('ascii', '74k', 'base'), ('ash', '74k', 'base')] class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER) CheckListCtrlMixin.__init__(self) ListCtrlAutoWidthMixin.__init__(self) class Repository(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(450, 400)) panel = wx.Panel(self, -1) vbox = wx.BoxSizer(wx.VERTICAL) hbox = wx.BoxSizer(wx.HORIZONTAL) leftPanel = wx.Panel(panel, -1) rightPanel = wx.Panel(panel, -1) self.log = wx.TextCtrl(rightPanel, -1, style=wx.TE_MULTILINE) self.list = CheckListCtrl(rightPanel) self.list.InsertColumn(0, 'Package', width=140) self.list.InsertColumn(1, 'Size') self.list.InsertColumn(2, 'Repository') for i in packages: index = self.list.InsertStringItem(sys.maxint, i[0]) self.list.SetStringItem(index, 1, i[1]) self.list.SetStringItem(index, 2, i[2]) vbox2 = wx.BoxSizer(wx.VERTICAL) sel = wx.Button(leftPanel, -1, 'Select All', size=(100, -1)) des = wx.Button(leftPanel, -1, 'Deselect All', size=(100, -1)) apply = wx.Button(leftPanel, -1, 'Apply', size=(100, -1)) self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=sel.GetId()) self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=des.GetId()) self.Bind(wx.EVT_BUTTON, self.OnApply, id=apply.GetId()) vbox2.Add(sel, 0, wx.TOP, 5) vbox2.Add(des) vbox2.Add(apply) leftPanel.SetSizer(vbox2) vbox.Add(self.list, 1, wx.EXPAND | wx.TOP, 3) vbox.Add((-1, 10)) vbox.Add(self.log, 0.5, wx.EXPAND) vbox.Add((-1, 10)) rightPanel.SetSizer(vbox) hbox.Add(leftPanel, 0, wx.EXPAND | wx.RIGHT, 5) hbox.Add(rightPanel, 1, wx.EXPAND) hbox.Add((3, -1)) panel.SetSizer(hbox) self.Centre() self.Show(True) def OnSelectAll(self, event): num = self.list.GetItemCount() for i in range(num): self.list.CheckItem(i) def OnDeselectAll(self, event): num = self.list.GetItemCount() for i in range(num): self.list.CheckItem(i, False) def OnApply(self, event): num = self.list.GetItemCount() for i in range(num): if i == 0: self.log.Clear() if self.list.IsChecked(i): self.log.AppendText(self.list.GetItemText(i) + '\n') app = wx.App() Repository(None, -1, 'Repository') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/repository.png }} <code python> class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER) CheckListCtrlMixin.__init__(self) ListCtrlAutoWidthMixin.__init__(self) </code> wxPython enables multiple inheritance. Here we inherit from three different classes. <code python> def OnSelectAll(self, event): num = self.list.GetItemCount() for i in range(num): self.list.CheckItem(i) </code> Here we can see multiple inheritance in action. We can call two methods from two different classes on our //self.list// object. The //GetItemCount()// method is located in //CheckListCtrl// class and the //CheckItem()// method is in // CheckListCtrlMixin// class. </html> ===== Drag and drop in wxPython ===== Wikipedia: In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects. Drag and drop functionality is one of the most visible aspects of the graphical user interface. Drag and drop operation enables you to do complex things intuitively. In drag and drop we basically drag some data from a data source to a data target. So we must have: * Some data * A data source * A data target In wxPython we have two predefined data targets. **wx.TextDropTarget** and **wx.FileDropTarget**. <code python> #!/usr/bin/python # dragdrop.py import os import wx class MyTextDropTarget(wx.TextDropTarget): def __init__(self, object): wx.TextDropTarget.__init__(self) self.object = object def OnDropText(self, x, y, data): self.object.InsertStringItem(0, data) class DragDrop(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(650, 500)) splitter1 = wx.SplitterWindow(self, -1, style=wx.SP_3D) splitter2 = wx.SplitterWindow(splitter1, -1, style=wx.SP_3D) self.dir = wx.GenericDirCtrl(splitter1, -1, dir='/home/', style=wx.DIRCTRL_DIR_ONLY) self.lc1 = wx.ListCtrl(splitter2, -1, style=wx.LC_LIST) self.lc2 = wx.ListCtrl(splitter2, -1, style=wx.LC_LIST) dt = MyTextDropTarget(self.lc2) self.lc2.SetDropTarget(dt) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId()) tree = self.dir.GetTreeCtrl() splitter2.SplitHorizontally(self.lc1, self.lc2) splitter1.SplitVertically(self.dir, splitter2) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelect, id=tree.GetId()) self.OnSelect(0) self.Centre() self.Show(True) def OnSelect(self, event): list = os.listdir(self.dir.GetPath()) self.lc1.ClearAll() self.lc2.ClearAll() for i in range(len(list)): if list[i][0] != '.': self.lc1.InsertStringItem(0, list[i]) def OnDragInit(self, event): text = self.lc1.GetItemText(event.GetIndex()) tdo = wx.TextDataObject(text) tds = wx.DropSource(self.lc1) tds.SetData(tdo) tds.DoDragDrop(True) app = wx.App() DragDrop(None, -1, 'dragdrop.py') app.MainLoop() </code> <code python> #!/usr/bin/python # filedrop.py import wx class FileDrop(wx.FileDropTarget): def __init__(self, window): wx.FileDropTarget.__init__(self) self.window = window def OnDropFiles(self, x, y, filenames): for name in filenames: try: file = open(name, 'r') text = file.read() self.window.WriteText(text) file.close() except IOError, error: dlg = wx.MessageDialog(None, 'Error opening file\n' + str(error)) dlg.ShowModal() except UnicodeDecodeError, error: dlg = wx.MessageDialog(None, 'Cannot open non ascii files\n' + str(error)) dlg.ShowModal() class DropFile(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size = (450, 400)) self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE) dt = FileDrop(self.text) self.text.SetDropTarget(dt) self.Centre() self.Show(True) app = wx.App() DropFile(None, -1, 'filedrop.py') app.MainLoop() </code> </html> ===== Internationalization ===== In computing, Internationalization and localization are means of adapting computer software for non-native environments, especially other nations and cultures. Internationalization is the process of ensuring that an application is capable of adapting to local requirements, for instance ensuring that the local writing system can be displayed. Localization is the process of adapting the software to be as familiar as possible to a specific locale, by displaying text in the local language and using local conventions for the display of such things as units of measurement. (wikipedia) ==== Unicode ==== There are two builds of wxPython. The ansi build and the unicode build. If we want to create and use wxPython applications in languages other than english, we must have the unicode build. **Unicode** is an industry standard allowing computers to consistently represent and manipulate text expressed in any of the world's writing systems. It is a character enconding standart which uses 16 bits for storing characters. The traditional **ASCII** enconding uses only 8 bits. First, we need to get the unicode enconding of Лев Николaевич Толстoй Анна Каренина words. <code python> >>> unicode(u'Лев Николaевич Толстoй Анна Каренина') u'\u041b\u0435\u0432 \u041d\u0438\u043aa\u0430\u0301\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441o\u0439 \u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430' </code> We launch the python terminal and use the //unicode()// function call. Notice, that in the example, we use additional \n\ characters to divide the words into two lines. <code python> #!/usr/bin/python import wx text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\ \u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439 \n\ \u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430' class Unicode(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.DrawText(text, 50, 50) app = wx.App() Unicode(None, -1, 'Unicode') app.MainLoop() </code> In the example, we draw Anna Karenina in russian azbuka on the window. {{ http://zetcode.com/wxpython/images/unicode.jpg |Unicode}} ==== Locale ==== A locale is an object that defines user's language, country, number format, letter format, currency format etc. A local variant has the following format. <code python> [language[_territory][.codeset][@modifier]] </code> For example, **de_AT.utf8** is a german local used in Austria, with UTF8 codeset. <code python> #!/usr/bin/python # locale.py import wx import time import locale class Locale(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 420)) panel = wx.Panel(self, -1) tm = time.localtime() font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) us = wx.StaticText(self, -1, 'United States', (25, 20)) us.SetFont(font) wx.StaticLine(self, -1, (25, 50), (200 ,1)) locale.setlocale(locale.LC_ALL, '') date = time.strftime('%x', tm) time_ = time.strftime('%X', tm) curr = locale.currency(100000) wx.StaticText(self, -1, 'date: ', (25, 70)) wx.StaticText(self, -1, 'time: ', (25, 90)) wx.StaticText(self, -1, 'currency: ', (25, 110)) wx.StaticText(self, -1, str(date), (125, 70)) wx.StaticText(self, -1, str(time_), (125, 90)) wx.StaticText(self, -1, str(curr), (125, 110)) de = wx.StaticText(self, -1, 'Germany', (25, 150)) de.SetFont(font) wx.StaticLine(self, -1, (25, 180), (200,1)) locale.setlocale(locale.LC_ALL, ('de_DE', 'UTF8')) date = time.strftime('%x', tm) time_ = time.strftime('%X', tm) curr = locale.currency(100000) wx.StaticText(self, -1, 'date: ', (25, 200)) wx.StaticText(self, -1, 'time: ', (25, 220)) wx.StaticText(self, -1, 'currency: ', (25, 240)) wx.StaticText(self, -1, date, (125, 200)) wx.StaticText(self, -1, time_, (125, 220)) wx.StaticText(self, -1, curr, (125, 240)) de = wx.StaticText(self, -1, 'Slovakia', (25, 280)) de.SetFont(font) wx.StaticLine(self, -1, (25, 310), (200,1)) locale.setlocale(locale.LC_ALL, ('sk_SK', 'UTF8')) date = time.strftime('%x', tm) time_ = time.strftime('%X', tm) curr = locale.currency(100000) wx.StaticText(self, -1, 'date: ', (25, 330)) wx.StaticText(self, -1, 'time: ', (25, 350)) wx.StaticText(self, -1, 'currency: ', (25, 370)) wx.StaticText(self, -1, str(date), (125, 330)) wx.StaticText(self, -1, str(time_), (125, 350)) wx.StaticText(self, -1, str(curr), (125, 370)) self.Centre() self.Show(True) app = wx.App() Locale(None, -1, 'Locale') app.MainLoop() </code> We use the standart built-in module **locale** to work with localized settings. In our example, we will show various formats of date, time and currency in the USA, Germany and Slovakia. <code python> locale.setlocale(locale.LC_ALL, ('de_DE', 'UTF8')) </code> Here we set a locale object for Germany. **LC_ALL** is a combination of all various local settings, e.g. LC_TIME, LC_MONETARY, LC_NUMERIC. <code python> date = time.strftime('%x', tm) time_ = time.strftime('%X', tm) curr = locale.currency(100000) </code> These function calls reflect the current locale object. {{ http://zetcode.com/wxpython/images/locale.jpg |Locale}} ==== World Time ==== At a specific moment, we have different time in countries across the world. Our globe is divided into time zones. It is not uncommon for programmers to deal with such tasks. wxPython comes with a //wx.DateTime// object. According to the documentation, wxDateTime class represents an absolute moment in the time. <code python> #!/usr/bin/python import wx import time class WorldTime(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(270, 280)) self.panel = wx.Panel(self, -1) self.panel.SetBackgroundColour('#000000') font = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, 'Georgia') self.dt = wx.DateTime() self.tokyo = wx.StaticText(self.panel, -1, self.dt.FormatTime() , (20, 20)) self.tokyo.SetForegroundColour('#23f002') self.tokyo.SetFont(font) self.moscow = wx.StaticText(self.panel, -1, self.dt.FormatTime() , (20, 70)) self.moscow.SetForegroundColour('#23f002') self.moscow.SetFont(font) self.budapest = wx.StaticText(self.panel, -1, self.dt.FormatTime() , (20, 120)) self.budapest.SetForegroundColour('#23f002') self.budapest.SetFont(font) self.london = wx.StaticText(self.panel, -1, self.dt.FormatTime() , (20, 170)) self.london.SetForegroundColour('#23f002') self.london.SetFont(font) self.newyork = wx.StaticText(self.panel, -1, self.dt.FormatTime() , (20, 220)) self.newyork.SetForegroundColour('#23f002') self.newyork.SetFont(font) self.OnTimer(None) self.timer = wx.Timer(self) self.timer.Start(1000) self.Bind(wx.EVT_TIMER, self.OnTimer) self.Centre() self.Show(True) def OnTimer(self, evt): now = self.dt.Now() self.tokyo.SetLabel('Tokyo: ' + str(now.Format(('%a %T'), wx.DateTime.GMT_9))) self.moscow.SetLabel('Moscow: ' + str(now.Format(('%a %T'), wx.DateTime.MSD))) self.budapest.SetLabel('Budapest: ' + str(now.Format(('%a %T'), wx.DateTime.CEST))) self.london.SetLabel('London: ' + str(now.Format(('%a %T'), wx.DateTime.WEST))) self.newyork.SetLabel('New York: ' + str(now.Format(('%a %T'), wx.DateTime.EDT))) app = wx.App() WorldTime(None, -1, 'World Time') app.MainLoop() </code> In the code example, we show current time in Tokyo, Moscow, Budapest, London and New York. <code python> self.dt = wx.DateTime() </code> Here we create a //wx.DateTime// object. <code python> now = self.dt.Now() </code> We get the "absolute moment" in time. <code python> self.tokyo.SetLabel('Tokyo: ' + str(now.Format(('%a %T'), wx.DateTime.GMT_9))) </code> This code line sets the time to the appropriate format. The %a conversion specifier is an abbreviated weekday name according to the current locale. The %T is the time of day using decimal numbers using the format %H:%M:%S. The second parameter of the Format() method specifies the time zone. GMT_9 is used for Japan, EDT (Eastern Daylight Saving Time) is used in New York etc. The code example was checked with the [[http://www.timeanddate.com/worldclock/|timeanddate.com]] website. {{ http://zetcode.com/wxpython/images/worldtime.jpg |World Time}} ==== Sorting ==== Locale settings also affect the way, how strings are being sorted. For example hungarian language has some characters that are missing in Slovak language or English language. Some languages have accents, others don't. <code python> #!/usr/bin/python # collate.py import wx import locale ID_SORT = 1 words = [u'Sund', u'S\xe4bel', u'S\xfcnde', u'Schl\xe4fe', u'Sabotage'] class Collate(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(300, 220)) panel = wx.Panel(self, -1) hbox = wx.BoxSizer(wx.HORIZONTAL) self.listbox = wx.ListBox(panel, -1) for i in words: self.listbox.Append(i) hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20) btnPanel = wx.Panel(panel, -1) vbox = wx.BoxSizer(wx.VERTICAL) new = wx.Button(btnPanel, ID_SORT, 'Sort', size=(90, 30)) self.Bind(wx.EVT_BUTTON, self.OnSort, id=ID_SORT) vbox.Add((-1, 20)) vbox.Add(new) btnPanel.SetSizer(vbox) hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20) panel.SetSizer(hbox) locale.setlocale(locale.LC_COLLATE, ('de_DE', 'UTF8')) self.Centre() self.Show(True) def OnSort(self, event): self.listbox.Clear() words.sort( lambda a,b: locale.strcoll(a, b) ) for i in words: self.listbox.Append(i) app = wx.App() Collate(None, -1, 'Collate') app.MainLoop() </code> In our example, we took 5 germam words from the dictionary. The default //sort()// function sorts these words this way: Sabotage, Schläfe, Sund, Säbel, Sünde. This is incorrect, because in german alphabet ä character precedes a character. To get the corect sorting, we must use locale functions. <code python> locale.setlocale(locale.LC_COLLATE, ('de_DE', 'UTF8')) </code> Here we set the german collate. We could use the //LC_ALL// option or the more specific //LC_COLLATE// one. <code python> words.sort( lambda a,b: locale.strcoll(a, b) ) </code> The trick is to use a new compare function within the //sort()// function. We define an anonymous lambda function. The //strcoll()// function compares two strings and returns -1, 0, 1 exactly like the default one, but it takes the locale settings (the collate) into account. This way we have the correct sorting of words. {{ http://zetcode.com/wxpython/images/collate.jpg |Collate}} ==== Simple Translation ==== In the following example, we will demonstrate a very basic translation. A programmer has two options. Either to use the GNU gettext or to use the wxPython catalogs. Both systems are compatible. wxPython has a class //wx.Locale//, which is a base for using message catalogs. Each translation has one catalog. Say, we want to translate a string into german language. First, we must ensure, that we have language support for german language. <code python> $ locale -a C de_AT.utf8 de_BE.utf8 de_CH.utf8 de_DE.utf8 de_LU.utf8 en_AU.utf8 en_BW.utf8 en_CA.utf8 en_DK.utf8 en_GB.utf8 en_HK.utf8 en_IE.utf8 en_IN en_NZ.utf8 en_PH.utf8 en_SG.utf8 en_US.utf8 en_ZA.utf8 en_ZW.utf8 POSIX sk_SK.utf8 </code> To check what languages are supported, we use the //locale// command. On my system, I have english, german and slovak language support. English language and german language have different dialects, that's why we have so many options. Notice the **utf8** string. This means, that the system uses utf8 encoding for working with strings. Next we write our code example. We put the string that are to be translated into this _(), or we can use the //wx.GetTranslation()// call. <code python> #!/usr/bin/python import wx class Translation(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(220, 100)) panel = wx.Panel(self, -1) mylocale = wx.Locale() mylocale.AddCatalogLookupPathPrefix('.') mylocale.AddCatalog('simple_de') _ = wx.GetTranslation wx.StaticText(panel, -1, _("hello"), (10, 10)) #wx.StaticText(panel, -1, wx.GetTranslation('hello'), (10, 10)) self.Centre() self.Show(True) app = wx.App() Translation(None, -1, 'Translation') app.MainLoop() </code> Next we create a so called PO file. It is a simple text file, which is translators use to translate the strings. <code python> pygettext -o simple_de.po simple.py </code> To create a po file, we use the **pygettext** command. To fully understand the format of the po file, consult the gnu gettext [[http://www.gnu.org/software/gettext/manual/gettext.html|manual]]. <code python> "Content-Type: text/plain; charset=utf-8\n" </code> We edit the simple_de.po file. We must specify the charset. In our case it is utf-8. <code python> #: simple.py:17 msgid "hello" msgstr "Grüß Gott" </code> Here we provide a translation for the hello string. The last thing we do is to create a binary message catalog. <code python> msgfmt --output-file simple_de.mo simple_de.po </code> To produce a mo file, we call the **msgfmt** command. {{ http://zetcode.com/wxpython/images/translation.jpg |Simple translation}} ===== Working with databases ===== Database driven applications account for a large part of all applications ever developed. And they will definitely in the future as well. Most of them are business applications. Companies work with large amount of data and they naturally need software for that. Well, you know, we live in a era called information revolution after all. Some GUI toolkits are geared towards developing business applications. For example the WinForms or the Swing. They provide widgets that are adapted to the business application developing. A data grid widget is a good example. Swing toolkit has prorities like security or robustness. But definitelly not the look and feel. **Database** is a structured collection of data that is stored in a computer. A computer program, that manages and queries a database is calles a **Database Management System (DBMS)**. Some thirty years ago, DBMS were available only in the research laboratories of giant companies like IBM. Later on, they began to spread. But they were very expensive. These days, we can found DBMS everywhere. On the web, on our personal computers, in various mobile devices or portables. We can have many different databeses for little or no money that would cost thousands of dollars in the past. We live in interesting times. There are various database models. The most significant database model is the **relational database model (RDBMS)**. The data is divided into tables. Among these tables we define relations. We all have heard about various database management systems. There are several well known commercial DBMS as well as open source ones. ^ Commercial RDBMS | Oracle \\ Sybase \\ MS SQL \\ Access | ^ Opensource RDBMS | MySQL \\ PostgreSQL \\ Firebird \\ SQLite | Python programming language has modules for all above RDBMS. ==== SQLite ==== Starting from Python 2.5.x series, an SQLite library is included in the python language. SQLite is a small embeddable library. This means that programmers can integrate the libraty inside their applications. No server is needed to work with SQLite. Therefore SQLite is also called a zero-configuration SQL database engine. SQLite has the following features. * works with transactions * no administration needed * small code footprint, less than 250 KB * simple to use and fast * single file database structure *supports databases up to 2 tebibytes (2<sup>41</sup> bytes) in size</i> SQLite supports these data types: * TEXT * INTEGER * FLOAT * BLOB *NULL</i> Before we start working with SQLite, we define some important terms. A database **query** is a search for information from a database. A query is written in SQL language. **Structured Query Language** (SQL) is a computer language used to create, retrieve, update and delete data from the database. It was developed by the IBM corporation. SQL language has three subsets. * DML * DDL * DCL The DML (Data Manipulation Language) is used to add, update and delete data. SQLite understands insert, update and delete sql commands. The DDL (Data Definition Language) is used to define new tables and records. SQLite has create, drop, alter sql commands from this group. The DCL (Data Control Language) is used to set privileges for database users. SQLite does not have this subset. A **cursor** is a database object used to traverse the results of a SQL query. A **transaction** is a unit of operation with a database management system. It can contain one or more queries. Transactions are used to ensure the integrity of data in a database. If everything is ok, transactions are commited. If one or more queries fails, transactions are rolled back. Databases that support transactions are called transactional databases. An SQLite database is a transactional database. An SQL **result set** is a set of rows and metadata about the query from a database. It is a set of records that results from running a query. A single unit of structured data within a database table is called a **record** or a **row**. === sqlite3 === SQLite library includes a small command line utility called sqlite3. It is used to manually enter and execute SQL commands against a SQLite database. To launch this utility, we type sqlite3 into the shell. The command is to be followed by a database name. If the database does not exist, a new one is created. We work with sqlite3 with a definite set of dot commands. To show all available commands, we type //.help//. Some of the commands are shown in the following table. ^ Command ^ Description ^ |.databases| show a database name | |.dump table| dump a table into an SQL text format | |.exit| exit the sqlite3 program | |.headers ON/OFF| show or hide column headers | |.help| show help | |.mode mode table| change mode for a table | |.quit| same as .exit | |.read filename| execute SQL commands in a filename | |.show| show sqlite3 settings | |.tables pattern| list tables that match pattern | |.width num num ...| set width for columns | First, we create a new database called people. <code python> $ sqlite3 people SQLite version 3.3.13 Enter ".help" for instructions sqlite> sqlite> .databases seq name file --- --------------- ---------------------------------------------------------- 0 main /home/vronskij/tmp/people sqlite> .exit $ </code> All commands of //sqlite3// start with the dot "." character. To show all available commands, we simply type //.help//. The //.databases// command shows our current database. The //.exit// command quits the sqlite3 utility and returns to the shell. Next we create a table. <code python> sqlite> .tables sqlite> create table neighbours(name text, age numeric, remark text); sqlite> .tables neighbours </code> The //.tables// command shows all available tables in the database. We create a table called neighbours. Our table will have three columns. We will use text and numeric data types. Notice that each SQL command is followed by a semicolon ";". Now it is time to insert some real data. <code python> sqlite> insert into neighbours values('sandy', 7, 'stubborn'); sqlite> insert into neighbours values('jane', 18, 'beautiful'); sqlite> insert into neighbours values('mark', 28, 'lazy'); sqlite> insert into neighbours values('steven', 34, 'friendly'); sqlite> insert into neighbours values('alice', 17, 'slick'); sqlite> insert into neighbours values('tom', 25, 'clever'); sqlite> insert into neighbours values('jack', 89, 'wise'); sqlite> insert into neighbours values('lucy', 18, 'cute'); </code> The SQL //select// command is probably the most widely used DML (data manipulation language) command. <code python> sqlite> select * from neighbours; sandy|7|stubborn jane|18|beautiful mark|28|lazy steven|34|friendly alice|17|slick tom|25|clever jack|89|wise lucy|18|cute </code> The sqlite3 has several modes to display data. Namely: ^ Mode ^ Description ^ |csv| comma separated values | |column| left aligned columns | |html| html table code | |insert| SQL insert statements for table | |line| one value per line | |list| values delimited by .separator string | |tabs| tab separated values | The default mode is the list mode. We can see the current settings if we type the .show command. <code python> sqlite> .show echo: off explain: off headers: off mode: list nullvalue: "" output: stdout separator: "|" width: </code> I prefer the column mode. In the next step we change the default settings a bit. <code python> sqlite> .mode column sqlite> .headers on sqlite> .width 10 4 15 sqlite> select * from neighbours; name age remark ---------- ---- --------------- sandy 7 stubborn jane 18 beautiful mark 28 lazy steven 34 friendly alice 17 slick tom 25 clever jack 89 wise lucy 18 cute </code> We change the mode with the //.mode// command to the column mode. We set headers on with the //.headers// command. Finally we change the width of each column with the //.width// command. The default value is ten characters. Backing up the data is the most important thing. sqlite3 has a simple solution. We utilize command //.dump//. <code python> sqlite> .tables neighbours sqlite> .dump neighbours BEGIN TRANSACTION; CREATE TABLE neighbours(name text, age numeric, remark text); INSERT INTO "neighbours" VALUES('sandy',7,'stubborn'); INSERT INTO "neighbours" VALUES('jane',18,'beautiful'); INSERT INTO "neighbours" VALUES('mark',28,'lazy'); INSERT INTO "neighbours" VALUES('steven',34,'friendly'); INSERT INTO "neighbours" VALUES('alice',17,'slick'); INSERT INTO "neighbours" VALUES('tom',25,'clever'); INSERT INTO "neighbours" VALUES('jack',89,'wise'); INSERT INTO "neighbours" VALUES('lucy',18,'cute'); COMMIT; </code> The //.dump// command transforms the table into a set of SQL text format. These SQL commands will recreate the table into the original state. We copy and paste these SQL commnads into a neighbours.sql text file. In the next steps we drop a table and recreate it from our file. <code python> sqlite> drop table neighbours; sqlite> .tables sqlite> .read ../neighbours.sql sqlite> .tables neighbours sqlite> select * from neighbours; name age remark ---------- ---------- ---------- sandy 7 stubborn jane 18 beautiful mark 28 lazy steven 34 friendly alice 17 slick tom 25 clever jack 89 wise lucy 18 cute </code> We drop the neighbours table with the drop table SQL command. The command //.tables// shows no table. Then we type sqlite //.read// command to execute all SQL commands in the specified file. Finally, we verify our data. === SQLite python API === pysqlite is a python interface to the SQLite library. From python2.5x series, it is included in the python language. The pysqlite module is included under the package name sqlite3. <code python> import sqlite3 as lite </code> Simple steps * create connection object * create cursor object * execute query * fetch data (optional) * close cursor and connection objects To create a connection, we call the //connect()// module method. <code python> import sqlite3 as lite con = lite.connect('databasename') con = lite.connect(':memory:') </code> There are two ways for creating a connection object. We can create a connection to a database on the filesystem. We simply specify the path to the filename. We can also create a database in memory. This is done with a special string ':memory:'. We launch a python interpreter. We will test our examples there. <code python> $ python Python 2.5.1c1 (release25-maint, Apr 6 2007, 22:02:36) [GCC 4.1.2 (Ubuntu 4.1.2-0ubuntu4)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> </code> <code python> >>> import sqlite3 as lite >>> con = lite.connect('people') >>> cur = con.cursor() >>> cur.execute('select name from neighbours') >>> print cur.fetchall() [(u'sandy',), (u'jane',), (u'mark',), (u'steven',), (u'alice',), (u'tom',), (u'jack',), (u'lucy',)] >>> cur.close() >>> con.close() </code> First we import the sqlite3 module. Then we connect to our people database. The database file is in our current directory. To create a cursor object, we call the //cursor()// method of the connection object. After that we call two cursor object methods. The //execute()// method executes SQL commands. The //fetchall()// method retrieves all data that we have selected. The kosher way to end our work is to close the cursor and the connection object. === Commiting changes === SQLite library works with transactions. It is important to understand how it works. According to the documentation, for every DML statement, SQLite opens up a transaction. We must commit our changes to apply them. For every DCL statement, SQLite library commits automatically the changes. We will demonstrate this in short examples. <code python> >>> cur.execute("update neighbours set age=29 where name='lucy'") >>> cur.execute("select age from neighbours where name='lucy'") >>> print cur.fetchone() (29,) >>> cur.close() >>> con.close() >>> (CTRL + D) $ sqlite3 people sqlite> select age from neighbours where name='lucy'; 18 </code> What went wrong? We did not commit our changes. When we executed the select statement using the python sqlite api, we received results within a transaction context. The changes were not really written to the database. When we checked the data in the sqlite3 utility, we got age 18. The data was not changed. <code python> >>> cur.execute("update neighbours set age=29 where name='lucy'") >>> con.commit() >>> cur.close() >>> con.close() >>> (CTRL + D) $ sqlite3 people sqlite> select age from neighbours where name='lucy'; 29 </code> After committing our changes with the //commit()// method of the connection object, the data changes are really written to the database. In the next example we demonstrate that the DCL statements are committed automatically. We will use create table command, which is a part of the DCL language. <code python> >>> cur.execute('create table relatives(name text, age numeric)') >>> cur.close() >>> con.close() >>> (CTRL + D) $ sqlite3 people sqlite> .tables neighbours relatives </code> There is one more thing to mention. We can create a connection, which will automatically commit all our changes. This is done, when we set the //isolation_level// parameter to None. <code python> >>> import sqlite3 as lite >>> con = lite.connect('people', isolation_level=None) >>> cur = con.cursor() >>> cur.execute("insert into neighbours values ('rebecca', 16, 'shy')") >>> cur.close() >>> con.close() >>> (CTRL + D) $ sqlite3 people sqlite> select * from neighbours where name='rebecca'; rebecca|16|shy sqlite> </code> === Autoincrement === Autoincremental primary key is a handy feature. We insert new rows and the key is incremented automatically by one. The implementation of the autoincrement feature may differ among RDMSs. In the next example we will show how it is done in SQLite database. <code python> sqlite> create table books(id integer primary key autoincrement not null, name text, author text); sqlite> insert into books (name, author) values ('anna karenina', 'leo tolstoy'); sqlite> insert into books (name, author) values ('father goriot', 'honore de balzac'); sqlite> select * from books; 1|anna karenina|leo tolstoy 2|father goriot|honore de balzac sqlite> </code> The keyword //autoincrement// is used to create autoincremental primary key in SQLite. === Security considerations === It is possible but insecure to pass parameters this way. <code python> bookname = 'atlante illustrato di filosofia' bookauthor = 'ubaldo nicola' cur.execute("insert into books(name, author) values ('%s', '%s')" % (bookname, bookauthor)) </code> It is vulnerable to attacks. These attacks are called SQL injections. Don't do this. <code python> >>> import sqlite3 as lite >>> print lite.paramstyle qmark </code> The python Database API specification lists these possible parameter style passings: * qmark * numeric * named * format * pyformat Python SQLite API uses the qmark (question mark) quoting. The previous example rewritten in qmark style: <code python> bookname = 'atlante illustrato di filosofia' bookauthor = 'ubaldo nicola' cur.execute('insert into books(name, author) values (?, ?)', (bookname, bookauthor)) </code> TODO blob === Putting it together === So far we have been looking at the SQLite3 library, databases and SQL language. Now it is time to put it all together with wxPython in a simple functional script. The next simple script will do only one specific thing. Insert data into a table. We will use our people database, neigbours table. <code python> #!/usr/bin/python # insertdata.py import wx import sqlite3 as lite class InsertData(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(280, 200)) panel = wx.Panel(self, -1) gs = wx.FlexGridSizer(3, 2, 9, 9) vbox = wx.BoxSizer(wx.VERTICAL) hbox = wx.BoxSizer(wx.HORIZONTAL) name = wx.StaticText(panel, -1, 'Name') remark = wx.StaticText(panel, -1, 'Remark') age = wx.StaticText(panel, -1, 'Age') self.sp = wx.SpinCtrl(panel, -1, '', size=(60, -1), min=1, max=125) self.tc1 = wx.TextCtrl(panel, -1, size=(150, -1)) self.tc2 = wx.TextCtrl(panel, -1, size=(150, -1)) gs.AddMany([(name), (self.tc1, 1, wx.LEFT, 10), (remark), (self.tc2, 1, wx.LEFT, 10), (age), (self.sp, 0, wx.LEFT, 10)]) vbox.Add(gs, 0, wx.ALL, 10) vbox.Add((-1, 30)) insert = wx.Button(panel, -1, 'Insert', size=(-1, 30)) cancel = wx.Button(panel, -1, 'Cancel', size=(-1, 30)) hbox.Add(insert) hbox.Add(cancel, 0, wx.LEFT, 5) vbox.Add(hbox, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10) self.Bind(wx.EVT_BUTTON, self.OnInsert, id=insert.GetId()) self.Bind(wx.EVT_BUTTON, self.OnCancel, id=cancel.GetId()) panel.SetSizer(vbox) self.Centre() self.Show(True) def OnInsert(self, event): try: con = lite.connect('people') cur = con.cursor() name = self.tc1.GetValue() age = self.sp.GetValue() remark = self.tc2.GetValue() cur.execute('insert into neighbours values(?, ?, ?)', (name, age, remark)) con.commit() cur.close() con.close() except lite.Error, error: dlg = wx.MessageDialog(self, str(error), 'Error occured') dlg.ShowModal() def OnCancel(self, event): self.Close() app = wx.App() InsertData(None, -1, 'Insert Dialog') app.MainLoop() </code> <code python> gs = wx.FlexGridSizer(3, 2, 9, 9) </code> In our Dialog box we use items of different size. That's why we have chosen the //wx.FlexGridSizer.// Items in //wx.GridSizer// have always the same size. <code python> name = self.tc1.GetValue() age = self.sp.GetValue() remark = self.tc2.GetValue() cur.execute('insert into neighbours values(?, ?, ?)', (name, age, remark)) </code> This is the crutial part of the code. In the first three lines, we get the values that the user has inserted. These values are inserted into the database with the appropriate the SQL code. <code python> except lite.Error, error: dlg = wx.MessageDialog(self, str(error), 'Error occured') dlg.ShowModal() </code> We have placed our database related code between the try - catch clause. This is because working with data and databases is prone to errors. The //Error// exception is a base class for all other exceptions implemented in SQLite library. {{ http://zetcode.com/wxpython/images/insertdata.png |InsertData screenshot}} TODO: Supported Errors, Warning ===== Application skeletons in wxPython ===== In this section, we will create some application skeletons. Our scripts will work out the interface but will not implement the functionality. The goal is to show, how several well known GUI interfaces could be done in wxPython. Most manuals, tutorials and books show only the basic usage of a widget. When I was a beginner, I always wondered how this or this could be done. And I think, many newbies think the same. ==== File Manager ==== File Hunter is a skeleton of a file manager. It copies the lookout of the Krusader, the best file manager available on Unix systems. If you double click on the splitter widget, it will divide the File Hunter into two parts with the same width. The same happens, if you resize the main window. {{ http://zetcode.com/wxpython/images/filehunter.png }} <code python> #!/usr/bin/python import wx import os import time ID_BUTTON=100 ID_EXIT=200 ID_SPLITTER=300 class MyListCtrl(wx.ListCtrl): def __init__(self, parent, id): wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT) files = os.listdir('.') images = ['images/empty.png', 'images/folder.png', 'images/source_py.png', 'images/image.png', 'images/pdf.png', 'images/up16.png'] self.InsertColumn(0, 'Name') self.InsertColumn(1, 'Ext') self.InsertColumn(2, 'Size', wx.LIST_FORMAT_RIGHT) self.InsertColumn(3, 'Modified') self.SetColumnWidth(0, 220) self.SetColumnWidth(1, 70) self.SetColumnWidth(2, 100) self.SetColumnWidth(3, 420) self.il = wx.ImageList(16, 16) for i in images: self.il.Add(wx.Bitmap(i)) self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) j = 1 self.InsertStringItem(0, '..') self.SetItemImage(0, 5) for i in files: (name, ext) = os.path.splitext(i) ex = ext[1:] size = os.path.getsize(i) sec = os.path.getmtime(i) self.InsertStringItem(j, name) self.SetStringItem(j, 1, ex) self.SetStringItem(j, 2, str(size) + ' B') self.SetStringItem(j, 3, time.strftime('%Y-%m-%d %H:%M', time.localtime(sec))) if os.path.isdir(i): self.SetItemImage(j, 1) elif ex == 'py': self.SetItemImage(j, 2) elif ex == 'jpg': self.SetItemImage(j, 3) elif ex == 'pdf': self.SetItemImage(j, 4) else: self.SetItemImage(j, 0) if (j % 2) == 0: self.SetItemBackgroundColour(j, '#e6f1f5') j = j + 1 class FileHunter(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, -1, title) self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER) self.splitter.SetMinimumPaneSize(50) p1 = MyListCtrl(self.splitter, -1) p2 = MyListCtrl(self.splitter, -1) self.splitter.SplitVertically(p1, p2) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_SPLITTER_DCLICK, self.OnDoubleClick, id=ID_SPLITTER) filemenu= wx.Menu() filemenu.Append(ID_EXIT,"E&xit"," Terminate the program") editmenu = wx.Menu() netmenu = wx.Menu() showmenu = wx.Menu() configmenu = wx.Menu() helpmenu = wx.Menu() menuBar = wx.MenuBar() menuBar.Append(filemenu,"&File") menuBar.Append(editmenu, "&Edit") menuBar.Append(netmenu, "&Net") menuBar.Append(showmenu, "&Show") menuBar.Append(configmenu, "&Config") menuBar.Append(helpmenu, "&Help") self.SetMenuBar(menuBar) self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT) tb.AddSimpleTool(10, wx.Bitmap('images/previous.png'), 'Previous') tb.AddSimpleTool(20, wx.Bitmap('images/up.png'), 'Up one directory') tb.AddSimpleTool(30, wx.Bitmap('images/home.png'), 'Home') tb.AddSimpleTool(40, wx.Bitmap('images/refresh.png'), 'Refresh') tb.AddSeparator() tb.AddSimpleTool(50, wx.Bitmap('images/write.png'), 'Editor') tb.AddSimpleTool(60, wx.Bitmap('images/terminal.png'), 'Terminal') tb.AddSeparator() tb.AddSimpleTool(70, wx.Bitmap('images/help.png'), 'Help') tb.Realize() self.sizer2 = wx.BoxSizer(wx.HORIZONTAL) button1 = wx.Button(self, ID_BUTTON + 1, "F3 View") button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit") button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy") button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move") button5 = wx.Button(self, ID_BUTTON + 5, "F7 Mkdir") button6 = wx.Button(self, ID_BUTTON + 6, "F8 Delete") button7 = wx.Button(self, ID_BUTTON + 7, "F9 Rename") button8 = wx.Button(self, ID_EXIT, "F10 Quit") self.sizer2.Add(button1, 1, wx.EXPAND) self.sizer2.Add(button2, 1, wx.EXPAND) self.sizer2.Add(button3, 1, wx.EXPAND) self.sizer2.Add(button4, 1, wx.EXPAND) self.sizer2.Add(button5, 1, wx.EXPAND) self.sizer2.Add(button6, 1, wx.EXPAND) self.sizer2.Add(button7, 1, wx.EXPAND) self.sizer2.Add(button8, 1, wx.EXPAND) self.Bind(wx.EVT_BUTTON, self.OnExit, id=ID_EXIT) self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.splitter,1,wx.EXPAND) self.sizer.Add(self.sizer2,0,wx.EXPAND) self.SetSizer(self.sizer) size = wx.DisplaySize() self.SetSize(size) self.sb = self.CreateStatusBar() self.sb.SetStatusText(os.getcwd()) self.Center() self.Show(True) def OnExit(self,e): self.Close(True) def OnSize(self, event): size = self.GetSize() self.splitter.SetSashPosition(size.x / 2) self.sb.SetStatusText(os.getcwd()) event.Skip() def OnDoubleClick(self, event): size = self.GetSize() self.splitter.SetSashPosition(size.x / 2) app = wx.App(0) FileHunter(None, -1, 'File Hunter') app.MainLoop() </code> ==== SpreadSheet ==== Gnumeric, KSpread and OpenOffice Calc are famous spreadsheet applications available on Unix. The following example shows a skeleton of a spreadsheet application in wxPython. Applications have their own life. This is also true for educational scripts. After uprading to wx.Python 2.8.1.1 I realized, that the spreadsheet example does not work. The following line was the problem. <code python> toolbar2.AddControl(wx.StaticText(toolbar2, -1, ' ')) </code> Of course, we cannot add a widget to itself. But the previous version of the toolkit was happy with it. Under the current version it did not work, signalizing a problem. It might or might not work on the Mac and Windows. Originally, I wanted to add some space between the combo boxes. Under the new version of the toolkit it stopped to work either so I dropped the line. Besides fixing this bug, I also cleaned the code a bit and replaced the depreciated methods (//AddSimpleTool()//) of the toolbar with the new ones (//AddLabelTool()//). {{ http://zetcode.com/wxpython/images/spreadsheet.png }} <code python> #!/usr/bin/python # spreadsheet.py from wx.lib import sheet import wx class MySheet(sheet.CSheet): def __init__(self, parent): sheet.CSheet.__init__(self, parent) self.row = self.col = 0 self.SetNumberRows(55) self.SetNumberCols(25) for i in range(55): self.SetRowSize(i, 20) def OnGridSelectCell(self, event): self.row, self.col = event.GetRow(), event.GetCol() control = self.GetParent().GetParent().position value = self.GetColLabelValue(self.col) + self.GetRowLabelValue(self.row) control.SetValue(value) event.Skip() class Newt(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, -1, title, size = (550, 500)) fonts = ['Times New Roman', 'Times', 'Courier', 'Courier New', 'Helvetica', 'Sans', 'verdana', 'utkal', 'aakar', 'Arial'] font_sizes = ['10', '11', '12', '14', '16'] box = wx.BoxSizer(wx.VERTICAL) menuBar = wx.MenuBar() menu1 = wx.Menu() menuBar.Append(menu1, '&File') menu2 = wx.Menu() menuBar.Append(menu2, '&Edit') menu3 = wx.Menu() menuBar.Append(menu3, '&Edit') menu4 = wx.Menu() menuBar.Append(menu4, '&Insert') menu5 = wx.Menu() menuBar.Append(menu5, 'F&ormat') menu6 = wx.Menu() menuBar.Append(menu6, '&Tools') menu7 = wx.Menu() menuBar.Append(menu7, '&Data') menu8 = wx.Menu() menuBar.Append(menu8, '&Help') self.SetMenuBar(menuBar) toolbar1 = wx.ToolBar(self, -1, style= wx.TB_HORIZONTAL) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_new.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_open.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_save.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_cut.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_copy.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_paste.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_delete.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_undo.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_redo.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/incr22.png')) toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/decr22.png')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/chart.xpm')) toolbar1.AddSeparator() toolbar1.AddLabelTool(-1, '', wx.Bitmap('icons/stock_exit.png')) toolbar1.Realize() toolbar2 = wx.ToolBar(self, wx.TB_HORIZONTAL | wx.TB_TEXT) self.position = wx.TextCtrl(toolbar2) font = wx.ComboBox(toolbar2, -1, value = 'Times', choices=fonts, size=(100, -1), style=wx.CB_DROPDOWN) font_height = wx.ComboBox(toolbar2, -1, value = '10', choices=font_sizes, size=(50, -1), style=wx.CB_DROPDOWN) toolbar2.AddControl(self.position) toolbar2.AddControl(font) toolbar2.AddControl(font_height) toolbar2.AddSeparator() bold = wx.Bitmap('icons/stock_text_bold.png') toolbar2.AddCheckTool(-1, bold) italic = wx.Bitmap('icons/stock_text_italic.png') toolbar2.AddCheckTool(-1, italic) under = wx.Bitmap('icons/stock_text_underline.png') toolbar2.AddCheckTool(-1, under) toolbar2.AddSeparator() toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_left.png')) toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_center.png')) toolbar2.AddLabelTool(-1, '', wx.Bitmap('icons/text_align_right.png')) box.Add(toolbar1, border=5) box.Add((5,5) , 0) box.Add(toolbar2) box.Add((5,10) , 0) toolbar2.Realize() self.SetSizer(box) notebook = wx.Notebook(self, -1, style=wx.RIGHT) sheet1 = MySheet(notebook) sheet2 = MySheet(notebook) sheet3 = MySheet(notebook) sheet1.SetFocus() notebook.AddPage(sheet1, 'Sheet1') notebook.AddPage(sheet2, 'Sheet2') notebook.AddPage(sheet3, 'Sheet3') box.Add(notebook, 1, wx.EXPAND) self.CreateStatusBar() self.Centre() self.Show(True) app = wx.App() Newt(None, -1, 'SpreadSheet') app.MainLoop() </code> Much of the code builds the menus and toolbars. Besides, it is quite a simple example. <code python> class MySheet(sheet.CSheet): def __init__(self, parent): sheet.CSheet.__init__(self, parent) self.row = self.col = 0 self.SetNumberRows(55) self.SetNumberCols(25) for i in range(55): self.SetRowSize(i, 20) </code> The MySheet class inherits from the CSheet class, which is located in thel wx.lib module. It is basically a wx.Grid widget with some additional functionality. We set the row size to 20px. This is purely for aesthetical purpose. <code python> control = self.GetParent().GetParent().position </code> The position text control shows the selected cell of the grid widget. It is the first widget of the second toolbar. Being inside a MySheet class, we need to get a reference to the text control, which is defined in the Newt class. MySheet is a child of the notebook. And notebook is a child of Newt. So we manage to get to the position text control by calling the //GetParent()// method twice. <code python> notebook = wx.Notebook(self, -1, style=wx.RIGHT) </code> This is a bug. Under current version of wxPython (on GTK+), right is bottom and bottom is right. ==== Browser ==== These days internet browsers are one of the most important applications in the IT world. The best available browsers are Opera and Firefox. We mimic the look of a Firefox in our script. {{ http://zetcode.com/wxpython/images/browser.png }} <code python> #!/usr/bin/python import wx from wx.lib.buttons import GenBitmapTextButton class Browser(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(450, 400)) panel = wx.Panel(self, -1) panel.SetBackgroundColour('WHITE') menubar = wx.MenuBar() file = wx.Menu() file.Append(1, '&Quit', '') edit = wx.Menu() view = wx.Menu() go = wx.Menu() bookmarks = wx.Menu() tools = wx.Menu() help = wx.Menu() menubar.Append(file, '&File') menubar.Append(edit, '&Edit') menubar.Append(view, '&View') menubar.Append(go, '&Go') menubar.Append(bookmarks, '&Bookmarks') menubar.Append(tools, '&Tools') menubar.Append(help, '&Help') self.SetMenuBar(menubar) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) toolbar1 = wx.Panel(panel, -1, size=(-1, 40)) back = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/back.png'), style=wx.NO_BORDER) forward = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/forward.png'), style=wx.NO_BORDER) refresh = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/refresh.png'), style=wx.NO_BORDER) stop = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/stop.png'), style=wx.NO_BORDER) home = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/home.png'), style=wx.NO_BORDER) address = wx.ComboBox(toolbar1, -1, size=(50, -1)) go = wx.BitmapButton(toolbar1, -1, wx.Bitmap('icons/go.png'), style=wx.NO_BORDER) text = wx.TextCtrl(toolbar1, -1, size=(150, -1)) hbox1.Add(back) hbox1.Add(forward) hbox1.Add(refresh) hbox1.Add(stop) hbox1.Add(home) hbox1.Add(address, 1, wx.TOP, 4) hbox1.Add(go, 0, wx.TOP | wx.LEFT, 4) hbox1.Add(text, 0, wx.TOP | wx.RIGHT, 4) vbox.Add(toolbar1, 0, wx.EXPAND) line = wx.StaticLine(panel) vbox.Add(line, 0, wx.EXPAND) toolbar2 = wx.Panel(panel, -1, size=(-1, 30)) bookmark1 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/love.png'), style=wx.NO_BORDER) bookmark2 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/books.png'), style=wx.NO_BORDER) bookmark3 = wx.BitmapButton(toolbar2, -1, wx.Bitmap('icons/sound.png'), style=wx.NO_BORDER) hbox2.Add(bookmark1, flag=wx.RIGHT, border=5) hbox2.Add(bookmark2, flag=wx.RIGHT, border=5) hbox2.Add(bookmark3) toolbar2.SetSizer(hbox2) vbox.Add(toolbar2, 0, wx.EXPAND) line = wx.StaticLine(panel) vbox.Add(line, 0, wx.EXPAND) panel.SetSizer(vbox) self.CreateStatusBar() self.Centre() self.Show(True) app = wx.App(0) Browser(None, -1, 'Browser') app.MainLoop() </code> The question was, how to create a sizeable combo box, that is used in both Firefox and Opera? We cannot use a //wx.Toolbar//. It is not possible to create such a functionality with //wx.Toolbar//. Confirmed with Robin Dunn. So we must do a workaround. <code python> toolbar1 = wx.Panel(panel, -1, size=(-1, 40)) </code> The trick is simple. We create a plain //wx.Panel//. <code python> hbox1 = wx.BoxSizer(wx.HORIZONTAL) ... hbox1.Add(back) hbox1.Add(forward) hbox1.Add(refresh) </code> We create a horizontal sizer and add all necessary buttons. <code python> hbox1.Add(address, 1, wx.TOP, 4) </code> Then we add the combo box to the sizer. This kind of combo box is usually called an address bar. Notice, that it is the only widget, that has the proportion set to 1. This was necessary to make it resizable. The second toolbar was created in a similar way. The toolbars are separated by a line. First I thought, it was some kind of a panel border. I tested all possible borders, but it wasn't quite what I expected. <code python> line = wx.StaticLine(panel) </code> Then I suddently got it. It is a simple static line! Sometimes, we must create a solution, for which we don't have a suitable widget. By using simple common sense, we can easily find a way. </html> ===== Creating custom widgets ===== Have you ever looked at an application and wondered, how a particular gui item was created? Probably every wannabe programmer has. Then you were looking at a list of widgets provided by your favourite gui library. But you couldn't find it. Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets. There are actually two kinds of toolkits. Spartan toolkits and heavy weight toolkits. The FLTK toolkit is a kind of a spartan toolkit. It provides only the very basic widgets and assumes, that the programemer will create the more complicated ones himself. wxPython is a heavy weight one. It has lots of widgets. Yet it does not provide the more specialized widgets. For example a speed meter widget, a widget that measures the capacity of a CD to be burned (found e.g. in nero). Toolkits also don't have usually charts. Programmers must create such widgets by themselves. They do it by using the drawing tools provided by the toolkit. There are two possibilities. A programmer can modify or enhance an existing widget. Or he can create a custom widget from scratch. Here I assume, you have read the chapter on the GDI. ==== A hyperlink widget ==== The first example will create a hyperlink. The hyperlink widget will be based on an existing //wx.lib.stattext.GenStaticText// widget. <code python> #!/usr/bin/python # link.py import wx from wx.lib.stattext import GenStaticText import webbrowser class Link(GenStaticText): def __init__(self, parent, id=-1, label='', pos=(-1, -1), size=(-1, -1), style=0, name='Link', URL=''): GenStaticText.__init__(self, parent, id, label, pos, size, style, name) self.url = URL self.font1 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana') self.font2 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana') self.SetFont(self.font2) self.SetForegroundColour('#0000ff') self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent) self.Bind(wx.EVT_MOTION, self.OnMouseEvent) def OnMouseEvent(self, event): if event.Moving(): self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) self.SetFont(self.font1) elif event.LeftUp(): webbrowser.open_new(self.url) else: self.SetCursor(wx.NullCursor) self.SetFont(self.font2) event.Skip() class HyperLink(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(220, 150)) panel = wx.Panel(self, -1) Link(panel, -1, 'ZetCode', pos=(10, 60), URL='http://www.zetcode.com') motto = GenStaticText(panel, -1, 'Knowledge only matters', pos=(10, 30)) motto.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana')) self.Centre() self.Show(True) app = wx.App() HyperLink(None, -1, 'A Hyperlink') app.MainLoop() </code> This hyperlink widget is based on an existing widget. In this example we don't draw anything, we just use an existing widget, which we modify a bit. <code python> from wx.lib.stattext import GenStaticText import webbrowser </code> Here we import the base widget from which we derive our hyperlink widget and the webbrowser module. webbrowser module is a standard python module. We will use it to open links in a default browser. <code python> self.SetFont(self.font2) self.SetForegroundColour('#0000ff') </code> The idea behind creating a hyperlink widget is simple. We inherit from a base //wx.lib.stattext.GenStaticText// widget class. So we have a text widget. Then we modify it a bit to make a hyperlink out of this text. We change the font and the colour of the text. Hyperlinks are usually blue. <code python> if event.Moving(): self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) self.SetFont(self.font1) </code> If we hover a mouse pointer over the link, we change the font to underlined and also change the mouse pointer to a hand cursor. <code python> elif event.LeftUp(): webbrowser.open_new(self.url) </code> If we left click on the link, we open the link in a defaul browser. {{ http://zetcode.com/wxpython/images/link.jpg }} {{ http://zetcode.com/wxpython/images/linkw.jpg }} ==== Burning widget ==== This is an example of a widget, that we create from a ground up. We put a **wx.Panel** on the bottom of the window and draw the entire widget manually. If you have ever burned a cd or a dvd, you already saw this kind of widget. Remark for windows users. To avoid flicker, use double buffering. <code python> #!/usr/bin/python # burning.py import wx class Widget(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id, size=(-1, 30), style=wx.SUNKEN_BORDER) self.parent = parent self.font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Courier 10 Pitch') self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) def OnPaint(self, event): num = range(75, 700, 75) dc = wx.PaintDC(self) dc.SetFont(self.font) w, h = self.GetSize() self.cw = self.parent.GetParent().cw step = int(round(w / 10.0)) j = 0 till = (w / 750.0) * self.cw full = (w / 750.0) * 700 if self.cw >= 700: dc.SetPen(wx.Pen('#FFFFB8')) dc.SetBrush(wx.Brush('#FFFFB8')) dc.DrawRectangle(0, 0, full, 30) dc.SetPen(wx.Pen('#ffafaf')) dc.SetBrush(wx.Brush('#ffafaf')) dc.DrawRectangle(full, 0, till-full, 30) else: dc.SetPen(wx.Pen('#FFFFB8')) dc.SetBrush(wx.Brush('#FFFFB8')) dc.DrawRectangle(0, 0, till, 30) dc.SetPen(wx.Pen('#5C5142')) for i in range(step, 10*step, step): dc.DrawLine(i, 0, i, 6) width, height = dc.GetTextExtent(str(num[j])) dc.DrawText(str(num[j]), i-width/2, 8) j = j + 1 def OnSize(self, event): self.Refresh() class Burning(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(330, 200)) self.cw = 75 panel = wx.Panel(self, -1) CenterPanel = wx.Panel(panel, -1) self.sld = wx.Slider(CenterPanel, -1, 75, 0, 750, (-1, -1), (150, -1), wx.SL_LABELS) vbox = wx.BoxSizer(wx.VERTICAL) hbox = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) hbox3 = wx.BoxSizer(wx.HORIZONTAL) self.wid = Widget(panel, -1) hbox.Add(self.wid, 1, wx.EXPAND) hbox2.Add(CenterPanel, 1, wx.EXPAND) hbox3.Add(self.sld, 0, wx.TOP, 35) CenterPanel.SetSizer(hbox3) vbox.Add(hbox2, 1, wx.EXPAND) vbox.Add(hbox, 0, wx.EXPAND) self.Bind(wx.EVT_SCROLL, self.OnScroll) panel.SetSizer(vbox) self.sld.SetFocus() self.Centre() self.Show(True) def OnScroll(self, event): self.cw = self.sld.GetValue() self.wid.Refresh() app = wx.App() Burning(None, -1, 'Burning widget') app.MainLoop() </code> All the important code resides in the //OnPaint()// method of the Widget class. This widget shows graphically the total capacity of a medium and the free space available to us. The widget is controlled by a slider widget. The minimum value of our custom widget is 0, the maximum is 750. If we reach value 700, we began drawing in red colour. This normally indicates overburning. <code python> w, h = self.GetSize() self.cw = self.parent.GetParent().cw ... till = (w / 750.0) * self.cw full = (w / 750.0) * 700 </code> We draw the widget dynamically. The greater the window, the greater the burning widget. And vice versa. That is why we must calculate the size of the //wx.Panel// onto which we draw the custom widget. The till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The full parameter determines the point, where we begin to draw in red color. Notice the use of floating point arithmetics. This is to achieve greater precision. The actual drawing consists of three steps. We draw the yellow or red and yellow rectangle. Then we draw the vertical lines, which divide the widget into several parts. Finally, we draw the numbers, which indicate the capacity of the medium. <code python> def OnSize(self, event): self.Refresh() </code> Every time the window is resized, we refresh the widget. This causes the widget to repaint itself. <code python> def OnScroll(self, event): self.cw = self.sld.GetValue() self.wid.Refresh() </code> If we scroll the thumb of the slider, we get the actual value and save it into the //self.cw// parameter. This value is used, when the burning widget is drawn. Then we cause the widget to be redrawn. {{ http://zetcode.com/wxpython/images/burning.jpg }} {{ http://zetcode.com/wxpython/images/burningw.jpg }} ==== The CPU widget ==== There are system applications that measure system resources. The temperature, memory and CPU consuption etc. By displaying a simple text like CPU 54% you probably won't impress your users. Specialized widgets are created to make the application more appealing. The following widget is often used in system applications. Remark for windows users. To avoid flicker, use double buffering. Change the size of the application and the width of the slider. <code python> #!/usr/bin/python # cpu.py import wx class CPU(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id, size=(80, 110)) self.parent = parent self.SetBackgroundColour('#000000') self.Bind(wx.EVT_PAINT, self.OnPaint) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetDeviceOrigin(0, 100) dc.SetAxisOrientation(True, True) pos = self.parent.GetParent().GetParent().sel rect = pos / 5 for i in range(1, 21): if i > rect: dc.SetBrush(wx.Brush('#075100')) dc.DrawRectangle(10, i*4, 30, 5) dc.DrawRectangle(41, i*4, 30, 5) else: dc.SetBrush(wx.Brush('#36ff27')) dc.DrawRectangle(10, i*4, 30, 5) dc.DrawRectangle(41, i*4, 30, 5) class CPUWidget(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(190, 140)) self.sel = 0 panel = wx.Panel(self, -1) centerPanel = wx.Panel(panel, -1) self.cpu = CPU(centerPanel, -1) hbox = wx.BoxSizer(wx.HORIZONTAL) self.slider = wx.Slider(panel, -1, self.sel, 0, 100, (-1, -1), (25, 90), wx.VERTICAL | wx.SL_LABELS | wx.SL_INVERSE) self.slider.SetFocus() hbox.Add(centerPanel, 0, wx.LEFT | wx.TOP, 20) hbox.Add(self.slider, 0, wx.LEFT | wx.TOP, 23) self.Bind(wx.EVT_SCROLL, self.OnScroll) panel.SetSizer(hbox) self.Centre() self.Show(True) def OnScroll(self, event): self.sel = event.GetInt() self.cpu.Refresh() app = wx.App() CPUWidget(None, -1, 'cpu') app.MainLoop() </code> Creating this widget is quite simple. We create a black panel. Then we draw small rectangles onto this panel. The color of the rectangles depend on the value of the slider. The color can be dark green or bright green. <code python> dc.SetDeviceOrigin(0, 100) dc.SetAxisOrientation(True, True) </code> Here we change the default coordinate system to cartesian. This is to make the drawing intuitive. <code python> pos = self.parent.GetParent().GetParent().sel rect = pos / 5 </code> Here we get the value of the sizer. We have 20 rectangles in each column. The slider has 100 numbers. The rect parameter makes a convertion from slider values into rectangles, that will be drawn in bright green color. <code python> for i in range(1, 21): if i > rect: dc.SetBrush(wx.Brush('#075100')) dc.DrawRectangle(10, i*4, 30, 5) dc.DrawRectangle(41, i*4, 30, 5) else: dc.SetBrush(wx.Brush('#36ff27')) dc.DrawRectangle(10, i*4, 30, 5) dc.DrawRectangle(41, i*4, 30, 5) </code> Here we draw 40 rectangles, 20 in each column. If the number of the rectangle being drawn is greater than the converted rect value, we draw it in a dark green color. Otherwise in bright green. {{ http://zetcode.com/wxpython/images/cpu.jpg |cpu widget on linux}} {{ http://zetcode.com/wxpython/images/cpuw.jpg |cpu widget on windows}} </html> ===== xml resource files ===== The idea behind xml resources is to separate the interface from the code of an application. Several GUI builders use this concept for creating interfaces. For example the famous Glade. In our example we create a simple frame window with one button. We load resources from a file, load a panel and bind an event to a button. <code python> #!/usr/bin/python # xml.py import wx import wx.xrc as xrc class Xml(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) res = xrc.XmlResource('resource.xrc') res.LoadPanel(self, 'MyPanel') self.Bind(wx.EVT_BUTTON, self.OnClose, id=xrc.XRCID('CloseButton')) self.Center() self.Show(True) def OnClose(self, event): self.Close() app = wx.App() Xml(None, -1, 'xml.py') app.MainLoop() </code> This is resource file resource.xrc It is a xml file, where we define our widgets and their patterns. In thid file, we use tags like <code python>&lt;object&gt;&lt;/object&gt;, &lt;item&gt;&lt;/item&gt;</code> etc. <code python> &lt;?xml version="1.0" ?&gt; &lt;resource&gt; &lt;object class="wxPanel" name="MyPanel"&gt; &lt;object class="wxButton" name="CloseButton"&gt; &lt;label&gt;Close&lt;/label&gt; &lt;pos&gt;15,10&lt;/pos&gt; &lt;/object&gt; &lt;/object&gt; &lt;/resource&gt; </code> We use these two calls for working with widgets: * XRCID(resource_name) - gives us the id of a button or menu item * XRCCTRL(resource_name) - gives us the handlers of our widgets defined in resource file </html> ===== The GDI ===== The **GDI** (**Graphics Device Interface**) is an interface for working with graphics. It is used to interact with graphic devices such as monitor, printer or a file. The GDI allows programmers to display data on a screen or printer without having to be concerned about the details of a particular device. The GDI insulates the programmer from the hardware. {{ http://zetcode.com/wxpython/images/gdi2.png |The GDI}} From the programmer's point of view, the GDI is a group of classes and methods for working with graphics. The GDI consists of 2D Vector Graphics, Fonts and Images. To begin drawing graphics, we must create a **device context** (**DC**) object. In wxPython the device context is called **wx.DC**. The documentation defines wx.DC as a device context onto which which graphics and text can be drawn. It represents number of devices in a generic way. Same piece of code can write to different kinds of devices. Be it a screen or a printer. The wx.DC is not intended to be used directly. Instead a programmer should choose one of the derived classes. Each derived class is intended to be used under specific conditions. <strong>Derived wx.DC classes</strong> * wxBufferedDC * wxBufferedPaintDC * wxPostScriptDC * wxMemoryDC * wxPrinterDC * wxScreenDC * wxClientDC * wxPaintDC * wxWindowDC The //wx.ScreenDC// is used to draw anywhere on the screen. The //wx.WindowDC// is used if we want to paint on the whole window (Windows only). This includes window decorations. The //wx.ClientDC// is used to draw on the client area of a window. The client area is the area of a window without it's decorations (title and border). The //wx.PaintDC// is used to draw on the client area as well. But there is one difference between the //wx.PaintDC// and the //wx.ClientDC//. The //wx.PaintDC// should be used only from a //wx.PaintEvent//. The //wx.ClientDC// shoud not be used from a //wx.PaintEvent//. The //wx.MemoryDC// is used to draw graphics on the bitmap. The //wx.PostScriptDC// is used to write to PostScript files on any platform. The //wx.PrinterDC// is used to access a printer (Windows only). ==== Drawing a simple line ==== Our first example will draw a simple line onto the client area of a window. <code python> DrawLine(int x1, int y1, int x2, int y2) </code> This method draws a line from the first point to the second. Excluding the second point. <code python> #!/usr/bin/python # line1.py import wx class Line(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) wx.FutureCall(2000, self.DrawLine) self.Centre() self.Show(True) def DrawLine(self): dc = wx.ClientDC(self) dc.DrawLine(50, 60, 190, 60) app = wx.App() Line(None, -1, 'Line') app.MainLoop() </code> <code python> wx.FutureCall(2000, self.DrawLine) </code> We call the //DrawLine()// method after the window has been created. We do it because, when the window is created, it is drawn. All our drawings would be therefore lost. We can start drawing after the window has been created. This is the reason, why we call the //wx.FutureCall()// method. <code python> def DrawLine(self): dc = wx.ClientDC(self) dc.DrawLine(50, 60, 190, 60) </code> We create a //wx.ClientDC// device context. The only parameter is the window on which we want to draw. In our case it is //self//, which is a reference to our //wx.Frame// widget. We call the //DrawLine()// method of the device context. This call actually draws a line on our window. It is very important to understand the following behaviour. If we resize the window, the line will disappear. Why is this happening? Every window is redrawn, if it is resized. It is also redrawn, if it is maximized. The window is also redrawn, if we cover it by another window and uncover afterwards. The window is drawn to it's default state and our line is lost. We have to draw the line each time the window is resized. The solution is the //wx.PaintEvent//. This event is triggered every time, the window is redrawn. We will draw our line inside a method that will be hooked to the paint event. The following example shows how it is done. <code python> #!/usr/bin/python # line2.py import wx class Line(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.DrawLine(50, 60, 190, 60) app = wx.App() Line(None, -1, 'Line') app.MainLoop() </code> <code python> self.Bind(wx.EVT_PAINT, self.OnPaint) </code> Here we bind the //OnPaint// method to the //wx.PaintEvent// event. It means, that each time our window is repainted, we call the //OnPaint// method. Now the line will not disappear, if we resize our window (cover it, maximize it). <code python> dc = wx.PaintDC(self) </code> Notice, that this time we have used the //wx.PaintDC// device context. {{ http://zetcode.com/wxpython/images/line.png |}} <h2>2D Vector Graphics</h2> There are two different computer graphics. **Vector** and **raster** graphics. Raster graphics represents images as a collection of pixels. Vector graphics is the use of geometrical primitives such as points, lines, curves or polygons to represent images. These primitives are created using mathematical equations. Both types of computer graphics have advantages and disadvantages. The advantages of vector graphics over raster are: * smaller size * ability to zoom indefinitely * moving, scaling, filling or rotating does not degrade the quality of an image <strong>Types of primitives</strong> * points * lines * polylines * polygons * circles * ellipses * Splines **Device context attributes** ^ Attribute ^ Object ^ Default value ^ Get Method ^ Set Method ^ | Brush | wx.Brush | wx.WHITE_BRUSH| wx.Brush GetBrush()| SetBrush(wx.Brush brush) | | Pen | wx.Pen | wx.BLACK_PEN| wx.Pen GetPen()| SetPen(wx.Pen pen) | | Mapping Mode | --| wx.MM_TEXT| int GetMapMode()| SetMapMode(int mode) | | BackgroundMode | --| wx.TRANSPARENT| int GetBackgroundMode()| SetBackgroundMode(int mode) | | Text background colour | wx.Colour| wx.WHITE| wx.Colour GetTextBackground()| SetTextBackground(wx.Colour colour) | | Text foreground colour | wx.Colour| wx.BLACK| wx.Colour GetTextForeground()| SetTextForeground(wx.Colour colour) | In the following lines we will introduce several elementary objects. Colours, Brushes, Pens, Joins, Caps, Gradients. === Colours === A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Valid RGB values are in the range 0 to 255. There are three ways for setting colours. We can create a wx.Colour object, use a predefined colour name or use hex value string. //wx.Colour(0,0,255)//, //'BLUE'//, //'#0000FF'//. These three notations produce the same colour. I prefer the hexadecimal notation. A perfect tool for working with colours can be found on the [[http://www.colorjack.com|colorjack.com]] website. Or we can use such a tool as Gimp. We have also a list of predefined colour names that we can use in our programs. |AQUAMARINE| BLACK| BLUE| BLUE VIOLET| BROWN | |CADET BLUE| CORAL| CORNFLOWER BLUE| CYAN| DARK GREY | |DARK GREEN| DARK OLIVE GREEN| DARK ORCHID| DARK SLATE BLUE| DARK SLATE GREY | |DARK TURQUOISE| DIM GREY| FIREBRICK| FOREST GREEN| GOLD | |GOLDENROD| GREY| GREEN| GREEN YELLOW| INDIAN RED | |KHAKI| LIGHT BLUE| LIGHT GREY| LIGHT STEEL BLUE| LIME GREEN | |MAGENTA| MAROON| MEDIUM AQUAMARINE| MEDIUM BLUE| MEDIUM FOREST GREEN | |MEDIUM GOLDENROD| MEDIUM ORCHID| MEDIUM SEA GREEN| MEDIUM SLATE BLUE| MEDIUM SPRING GREEN | |MEDIUM TURQUOISE| MEDIUM VIOLET RED| MIDNIGHT BLUE| NAVY| ORANGE | |ORANGE RED| ORCHID| PALE GREEN| PINK| PLUM | |PURPLE| RED| SALMON| SEA GREEN| SIENNA | |SKY BLUE| SLATE BLUE| SPRING GREEN| STEEL BLUE| TAN | |THISTLE | TURQUOISE| VIOLET| VIOLET RED| WHEAT | |WHITE| YELLOW| YELLOW GREEN| - | - | <code python> #!/usr/bin/python # colours.py import wx class Colours(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 280)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('#d4d4d4')) dc.SetBrush(wx.Brush('#c56c00')) dc.DrawRectangle(10, 15, 90, 60) dc.SetBrush(wx.Brush('#1ac500')) dc.DrawRectangle(130, 15, 90, 60) dc.SetBrush(wx.Brush('#539e47')) dc.DrawRectangle(250, 15, 90, 60) dc.SetBrush(wx.Brush('#004fc5')) dc.DrawRectangle(10, 105, 90, 60) dc.SetBrush(wx.Brush('#c50024')) dc.DrawRectangle(130, 105, 90, 60) dc.SetBrush(wx.Brush('#9e4757')) dc.DrawRectangle(250, 105, 90, 60) dc.SetBrush(wx.Brush('#5f3b00')) dc.DrawRectangle(10, 195, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c')) dc.DrawRectangle(130, 195, 90, 60) dc.SetBrush(wx.Brush('#785f36')) dc.DrawRectangle(250, 195, 90, 60) app = wx.App() Colours(None, -1, 'Colours') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/colours.png |Colours}} === wx.Pen === Pen is an elementary graphics object. It is used to draw lines, curves and outlines of rectangles, ellipses, polygons or other shapes. <code python> wx.Pen(wx.Colour colour, widht=1, style=wx.SOLID) </code> The //wx.Pen// constructor has three parameters. Colour, width and style. Follows a list of possible pen styles. **Pen styles** * wx.SOLID * wx.DOT * wx.LONG_DASH * wx.SHORT_DASH * wx.DOT_DASH * wx.TRANSPARENT <code python> #!/usr/bin/python # pens.py import wx class Pens(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 190)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.SOLID)) dc.DrawRectangle(10, 15, 90, 60) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.DOT)) dc.DrawRectangle(130, 15, 90, 60) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.LONG_DASH)) dc.DrawRectangle(250, 15, 90, 60) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.SHORT_DASH)) dc.DrawRectangle(10, 105, 90, 60) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.DOT_DASH)) dc.DrawRectangle(130, 105, 90, 60) dc.SetPen(wx.Pen('#4c4c4c', 1, wx.TRANSPARENT)) dc.DrawRectangle(250, 105, 90, 60) app = wx.App() Pens(None, -1, 'Pens') app.MainLoop() </code> If we don't specify a custom brush, a default one is used. The default brush is //wx.WHITE_BRUSH//. The perimeter of the rectangles is drawn by the pen. The last one has no border. It is transparent, e.g. not visible. {{ http://zetcode.com/wxpython/images/pens.png |Pens}} **Joins and Caps** A pen object has additional two parameters. The //Join// and the //Cap//. The //Join// defines how joins between lines will be drawn. The //Join// style has the following options: * wx.JOIN_MITER * wx.JOIN_BEVEL * wx.JOIN_ROUND When using //wx.JOIN_MITER// the outer edges of the lines are extended. They meet at an angle, and this area is filled. In //wx.JOIN_BEVEL// the triangular notch between two lines is filled. In //wx.JOIN_ROUND// the circular arc between the two lines is filled. The default value is //wx.JOIN_ROUND//. The //Cap// defines how the line ends will be drawn by the pen. The options are: * wx.CAP_ROUND * wx.CAP_PROJECTING * wx.CAP_BUTT The //wx.CAP_ROUND// will draw rounded ends. The //wx.CAP_PROJECTING// and the //wx.CAP_BUTT// will both draw square ends. The difference between them is that the //wx.CAP_PROJECTING// will extend beyond the end point by the half of the line size. The //wx.CAP_ROUND// will extend beyond the end point as well. <code python> #!/usr/bin/python # joinscaps.py import wx class JoinsCaps(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(330, 300)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) pen = wx.Pen('#4c4c4c', 10, wx.SOLID) pen.SetJoin(wx.JOIN_MITER) dc.SetPen(pen) dc.DrawRectangle(15, 15, 80, 50) pen.SetJoin(wx.JOIN_BEVEL) dc.SetPen(pen) dc.DrawRectangle(125, 15, 80, 50) pen.SetJoin(wx.JOIN_ROUND) dc.SetPen(pen) dc.DrawRectangle(235, 15, 80, 50) pen.SetCap(wx.CAP_BUTT) dc.SetPen(pen) dc.DrawLine(30, 150, 150, 150) pen.SetCap(wx.CAP_PROJECTING) dc.SetPen(pen) dc.DrawLine(30, 190, 150, 190) pen.SetCap(wx.CAP_ROUND) dc.SetPen(pen) dc.DrawLine(30, 230, 150, 230) pen2 = wx.Pen('#4c4c4c', 1, wx.SOLID) dc.SetPen(pen2) dc.DrawLine(30, 130, 30, 250) dc.DrawLine(150, 130, 150, 250) dc.DrawLine(155, 130, 155, 250) app = wx.App() JoinsCaps(None, -1, 'Joins and Caps') app.MainLoop() </code> <code python> pen = wx.Pen('#4c4c4c', 10, wx.SOLID) </code> In order to see the various //Join// and //Cap// styles, we need to set the pen width to be greater than 1. <code python> dc.DrawLine(150, 130, 150, 250) dc.DrawLine(155, 130, 155, 250) </code> Notice the two enclosing vertical lines. The distance between them is 5px. It is exactly the half of the current pen width. {{ http://zetcode.com/wxpython/images/joinscaps.png |Joins and Caps}} === Gradients === In computer graphics, gradient is a smooth blending of shades from light to dark or from one color to another. In 2D drawing programs and paint programs, gradients are used to create colorful backgrounds and special effects as well as to simulate lights and shadows. (answers.com) <code python> GradientFillLinear(wx.Rect rect, wx.Colour initialColour, wx.Colour destColour, int nDirection=wx.EAST) </code> This method fills the area specified by a //rect// with a linear gradient, starting from //initialColour// and eventually fading to //destColour//. The nDirection parameter specifies the direction of the colour change, the default value is wx.EAST. <code python> #!/usr/bin/python # gradients.py import wx class Gradients(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(220, 260)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.GradientFillLinear((20, 20, 180, 40), '#ffec00', '#000000', wx.NORTH) dc.GradientFillLinear((20, 80, 180, 40), '#ffec00', '#000000', wx.SOUTH) dc.GradientFillLinear((20, 140, 180, 40), '#ffec00', '#000000', wx.EAST) dc.GradientFillLinear((20, 200, 180, 40), '#ffec00', '#000000', wx.WEST) app = wx.App() Gradients(None, -1, 'Gradients') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/gradients.png |Gradients}} === wx.Brush === Brush is an elementary graphics object. It is used to paint the background of graphics shapes, such as rectangles, ellipses or polygons. <code python> wx.Brush(wx.Colour colour, style=wx.SOLID) </code> The constructor of the //wx.Brush// accepts two parameters. Colour name and style. The following is a list of possible brush styles. **Brush styles** * wx.SOLID * wx.STIPPLE * wx.BDIAGONAL_HATCH * wx.CROSSDIAG_HATCH * wx.FDIAGONAL_HATCH * wx.CROSS_HATCH * wx.HORIZONTAL_HATCH * wx.VERTICAL_HATCH * wx.TRANSPARENT <code python> #!/usr/bin/python # brushes.py import wx class Brush(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 280)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetBrush(wx.Brush('#4c4c4c', wx.CROSS_HATCH)) dc.DrawRectangle(10, 15, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.SOLID)) dc.DrawRectangle(130, 15, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.BDIAGONAL_HATCH)) dc.DrawRectangle(250, 15, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.CROSSDIAG_HATCH)) dc.DrawRectangle(10, 105, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.FDIAGONAL_HATCH)) dc.DrawRectangle(130, 105, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.HORIZONTAL_HATCH)) dc.DrawRectangle(250, 105, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.VERTICAL_HATCH)) dc.DrawRectangle(10, 195, 90, 60) dc.SetBrush(wx.Brush('#4c4c4c', wx.TRANSPARENT)) dc.DrawRectangle(130, 195, 90, 60) app = wx.App() Brush(None, -1, 'Brushes') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/brushes.png }} **Custom Patterns** We are not restricted to use predefined patterns. We can easily create our own custom patterns. <code python> wx.Brush BrushFromBitmap(wx.Bitmap stippleBitmap) </code> This method creates a custom brush from the bitmap. <code python> #!/usr/bin/python # custompatterns.py import wx class CustomPatterns(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 280)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('#C7C3C3')) brush1 = wx.BrushFromBitmap(wx.Bitmap('pattern1.png')) dc.SetBrush(brush1) dc.DrawRectangle(10, 15, 90, 60) brush2 = wx.BrushFromBitmap(wx.Bitmap('pattern2.png')) dc.SetBrush(brush2) dc.DrawRectangle(130, 15, 90, 60) brush3 = wx.BrushFromBitmap(wx.Bitmap('pattern3.png')) dc.SetBrush(brush3) dc.DrawRectangle(250, 15, 90, 60) brush4 = wx.BrushFromBitmap(wx.Bitmap('pattern4.png')) dc.SetBrush(brush4) dc.DrawRectangle(10, 105, 90, 60) brush5 = wx.BrushFromBitmap(wx.Bitmap('pattern5.png')) dc.SetBrush(brush5) dc.DrawRectangle(130, 105, 90, 60) brush6 = wx.BrushFromBitmap(wx.Bitmap('pattern6.png')) dc.SetBrush(brush6) dc.DrawRectangle(250, 105, 90, 60) brush7 = wx.BrushFromBitmap(wx.Bitmap('pattern7.png')) dc.SetBrush(brush7) dc.DrawRectangle(10, 195, 90, 60) brush8 = wx.BrushFromBitmap(wx.Bitmap('pattern8.png')) dc.SetBrush(brush8) dc.DrawRectangle(130, 195, 90, 60) brushr9 = wx.BrushFromBitmap(wx.Bitmap('pattern9.png')) dc.SetBrush(brushr9) dc.DrawRectangle(250, 195, 90, 60) app = wx.App() CustomPatterns(None, -1, 'Custom Patterns') app.MainLoop() </code> I have created some small bitmaps. For this I used the Gimp. These bitmaps are rectangles, usually around 40-150px. {{ http://zetcode.com/wxpython/images/custompatterns.jpg |Custom Patterns}} ==== Basic primitives ==== === Point === The simplest geometrical object is a point. It is a plain dot on the window. <code python> DrawPoint(int x, int y) </code> This method draws a point at x, y coordinates. <code python> #!/usr/bin/python # points.py import wx import random class Points(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('RED')) for i in range(1000): w, h = self.GetSize() x = random.randint(1, w-1) y = random.randint(1, h-1) dc.DrawPoint(x, y) app = wx.App() Points(None, -1, 'Points') app.MainLoop() </code> A single point might be difficult to see. So we create 1000 points. <code python> dc.SetPen(wx.Pen('RED')) </code> Here we set the colour of the pen to red. <code python> w, h = self.GetSize() x = random.randint(1, w-1) </code> The points are distributed randomly around the client area of the window. They are also distributed dynamically. If we resize the window, the points will be drawn randomly over a new client size. The //randint(a, b)// method returns a random integer in range [a, b], e.g. including both points. {{ http://zetcode.com/wxpython/images/points.png |Points}} === Cross Hair === Cross Hair is a vertical and horizontal line the height and width of the window. It is centered on the given point. <code python> CrossHair(int x, int y) </code> The method draws a cross hair centered on coordinates x, y. <code python> #!/usr/bin/python # crosshair.py import wx class CrossHair(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.CrossHair(50, 50) app = wx.App() CrossHair(None, -1, 'CrossHair') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/crosshair.png |Cross Hair}} In the following code example we will create a functionality often seen in games. 'Aiming at the enemy.' <code python> #!/usr/bin/python # crosshair2.py import wx class CrossHair(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) self.SetBackgroundColour('WHITE') self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS)) self.Centre() self.Show(True) def DrawCrossHair(self, a, b): dc = wx.ClientDC(self) dc.Clear() dc.SetPen(wx.Pen(wx.Colour(100, 100, 100), 1, wx.DOT)) dc.CrossHair(a, b) def OnMotion(self, event): x, y = event.GetPosition() self.DrawCrossHair(x, y) def OnLeaveWindow(self, event): dc = wx.ClientDC(self) dc.SetBackground(wx.Brush('WHITE')) dc.Clear() app = wx.App() CrossHair(None, -1, 'CrossHair2') app.MainLoop() </code> <code python> self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) </code> We bind two events to event handlers. The //wx.EVT_MOTION// event is generated, when we move a cursor over the window. The second event //wx.EVT_LEAVE_WINDOW// is generated, when we leave the window with our mouse cursor. <code python> def OnMotion(self, event): x, y = event.GetPosition() self.DrawCrossHair(x, y) </code> Every time we move a cursor over a window, the method //OnMotion()// is called. In this method we figure out the current position of the mouse cursor and call the //DrawCrossHair// method, which is responsible for drawing the cross hair. <code python> def DrawCrossHair(self, a, b): dc = wx.ClientDC(self) dc.Clear() dc.SetPen(wx.Pen(wx.Colour(100, 100, 100), 1, wx.DOT)) dc.CrossHair(a, b) </code> The user defined method //DrawCrossHair()// draws the cross hair primitive. Notice that to do the drawing, we use the //wx.ClientDC// device context. Remember that this device context should be used outside the paint event. This script is a perfect example, where we use //wx.ClientDC//. Not //wx.PaintDC//. Before we draw a new cross hair drawing, we must clear the old one. This is done with the //Clear()// method. It does clear the device context area. It uses the default //wx.WHITE_BRUSH// brush, unless we set a different one. We have set the window cursor to //wx.CURSOR_CROSS//. In order to actually see it, we have set the pen, which draws the cross hair, to light gray color, 1px wide, dotted. === Chech Mark === Check Mark is another simple primitive. <code python> DrawCheckMark(int x, int y, int width, int height) </code> The //DrawCheckMark()// method draws a check mark on the window at coordinates x, y. It draws the check mark inside the rectangle defined by //width// and //height// parameters. <code python> #!/usr/bin/python # checkmark.py import wx class CheckMark(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 150)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.DrawCheckMark(100, 50, 30, 40) app = wx.App() CheckMark(None, -1, 'Check Mark') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/checkmark.png |Check Mark}} Shapes are more sophisticated geometrical objects. We will draw various geometrical shapes in the following example. <code python> #!/usr/bin/python # shapes.py import wx class Shapes(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 300)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.DrawEllipse(20, 20, 90, 60) dc.DrawRoundedRectangle(130, 20, 90, 60, 10) dc.DrawArc(240, 40, 340, 40, 290, 20) dc.DrawPolygon(((130, 140), (180, 170), (180, 140), (220, 110), (140, 100))) dc.DrawRectangle(20, 120, 80, 50) dc.DrawSpline(((240, 170), (280, 170), (285, 110), (325, 110))) dc.DrawLines(((20, 260), (100, 260), (20, 210), (100, 210))) dc.DrawCircle(170, 230, 35) dc.DrawRectangle(250, 200, 60, 60) app = wx.App() Shapes(None, -1, 'Shapes') app.MainLoop() </code> In our example we have drawn an ellipse, a rounded rectangle, an arc, a rectangle ,a polygon, splines, lines, a circle and a square (from right to left, from top to bottom). A circle is a special kind of ellipse and a square is a special kind of rectangle. {{ http://zetcode.com/wxpython/images/shapes.png |Shapes}} The device context can be divided into several parts called **Regions**. A region can be of any shape. A region can be a simple rectangle or circle. With //Union//, //Intersect//, //Substract// and //Xor// operations we can create complex regions from simple ones. Regions are used for outlining, filling or clipping. We can create regions in three ways. The easiest way is to create a rectangular region. More complex regions can be created from a list of points of from a bitmap. <code python> wx.Region(int x=0, int y=0, int width=0, int height=0) </code> This constructor creates a rectangular region. <code python> wx.RegionFromPoints(list points, int fillStyle=wx.WINDING_RULE) </code> This constructor creates a polygonal region. The //fillStyle// parameter can be wx.WINDING_RULE or wx.ODDEVEN_RULE. <code python> wx.RegionFromBitmap(wx.Bitmap bmp) </code> The most complex regions can be created with the previous method. Before we go to the regions, we will create a small example first. We divide the topic into several parts so that it is easier to understand. You may find it a good idea to revise your school math. [[http://en.wikipedia.org/wiki/Circle|Here]] we can find a good article. <code python> #!/usr/bin/python # lines.py import wx from math import hypot, sin, cos, pi class Lines(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(450, 400)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) size_x, size_y = self.GetClientSizeTuple() dc.SetDeviceOrigin(size_x/2, size_y/2) radius = hypot(size_x/2, size_y/2) angle = 0 while (angle < 2*pi): x = radius*cos(angle) y = radius*sin(angle) dc.DrawLinePoint((0, 0), (x, y)) angle = angle + 2*pi/360 app = wx.App() Lines(None, -1, 'Lines') app.MainLoop() </code> In this example we draw 260 lines from the middle of the client area. The distance between two lines is 1 degree. We create an interesting figure. <code python> import wx from math import hypot, sin, cos, pi </code> We need three mathematical functions and one constant from the math module. <code python> dc.SetDeviceOrigin(size_x/2, size_y/2) </code> The method //SetDeviceOrigin()// creates a new beginning of the coordinate system. We place it into the middle of the client area. By repositioning the coordinate system, we make our drawing less complicated. <code python> radius = hypot(size_x/2, size_y/2) </code> Here we get the Hypotenuse. It is the longest line, we can draw from the middle of the client area. It is the length of the line, that should be drawn from the beginning into the corner of the window. This way most of the lines are not drawn fully. The overlapping parts are not visible. see [[http://en.wikipedia.org/wiki/Hypotenuse|Hypotenuse]]. <code python> x = radius*cos(angle) y = radius*sin(angle) </code> These are parametric functions. They are used to find [x, y] points on the curve. All 360 lines are drawn from the beginning of the coordinate system up to the points on the circle. {{ http://zetcode.com/wxpython/images/lines.png |Lines}} === Clipping === //Clipping// is restricting drawing to a certain area. Clipping is used in two cases. To create effects and to improve performance of the application. We restrict drawing to a certain region with the //SetClippingRegionAsRegion()// method. In the following example we will modify and enhance our previous script. <code python> #!/usr/bin/python # star.py import wx from math import hypot, sin, cos, pi class Star(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(350, 300)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('#424242')) size_x, size_y = self.GetClientSizeTuple() dc.SetDeviceOrigin(size_x/2, size_y/2) points = (((0, 85), (75, 75), (100, 10), (125, 75), (200, 85), (150, 125), (160, 190), (100, 150), (40, 190), (50, 125))) region = wx.RegionFromPoints(points) dc.SetClippingRegionAsRegion(region) radius = hypot(size_x/2, size_y/2) angle = 0 while (angle < 2*pi): x = radius*cos(angle) y = radius*sin(angle) dc.DrawLinePoint((0, 0), (x, y)) angle = angle + 2*pi/360 dc.DestroyClippingRegion() app = wx.App() Star(None, -1, 'Star') app.MainLoop() </code> We draw again all the 360 lines. But this time, only a portion of the client aren is drawn. The region that we restrict our drawing to is a star object. <code python> region = wx.RegionFromPoints(points) dc.SetClippingRegionAsRegion(region) </code> We create a region from the list of points with the //wx.RegionFromPoins()// method. The //SetClippingRegionAsRegion()// method restricts the drawing to the specified region. In our case it is a star object. <code python> dc.DestroyClippingRegion() </code> We must destroy the clipping region. {{ http://zetcode.com/wxpython/images/star.png |Star}} === Operations over Regions === Regions can be combined to create more complex shapes. We can use four set operations. //Union//, //Intersect//, //Substract// and //Xor//. The following example shows all four operations in action. <code python> #!/usr/bin/python # operations.py import wx class Operations(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(270, 220)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetPen(wx.Pen('#d4d4d4')) dc.DrawRectangle(20, 20, 50, 50) dc.DrawRectangle(30, 40, 50, 50) dc.SetBrush(wx.Brush('#ffffff')) dc.DrawRectangle(100, 20, 50, 50) dc.DrawRectangle(110, 40, 50, 50) region1 = wx.Region(100, 20, 50, 50) region2 = wx.Region(110, 40, 50, 50) region1.IntersectRegion(region2) rect = region1.GetBox() dc.SetClippingRegionAsRegion(region1) dc.SetBrush(wx.Brush('#ff0000')) dc.DrawRectangleRect(rect) dc.DestroyClippingRegion() dc.SetBrush(wx.Brush('#ffffff')) dc.DrawRectangle(180, 20, 50, 50) dc.DrawRectangle(190, 40, 50, 50) region1 = wx.Region(180, 20, 50, 50) region2 = wx.Region(190, 40, 50, 50) region1.UnionRegion(region2) dc.SetClippingRegionAsRegion(region1) rect = region1.GetBox() dc.SetBrush(wx.Brush('#fa8e00')) dc.DrawRectangleRect(rect) dc.DestroyClippingRegion() dc.SetBrush(wx.Brush('#ffffff')) dc.DrawRectangle(20, 120, 50, 50) dc.DrawRectangle(30, 140, 50, 50) region1 = wx.Region(20, 120, 50, 50) region2 = wx.Region(30, 140, 50, 50) region1.XorRegion(region2) rect = region1.GetBox() dc.SetClippingRegionAsRegion(region1) dc.SetBrush(wx.Brush('#619e1b')) dc.DrawRectangleRect(rect) dc.DestroyClippingRegion() dc.SetBrush(wx.Brush('#ffffff')) dc.DrawRectangle(100, 120, 50, 50) dc.DrawRectangle(110, 140, 50, 50) region1 = wx.Region(100, 120, 50, 50) region2 = wx.Region(110, 140, 50, 50) region1.SubtractRegion(region2) rect = region1.GetBox() dc.SetClippingRegionAsRegion(region1) dc.SetBrush(wx.Brush('#715b33')) dc.DrawRectangleRect(rect) dc.DestroyClippingRegion() dc.SetBrush(wx.Brush('#ffffff')) dc.DrawRectangle(180, 120, 50, 50) dc.DrawRectangle(190, 140, 50, 50) region1 = wx.Region(180, 120, 50, 50) region2 = wx.Region(190, 140, 50, 50) region2.SubtractRegion(region1) rect = region2.GetBox() dc.SetClippingRegionAsRegion(region2) dc.SetBrush(wx.Brush('#0d0060')) dc.DrawRectangleRect(rect) dc.DestroyClippingRegion() app = wx.App() Operations(None, -1, 'Operations') app.MainLoop() </code> {{ http://zetcode.com/wxpython/images/operations.png |Set operations on Regions}} === A wx.ListBox widget === **Speak in English, measure in Metric** The English language became the global language for communication. So did the metric system become the global system in measuremet. According to this wikipedia [[http://en.wikipedia.org/wiki/Metric_system|article]], there are only three exceptions. The USA, Liberia and Myanmar. For example, Americans use Fahrenheits to measure temperature, gallons to tank their cars or pounds to weigh loads. This might lead to some funny situations. "It is very hot today. What is the temperature?" "Let me see. It is one hundred and ,wait one hundred and ..." ?!?! "... five Fahrenheits." "Aha." //(When I was in US talking to an American)// Even though we in Europe use the metric system, there are still exceptions. The USA is dominating the IT and we are importing their standards. So we also say that we have a 17 Inch monitor. Graphics can be put into a file, displayed on the screen of a monitor or other device (cameras, videocameras, mobile phones) or printed with a printer. Paper size can be set in millimeters, points or inches, the resolution of a screen is in pixels, the quality of a text is determined by the number of dots per inch. We have also dots, bits or samples. This is one of the reasons we have **logical** and **device** units. **Logical and device units** If we draw text or geometrical primitives on the client area, we position them using logical units. {{ http://zetcode.com/wxpython/images/drawtextstring.jpg }} If we want to draw some text, we provide the text parameter and the x, y positions. x, y are in logical units. The device then draws the text in device units. Logical and device units may be the same, or they may differ. Logical units are used by people (millimeters), device units are are native to a //particular// device. For example a native device unit for a screen is pixel. The native device unit for the //HEWLETT PACKARD LaserJet 1022// is 1200 dpi. (dots per inch). So far we have talked about various measurement units. The **mapping mode** of the device is a way how to convert logical units to device units. wxPython has the following mapping modes: ^ Mapping Mode ^ Logical Unit ^ |wx.MM_TEXT | 1 pixel | |wx.MM_METRIC| 1 millimeter | |wx.MM_LOMETRIC | 1/10 of a millimeter | |wx.MM_POINTS| 1 point, 1/72 of an inch | |wx.MM_TWIPS| 1/20 of a point or 1/1440 of an inch | The default mapping mode is wx.MM_TEXT. In this mode, the logical unit is the same as the device unit. When people position object on a screen or design a web page, they think usually in pixels. Web designers create three column pages and these columns are set in pixels. The lowest common denominator for a page is often 800 px etc. This thinking is natural as we know our monitors have e.g. 1024x768 pxs. We are not going to do convertions, rather we are accustomed to think in pixels. If we want to draw a structure in millimeters, we can use the two metric mapping modes. Drawing directly in millimeters is too thick for a screen, that's why we have the wx.MM_LOMETRIC mapping mode. {{ http://zetcode.com/wxpython/images/setmapmode.jpg }} To set a different mapping mode, we use the //SetMapMode()// method. **First ruler example** The first ruler example will measure screen objects in pixels. <code python> #!/usr/bin/python # ruler1.py import wx RW = 701 # ruler widht RM = 10 # ruler margin RH = 60 # ruler height class Ruler1(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(RW + 2*RM, 60), style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP) self.font = wx.Font(7, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, 'Courier 10 Pitch') self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Centre() self.Show(True) def OnPaint(self, event): dc = wx.PaintDC(self) brush = wx.BrushFromBitmap(wx.Bitmap('granite.png')) dc.SetBrush(brush) dc.DrawRectangle(0, 0, RW+2*RM, RH) dc.SetFont(self.font) dc.SetPen(wx.Pen('#F8FF25')) dc.SetTextForeground('#F8FF25') for i in range(RW): if not (i % 100): dc.DrawLine(i+RM, 0, i+RM, 10) w, h = dc.GetTextExtent(str(i)) dc.DrawText(str(i), i+RM-w/2, 11) elif not (i % 20): dc.DrawLine(i+RM, 0, i+RM, 8) elif not (i % 2): dc.DrawLine(i+RM, 0, i+RM, 4) def OnLeftDown(self, event): pos = event.GetPosition() x, y = self.ClientToScreen(event.GetPosition()) ox, oy = self.GetPosition() dx = x - ox dy = y - oy self.delta = ((dx, dy)) def OnMouseMove(self, event): if event.Dragging() and event.LeftIsDown(): x, y = self.ClientToScreen(event.GetPosition()) fp = (x - self.delta[0], y - self.delta[1]) self.Move(fp) def OnRightDown(self, event): self.Close() app = wx.App() Ruler1(None, -1, '') app.MainLoop() </code> In this example we create a ruler. This ruler will measure screen objects in pixels. We left the default mapping mode, which is //wx.MM_TEXT//. As we have already mentioned, this mode has the same logical and device units. In our case, pixels. <code python> wx.Frame.__init__(self, parent, id, title, size=(RW + 2*RM, 60), style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP) </code> We have created a borderless window. The ruler is 721 px wide. The ruler is RW + 2*RM = 701 + 20 = 721. The ruler shows 700 numbers. 0 ... 700 is 701 pixels. A ruler has a margin on both sides, 2*10 is 20 pixels. Together it makes 721 pixels. <code python> brush = wx.BrushFromBitmap(wx.Bitmap('granite.png')) dc.SetBrush(brush) dc.DrawRectangle(0, 0, RW+2*RM, RH) </code> Here we draw a custom pattern onto the window. I have used a predefined pattern available in the GIMP. It is called granite. <code python> w, h = dc.GetTextExtent(str(i)) dc.DrawText(str(i), i+RM-w/2, 11) </code> These lines ensure, that we align the text correctly. The //GetTextExtent()// method returns the width and the height of the text. We do not have a border around our window. So we must handle moving manually by additional code. The //OnLeftDown()// and the //OnMouseMove()// methods enable us to move the ruler. (TODO:link to dragging.) {{ http://zetcode.com/wxpython/images/ruler1.jpg }} You might ask yourself, why do we need all those lines, pens, gradients? What is it good for? The following scripts will bring some practical examples. We will utilize, what we have learnt in practice. **Charts** Creating charts is an excelent example of utilizing gdi drawing functions. Charts are not GUI widgets. No gui toolkit provides charts as part of the library. One exception is wxWidgets toolkit (and so the wxPython). But these charts are very simple and cannot be used in real applications. A developer has usually two options. To create his own charting library or use a third-party library. In the following example we create a simple line chart. We do not dwell into all details. I kept the example intentionally simple. A lot of things still remain undone. But you can grasp the idea and follow it. <code python> #!/usr/bin/python # linechart.py import wx data = ((10, 9), (20, 22), (30, 21), (40, 30), (50, 41), (60, 53), (70, 45), (80, 20), (90, 19), (100, 22), (110, 42), (120, 62), (130, 43), (140, 71), (150, 89), (160, 65), (170, 126), (180, 187), (190, 128), (200, 125), (210, 150), (220, 129), (230, 133), (240, 134), (250, 165), (260, 132), (270, 130), (280, 159), (290, 163), (300, 94)) years = ('2003', '2004', '2005') class LineChart(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) self.SetBackgroundColour('WHITE') self.Bind(wx.EVT_PAINT, self.OnPaint) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetDeviceOrigin(40, 240) dc.SetAxisOrientation(True, True) dc.SetPen(wx.Pen('WHITE')) dc.DrawRectangle(1, 1, 300, 200) self.DrawAxis(dc) self.DrawGrid(dc) self.DrawTitle(dc) self.DrawData(dc) def DrawAxis(self, dc): dc.SetPen(wx.Pen('#0AB1FF')) font = dc.GetFont() font.SetPointSize(8) dc.SetFont(font) dc.DrawLine(1, 1, 300, 1) dc.DrawLine(1, 1, 1, 201) for i in range(20, 220, 20): dc.DrawText(str(i), -30, i+5) dc.DrawLine(2, i, -5, i) for i in range(100, 300, 100): dc.DrawLine(i, 2, i, -5) for i in range(3): dc.DrawText(years[i], i*100-13, -10) def DrawGrid(self, dc): dc.SetPen(wx.Pen('#d5d5d5')) for i in range(20, 220, 20): dc.DrawLine(2, i, 300, i) for i in range(100, 300, 100): dc.DrawLine(i, 2, i, 200) def DrawTitle(self, dc): font = dc.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(font) dc.DrawText('Historical Prices', 90, 235) def DrawData(self, dc): dc.SetPen(wx.Pen('#0ab1ff')) for i in range(10, 310, 10): dc.DrawSpline(data) class LineChartExample(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(390, 300)) panel = wx.Panel(self, -1) panel.SetBackgroundColour('WHITE') hbox = wx.BoxSizer(wx.HORIZONTAL) linechart = LineChart(panel) hbox.Add(linechart, 1, wx.EXPAND | wx.ALL, 15) panel.SetSizer(hbox) self.Centre() self.Show(True) app = wx.App() LineChartExample(None, -1, 'A line chart') app.MainLoop() </code> <code python> dc.SetDeviceOrigin(40, 240) dc.SetAxisOrientation(True, True) </code> By default the coordinate system in wxPython begins at point [0, 0]. The beginning point is located at the upper left corner of the clinet area. The orientation of x values is from left to right and the orientation of y values is from top to bottom. The values can be only positive. This system is used in all GUI toolkits. (All I am aware of.) For charting we use cartesian coordinate system. In cartesian system, we can have both positive and negative values. The orientation of the x values is from left to right and the orientation of y values is from bottom to top. The origin is usually in the middle. But it is not compulsory. <code python> dc.SetDeviceOrigin(40, 240) dc.SetAxisOrientation(True, True) </code> The //SetDeviceOrigin()// method moves the origin to a new point on the client area. This is called **linear translation**. Then we change the axis orientation with the //SetAxisOrientation()// method. <code python> SetAxisOrientation(bool xLeftRight, bool yBottomUp) </code> The method signature is self-explanatory. We can put true or false values to these two parameters. <code python> self.DrawAxis(dc) self.DrawGrid(dc) self.DrawTitle(dc) self.DrawData(dc) </code> We separate the construction of the chart into four methods. The first will draw axis, the second will draw the grid, the third the title and the last one will draw the data. <code python> for i in range(3): dc.DrawText(years[i], i*100-13, -10) </code> Because of the simplicity of the script, there are some magic numbers. In reality, we would have to calculate them. In the previous code example, we draw the years alongside the x axis. We subtract 13 px from the x value. This is done to center the years over the vertical lines. It works on my linux box. I might not work correctly on other platforms. It might not work even on linux boxes with different themes. You just play a bit with this example. Adjusting it to fit under the different circumstances is no rocket science. Normally, we need to calculate the width of the chart, the width of the text and center the text manually. {{ http://zetcode.com/wxpython/images/linechart.png |A line chart}} **Note** Note is a small script that shows several interesting features of the GDI. We will see, how we can create a custom shaped window. There are small applications that are used to take visible notes. They work as reminders for people, that work with computers a lot. (e.g. us). <code python> #!/usr/bin/python # note.py import wx class Note(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, style=wx.FRAME_SHAPED | wx.SIMPLE_BORDER | wx.FRAME_NO_TASKBAR) self.font = wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, 'Comic Sans MS') self.bitmap = wx.Bitmap('note.png', wx.BITMAP_TYPE_PNG) self.cross = wx.Bitmap('cross.png', wx.BITMAP_TYPE_PNG) w = self.bitmap.GetWidth() h = self.bitmap.GetHeight() self.SetClientSize((w, h)) if wx.Platform == '__WXGTK__': self.Bind(wx.EVT_WINDOW_CREATE, self.SetNoteShape) else: self.SetNoteShape() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.bitmapRegion = wx.RegionFromBitmap(self.bitmap) self.crossRegion = wx.RegionFromBitmap(self.cross) self.bitmapRegion.IntersectRegion(self.crossRegion) self.bitmapRegion.Offset(170, 10) dc = wx.ClientDC(self) dc.DrawBitmap(self.bitmap, 0, 0, True) self.PositionTopRight() self.Show(True) def PositionTopRight(self): disx, disy = wx.GetDisplaySize() x, y = self.GetSize() self.Move((disx-x, 0)) def SetNoteShape(self, *event): region = wx.RegionFromBitmap(self.bitmap) self.SetShape(region) def OnLeftDown(self, event): pos = event.GetPosition() if self.bitmapRegion.ContainsPoint(pos): self.Close() x, y = self.ClientToScreen(event.GetPosition()) ox, oy = self.GetPosition() dx = x - ox dy = y - oy self.delta = ((dx, dy)) def OnMouseMove(self, event): if event.Dragging() and event.LeftIsDown(): x, y = self.ClientToScreen(event.GetPosition()) fp = (x - self.delta[0], y - self.delta[1]) self.Move(fp) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetFont(self.font) dc.SetTextForeground('WHITE') dc.DrawBitmap(self.bitmap, 0, 0, True) dc.DrawBitmap(self.cross, 170, 10, True) dc.DrawText('- Go shopping', 20, 20) dc.DrawText('- Make a phone call', 20, 50) dc.DrawText('- Write an email', 20, 80) app = wx.App() Note(None, -1, '') app.MainLoop() </code> The idea behind creating a shaped window is simple. Most applications are rectangular. They share lots of similarities. They have menus, toolbars, titles etc. This might be boring. Some developers create more fancy applications. We can make our applications more attractive by using images. The idea is as follows. We create a frame without a border. We can draw a custom image on the frame during the paint event. <code python> wx.Frame.__init__(self, parent, id, title, style=wx.FRAME_SHAPED | wx.SIMPLE_BORDER | wx.FRAME_NO_TASKBAR) </code> In order to create a custom shaped application, we must set necessary style options. The //wx.FRAME_SHAPED// enables to create a shaped window. The //wx.SIMPLE_BORDER// removes the thick border. The //wx.FRAME_NO_TASKBAR// prevents the application from appearing on the taskbar. <code python> self.font = wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, 'Comic Sans MS') </code> I was looking for a nice font for the note example. I finally chose Comic Sans MS. This is a proprietary font. Linux users must install msttcorefonts package. If we do not have a font installed, the system chooses another one. Usually not to our taste. <code python> self.bitmap = wx.Bitmap('note.png', wx.BITMAP_TYPE_PNG) self.cross = wx.Bitmap('cross.png', wx.BITMAP_TYPE_PNG) </code> I have created two bitmaps. The first is a rounded rectangle. With a kind of an orange fill. I have used **Inkscape** vector illustrator to create it. The second one is a small cross. It is used to close the application. For this I used **Gimp** image editor. <code python> w = self.bitmap.GetWidth() h = self.bitmap.GetHeight() self.SetClientSize((w, h)) </code> We are going to draw a bitmap on the frame. I order to cover the whole frame, we figure out the bitmap size. Then we set the site of the frame to the size of the bitmap. <code python> if wx.Platform == '__WXGTK__': self.Bind(wx.EVT_WINDOW_CREATE, self.SetNoteShape) else: self.SetNoteShape() </code> This is some platform dependent code. Linux developers should call the //SetNoteShape()// method immediately after the //wx.WindowCreateEvent// event. <code python> dc = wx.ClientDC(self) dc.DrawBitmap(self.bitmap, 0, 0, True) </code> These lines are not necessary, because a paint event is generated during the creation of the application. But we believe, it makes the example smoother. I say we, because this is what I have learnt from the others. <code python> def SetNoteShape(self, *event): region = wx.RegionFromBitmap(self.bitmap) self.SetShape(region) </code> Here we set the shape of the frame to that of the bitmap. The pixels outside the image become transparent. If we remove a border from the frame, we cannot move the window. The //OnLeftDown()// and the //OnMouseMove()// methods enable the user to move the window by clicking on the client area of the frame and dragging it. <code python> dc.DrawBitmap(self.bitmap, 0, 0, True) dc.DrawBitmap(self.cross, 170, 10, True) dc.DrawText('- Go shopping', 20, 20) dc.DrawText('- Make a phone call', 20, 50) dc.DrawText('- Write an email', 20, 80) </code> Within the //OnPaint()// method we draw two bitmaps and three texts. Finally we will talk about how we close the note script. <code python> self.bitmapRegion = wx.RegionFromBitmap(self.bitmap) self.crossRegion = wx.RegionFromBitmap(self.cross) self.bitmapRegion.IntersectRegion(self.crossRegion) self.bitmapRegion.Offset(170, 10) ... pos = event.GetPosition() if self.bitmapRegion.ContainsPoint(pos): self.Close() </code> We create two regions from two bitmaps. We intersect these two regions. This way we get all pixels that share both bitmaps. Finally we move the region to the point, where we draw the cross bitmap. We use the //Offset()// method. By default the region starts at [0, 0] point. Inside the //OnLeftDown()// method we check if we clicked inside the region. If true, we close the script. {{ http://zetcode.com/wxpython/images/note.jpg |Note example}} ===== Tips and Tricks ===== In this section we will show various interesting tips in wxPython. Here we will see examples, that could not be put elsewhere. ==== The tiniest wxPython application ==== This example is just for pure fun. Feel free to contact me, if you can shorthen it. Even for one single character. Except for the path to the python interpreter. <code python> #!/usr/bin/python import wx i = wx.App() wx.Frame(None).Show() i.MainLoop() </code> ==== Interactive Button ==== This tip shows how to program an interactive Button. This button reacts to users actions. In our case, the button changes it's background colour. When we enter the area of the button widget with a mouse pointer, wx/EVT_ENTER_WINDOW event is generated. Simirarly, wx.EVT_LEAVE_WINDOW event is generated, when we leave the area of the widget. So all you have to do is to bind those events to functions, that will change the colour/shape of the button widget appropriately. <code python> #!/usr/bin/python # interactivebutton.py import wx from wx.lib.buttons import GenButton class InteractiveButton(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) panel = wx.Panel(self, -1) self.btn = GenButton(panel, -1, 'button', pos=(100, 100), size=(-1, -1)) self.btn.SetBezelWidth(1) self.btn.SetBackgroundColour( 'DARKGREY') wx.EVT_ENTER_WINDOW(self.btn, self.func) wx.EVT_LEAVE_WINDOW(self.btn, self.func1) self.Centre() self.Show(True) def func(self, event): self.btn.SetBackgroundColour( 'GREY79') self.btn.Refresh() def func1(self, event): self.btn.SetBackgroundColour( 'DARKGREY') self.btn.Refresh() app = wx.App() InteractiveButton(None, -1, 'interactivebutton.py') app.MainLoop() </code> I have used a GenButton instead of a basic wx.Button. A GenButton can change border settigs, which I find attractive. But wx.Button would work as well. ==== Isabelle ==== When an error occurs in an application, an error dialog usually appears. This might get annoying. I have noticed a better solution in a SAP system. When a user enters an invalid command, statusbar turs red and an error message is displayed on stausbar. The red colour catches the eye and the user can easily read the error message. The following code mimics this situation. {{ http://zetcode.com/wxpython/images/isabelle.png }} <code python> #!/usr/bin/python # Isabelle import wx ID_TIMER = 1 ID_EXIT = 2 ID_ABOUT = 3 ID_BUTTON = 4 class Isabelle(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) self.timer = wx.Timer(self, ID_TIMER) self.blick = 0 file = wx.Menu() file.Append(ID_EXIT, '&Quit\tCtrl+Q', 'Quit Isabelle') help = wx.Menu() help.Append(ID_ABOUT, '&About', 'O Programe') menubar = wx.MenuBar() menubar.Append(file, '&File') menubar.Append(help, '&Help') self.SetMenuBar(menubar) toolbar = wx.ToolBar(self, -1) self.tc = wx.TextCtrl(toolbar, -1, size=(100, -1)) btn = wx.Button(toolbar, ID_BUTTON, 'Ok', size=(40, 28)) toolbar.AddControl(self.tc) toolbar.AddSeparator() toolbar.AddControl(btn) toolbar.Realize() self.SetToolBar(toolbar) self.Bind(wx.EVT_BUTTON, self.OnLaunchCommandOk, id=ID_BUTTON) self.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT) self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) self.Bind(wx.EVT_TIMER, self.OnTimer, id=ID_TIMER) self.panel = wx.Panel(self, -1, (0, 0), (500 , 300)) self.panel.SetBackgroundColour('GRAY') self.sizer=wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.panel, 1, wx.EXPAND) self.SetSizer(self.sizer) self.statusbar = self.CreateStatusBar() self.statusbar.SetStatusText('Welcome to Isabelle') self.Centre() def OnExit(self, event): dlg = wx.MessageDialog(self, 'Are you sure to quit Isabelle?', 'Please Confirm', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: self.Close(True) def OnAbout(self, event): dlg = wx.MessageDialog(self, 'Isabelle\t\n' '2004\t', 'About', wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def OnLaunchCommandOk(self, event): input = self.tc.GetValue() if input == '/bye': self.OnExit(self) elif input == '/about': self.OnAbout(self) elif input == '/bell': wx.Bell() else: self.statusbar.SetBackgroundColour('RED') self.statusbar.SetStatusText('Unknown Command') self.statusbar.Refresh() self.timer.Start(50) self.tc.Clear() def OnTimer(self, event): self.blick = self.blick + 1 if self.blick == 25: self.statusbar.SetBackgroundColour('#E0E2EB') self.statusbar.Refresh() self.timer.Stop() self.blick = 0 app = wx.App() Isabelle(None, -1, 'Isabelle') app.MainLoop() </code> There is a wx.TextCtrl on the Statusbar. There you enter your commands. We have defined three commands. /bye, /about and /beep. If you mistype any of them, Statusbar turns red and displays an error. This is done with the wx.Timer class. ==== Undo/Redo framework ==== Many applications have the ability to undo and redo the user's actions. The following example shows how it can be accomplished in wxPython. {{ http://zetcode.com/wxpython/images/undoredo.jpg }} <code python> #!/usr/bin/python # undoredo.py from wx.lib.sheet import * import wx stockUndo = [] stockRedo = [] ID_QUIT = 10 ID_UNDO = 11 ID_REDO = 12 ID_EXIT = 13 ID_COLSIZE = 80 ID_ROWSIZE = 20 class UndoText: def __init__(self, sheet, text1, text2, row, column): self.RedoText = text2 self.row = row self.col = column self.UndoText = text1 self.sheet = sheet def undo(self): self.RedoText = self.sheet.GetCellValue(self.row, self.col) if self.UndoText == None: self.sheetSetCellValue('') else: self.sheet.SetCellValue(self.row, self.col, self.UndoText) def redo(self): if self.RedoText == None: self.sheet.SetCellValue('') else: self.sheet.SetCellValue(self.row, self.col, self.RedoText) class UndoColSize: def __init__(self, sheet, position, size): self.sheet = sheet self.pos = position self.RedoSize = size self.UndoSize = ID_COLSIZE def undo(self): self.RedoSize = self.sheet.GetColSize(self.pos) self.sheet.SetColSize(self.pos, self.UndoSize) self.sheet.ForceRefresh() def redo(self): self.UndoSize = ID_COLSIZE self.sheet.SetColSize(self.pos, self.RedoSize) self.sheet.ForceRefresh() class UndoRowSize: def __init__(self, sheet, position, size): self.sheet = sheet self.pos = position self.RedoSize = size self.UndoSize = ID_ROWSIZE def undo(self): self.RedoSize = self.sheet.GetRowSize(self.pos) self.sheet.SetRowSize(self.pos, self.UndoSize) self.sheet.ForceRefresh() def redo(self): self.UndoSize = ID_ROWSIZE self.sheet.SetRowSize(self.pos, self.RedoSize) self.sheet.ForceRefresh() class MySheet(CSheet): instance = 0 def __init__(self, parent): CSheet.__init__(self, parent) self.SetRowLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.text = '' def OnCellChange(self, event): toolbar = self.GetParent().toolbar if (toolbar.GetToolEnabled(ID_UNDO) == False): toolbar.EnableTool(ID_UNDO, True) r = event.GetRow() c = event.GetCol() text = self.GetCellValue(r, c) # self.text - text before change # text - text after change undo = UndoText(self, self.text, text, r, c) stockUndo.append(undo) if stockRedo: # this might be surprising, but it is a standard behaviour # in all spreadsheets del stockRedo[:] toolbar.EnableTool(ID_REDO, False) def OnColSize(self, event): toolbar = self.GetParent().toolbar if (toolbar.GetToolEnabled(ID_UNDO) == False): toolbar.EnableTool(ID_UNDO, True) pos = event.GetRowOrCol() size = self.GetColSize(pos) undo = UndoColSize(self, pos, size) stockUndo.append(undo) if stockRedo: del stockRedo[:] toolbar.EnableTool(ID_REDO, False) def OnRowSize(self, event): toolbar = self.GetParent().toolbar if (toolbar.GetToolEnabled(ID_UNDO) == False): toolbar.EnableTool(ID_UNDO, True) pos = event.GetRowOrCol() size = self.GetRowSize(pos) undo = UndoRowSize(self, pos, size) stockUndo.append(undo) if stockRedo: del stockRedo[:] toolbar.EnableTool(ID_REDO, False) class Newt(wx.Frame): def __init__(self,parent,id,title): wx.Frame.__init__(self, parent, -1, title, size=(550, 500)) box = wx.BoxSizer(wx.VERTICAL) menuBar = wx.MenuBar() menu = wx.Menu() quit = wx.MenuItem(menu, ID_QUIT, '&Quit\tCtrl+Q', 'Quits Newt') quit.SetBitmap(wx.Bitmap('icons/exit16.png')) menu.AppendItem(quit) menuBar.Append(menu, '&File') self.Bind(wx.EVT_MENU, self.OnQuitNewt, id=ID_QUIT) self.SetMenuBar(menuBar) self.toolbar = wx.ToolBar(self, id=-1, style=wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT) self.toolbar.AddSimpleTool(ID_UNDO, wx.Bitmap('icons/undo.png'), 'Undo', '') self.toolbar.AddSimpleTool(ID_REDO, wx.Bitmap('icons/redo.png'), 'Redo', '') self.toolbar.EnableTool(ID_UNDO, False) self.toolbar.EnableTool(ID_REDO, False) self.toolbar.AddSeparator() self.toolbar.AddSimpleTool(ID_EXIT, wx.Bitmap('icons/exit.png'), 'Quit', '') self.toolbar.Realize() self.toolbar.Bind(wx.EVT_TOOL, self.OnUndo, id=ID_UNDO) self.toolbar.Bind(wx.EVT_TOOL, self.OnRedo, id=ID_REDO) self.toolbar.Bind(wx.EVT_TOOL, self.OnQuitNewt, id=ID_EXIT) box.Add(self.toolbar, border=5) box.Add((5,10), 0) self.SetSizer(box) self.sheet1 = MySheet(self) self.sheet1.SetNumberRows(55) self.sheet1.SetNumberCols(25) for i in range(self.sheet1.GetNumberRows()): self.sheet1.SetRowSize(i, ID_ROWSIZE) self.sheet1.SetFocus() box.Add(self.sheet1, 1, wx.EXPAND) self.CreateStatusBar() self.Centre() self.Show(True) def OnUndo(self, event): if len(stockUndo) == 0: return a = stockUndo.pop() if len(stockUndo) == 0: self.toolbar.EnableTool(ID_UNDO, False) a.undo() stockRedo.append(a) self.toolbar.EnableTool(ID_REDO, True) def OnRedo(self, event): if len(stockRedo) == 0: return a = stockRedo.pop() if len(stockRedo) == 0: self.toolbar.EnableTool(ID_REDO, False) a.redo() stockUndo.append(a) self.toolbar.EnableTool(ID_UNDO, True) def OnQuitNewt(self, event): self.Close(True) app = wx.App() Newt(None, -1, 'Newt') app.MainLoop() </code> <code python> stockUndo = [] stockRedo = [] </code> There are two list objects. stockUndo is a list that holds all changes, that we can undo. stockRedo keeps all changes, that can be redone. The changes are instantiated into a UndoText object. This object has two methods. undo and redo. <code python> class MySheet(CSheet): def __init__(self, parent): CSheet.__init__(self, parent) </code> Our example inherits from CSheet class. It is a grid widget with some additional logic. <code python> self.SetRowLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) </code> Here we center the labels in rows. By default, they are aligned to the right. <code python> r = event.GetRow() c = event.GetCol() text = self.GetCellValue(r, c) # self.text - text before change # text - text after change undo = UndoText(self, self.text, text, r, c) stockUndo.append(undo) </code> Every time we do some changes, an UndoText object is created and appended to the stockUndo list.. <code python> if stockRedo: # this might be surprising, but it is a standard behaviour # in all spreadsheets del stockRedo[:] toolbar.EnableTool(ID_REDO, False) </code> Yes, this behaviour was surprising for me. I did not know that it works this way, until I made this example. Basically, if you undo some changes and then start typing again, all redo changes are lost. OpenOffice Calc works this way. Gnumeric as well. <code python> if len(stockUndo) == 0: self.toolbar.EnableTool(ID_UNDO, False) ... self.toolbar.EnableTool(ID_REDO, True) </code> The undo and redo buttons are enabled or disabled accordingly. If there is nothing to undo, the undo button is disabled. <code python> a = stockUndo.pop() if len(stockUndo) == 0: self.toolbar.EnableTool(ID_UNDO, False) a.undo() stockRedo.append(a) </code> If we click undo, we pop up an UndoText object from the stockUndo list. Call the //undo()// method and append the object to the stockRedo list. ==== Configuring application settings ==== Many applications allow users to configure their settings. Users can toggle tooltips on and of, change fonts, default download paths etc. Mostly they have a menu option called preferences. Application settings are saved to the hard disk, so that users do not have to change the settings each time the application starts. In wxPython we have //wx.Config// class to do our job. On Linux, settings are stored in a simple hidden file. This file is located in the home user directory by default. The location of the configuration file can be changed. The name of the file is specified in the constructor of the //wx.Config// class. In the following code example, we can cofigure the size of the window. If there is no configuration file, the height and the width of the window is set to the defaul 250 px value. We can set these values to a range from 200 - 500px. After we save our values and restart the application, the window size is set to our preffered values. <code python> #!/usr/bin/python # myconfig.py import wx class MyConfig(wx.Frame): def __init__(self, parent, id, title): self.cfg = wx.Config('myconfig') if self.cfg.Exists('width'): w, h = self.cfg.ReadInt('width'), self.cfg.ReadInt('height') else: (w, h) = (250, 250) wx.Frame.__init__(self, parent, id, title, size=(w, h)) wx.StaticText(self, -1, 'Width:', (20, 20)) wx.StaticText(self, -1, 'Height:', (20, 70)) self.sc1 = wx.SpinCtrl(self, -1, str(w), (80, 15), (60, -1), min=200, max=500) self.sc2 = wx.SpinCtrl(self, -1, str(h), (80, 65), (60, -1), min=200, max=500) wx.Button(self, 1, 'Save', (20, 120)) self.Bind(wx.EVT_BUTTON, self.OnSave, id=1) self.statusbar = self.CreateStatusBar() self.Centre() self.Show(True) def OnSave(self, event): self.cfg.WriteInt("width", self.sc1.GetValue()) self.cfg.WriteInt("height", self.sc2.GetValue()) self.statusbar.SetStatusText('Configuration saved, %s ' % wx.Now()) app = wx.App() MyConfig(None, -1, 'myconfig.py') app.MainLoop() </code> Here we have the contents of a configuration file to our code example . It consists of two key, value pairs. <code python> $ cat .myconfig height=230 width=350 </code> {{ http://zetcode.com/wxpython/images/myconfig.png }} ==== Mouse gestures ==== A mouse gesture is a way of combining computer mouse movements and clicks which the software recognizes as a specific command. We can find mouse gestures in such successfull applications like Firefox or Opera. They really help users save their time while browsing on the Interent. Mouse gestures are created with wx.lib.gestures.MouseGestures class in wxPython. Available gestures: * L for left * R for right * U for up * D for down * 7 for northwest * 9 for northeast * 1 for southwest * 3 for southeast If you wonder why these numbers were chosen, have a look at the numerical pad. Mouse gestures can be combined. This way 'RDLU' is a mouse gesture triggered, when we do a square with a mouse pointer. Possible flags are: * wx.MOUSE_BTN_LEFT * wx.MOUSE_BTN_MIDDLE * wx.MOUSE_BTN_RIGHT <code python> #!/usr/bin/python # mousegestures.py import wx import wx.lib.gestures as gest class MyMouseGestures(wx.Frame): def __init__ (self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(300, 200)) panel = wx.Panel(self, -1) mg = gest.MouseGestures(panel, True, wx.MOUSE_BTN_LEFT) mg.SetGesturePen(wx.Colour(255, 0, 0), 2) mg.SetGesturesVisible(True) mg.AddGesture('DR', self.OnDownRight) self.Centre() self.Show(True) def OnDownRight(self): self.Close() app = wx.App() MyMouseGestures(None, -1, 'mousegestures.py') app.MainLoop() </code> In our example, we have registered a mouse gesture for a panel. Mouse gesture is triggered, when a left button is pressed and we go down and right with a cursor. As in letter 'L'. Our mouse gesture will close the application. <code python> mg = gest.MouseGestures(panel, True, wx.MOUSE_BTN_LEFT) </code> If we want to use mouse gestures, we have to create a MouseGesture object. The first parameter is a window, where the mouse gesture is registered. Second parameter defines a way to register a gesture. True is for automatic, False for manual. Manual is not fully implemented and we are happy with the automatic way. Last parameter defines a mouse button, which will be pressed when triggering gestures. The button can be later changed with the SetMouseButton() method. <code python> mg.SetGesturePen(wx.Colour(255, 0, 0), 2) </code> Our gestures will be painted as red lines. They will be 2 pixels wide. <code python> mg.SetGesturesVisible(True) </code> We set this gesture visible with the SetGesturesVisible() method. <code python> mg.AddGesture('DR', self.OnDownRight) </code> We register a mouse gesture with the AddGesture() method. The first parameter is the gesture. Second parameter is the method triggered by the gesture. </html> ===== wxPython Gripts ===== In this section we will show some small, complete scripts. These graphical scripts or "gripts" will demonstrate various areas in programming. Programming in Python, wxPython is easier than in most other toolkits. But it is still a laborious task. There is a long, long way from easy scripts to professional applications. ==== Tom ==== Each application should have a good name. Short and easily remembered. So, we have Tom. A simple gript that sends an email. {{ http://zetcode.com/wxpython/images/tom.png }} <code python> #!/usr/bin/python # Tom import wx import smtplib class Tom(wx.Dialog): def __init__(self, parent, id, title): wx.Dialog.__init__(self, parent, id, title, size=(400, 420)) panel = wx.Panel(self, -1) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) hbox3 = wx.BoxSizer(wx.HORIZONTAL) st1 = wx.StaticText(panel, -1, 'From') st2 = wx.StaticText(panel, -1, 'To ') st3 = wx.StaticText(panel, -1, 'Subject') self.tc1 = wx.TextCtrl(panel, -1, size=(180, -1)) self.tc2 = wx.TextCtrl(panel, -1, size=(180, -1)) self.tc3 = wx.TextCtrl(panel, -1, size=(180, -1)) self.write = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE) button_send = wx.Button(panel, 1, 'Send') hbox1.Add(st1, 0, wx.LEFT, 10) hbox1.Add(self.tc1, 0, wx.LEFT, 35) hbox2.Add(st2, 0, wx.LEFT, 10) hbox2.Add(self.tc2, 0, wx.LEFT, 50) hbox3.Add(st3, 0, wx.LEFT, 10) hbox3.Add(self.tc3, 0, wx.LEFT, 20) vbox.Add(hbox1, 0, wx.TOP, 10) vbox.Add(hbox2, 0, wx.TOP, 10) vbox.Add(hbox3, 0, wx.TOP, 10) vbox.Add(self.write, 1, wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, 15) vbox.Add(button_send, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 20) self.Bind(wx.EVT_BUTTON, self.OnSend, id=1) panel.SetSizer(vbox) self.Centre() self.ShowModal() self.Destroy() def OnSend(self, event): sender = self.tc1.GetValue() recipient = self.tc2.GetValue() subject = self.tc3.GetValue() text = self.write.GetValue() header = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (sender, recipient, subject) message = header + text try: server = smtplib.SMTP('mail.chello.sk') server.sendmail(sender, recipient, message) server.quit() dlg = wx.MessageDialog(self, 'Email was successfully sent', 'Success', wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() except smtplib.SMTPException, error: dlg = wx.MessageDialog(self, 'Failed to send email', 'Error', wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() app = wx.App() Tom(None, -1, 'Tom') app.MainLoop() </code> For working with emails we need to import smtp module. This module is part of the python language. <code python> import smtplib </code> From, To and Subject options must be separated by carriedge return and newline as shown here. This weird thing is requested by RFC 821 norm. So we must follow it. <code python> header = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (sender, recipient, subject) </code> Next we create an SMTP connection. Here you specify your settings. Each ISP gives you the name of the pop and smtp servers. In my case, 'mail.chello.sk' is a name for both. A mail is sent by calling the sendmail() method. Finally, we quit the connection with the quit() method. <code python> server = smtplib.SMTP('mail.chello.sk') server.sendmail(sender, recipient, message) server.quit() </code> ==== Editor ==== This editor example is the largest so far. {{ http://zetcode.com/wxpython/images/editor.png }} <code python> #!/usr/bin/python # Editor import wx import os class Editor(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(600, 500)) # variables self.modify = False self.last_name_saved = '' self.replace = False # setting up menubar menubar = wx.MenuBar() file = wx.Menu() new = wx.MenuItem(file, 101, '&New\tCtrl+N', 'Creates a new document') new.SetBitmap(wx.Bitmap('icons/stock_new-16.png')) file.AppendItem(new) open = wx.MenuItem(file, 102, '&Open\tCtrl+O', 'Open an existing file') open.SetBitmap(wx.Bitmap('icons/stock_open-16.png')) file.AppendItem(open) file.AppendSeparator() save = wx.MenuItem(file, 103, '&Save\tCtrl+S', 'Save the file') save.SetBitmap(wx.Bitmap('icons/stock_save-16.png')) file.AppendItem(save) saveas = wx.MenuItem(file, 104, 'Save &As...\tShift+Ctrl+S', 'Save the file with a different name') saveas.SetBitmap(wx.Bitmap('icons/stock_save_as-16.png')) file.AppendItem(saveas) file.AppendSeparator() quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application') quit.SetBitmap(wx.Bitmap('icons/stock_exit-16.png')) file.AppendItem(quit) edit = wx.Menu() cut = wx.MenuItem(edit, 106, '&Cut\tCtrl+X', 'Cut the Selection') cut.SetBitmap(wx.Bitmap('icons/stock_cut-16.png')) edit.AppendItem(cut) copy = wx.MenuItem(edit, 107, '&Copy\tCtrl+C', 'Copy the Selection') copy.SetBitmap(wx.Bitmap('icons/stock_copy-16.png')) edit.AppendItem(copy) paste = wx.MenuItem(edit, 108, '&Paste\tCtrl+V', 'Paste text from clipboard') paste.SetBitmap(wx.Bitmap('icons/stock_paste-16.png')) edit.AppendItem(paste) delete = wx.MenuItem(edit, 109, '&Delete', 'Delete the selected text') delete.SetBitmap(wx.Bitmap('icons/stock_delete-16.png',)) edit.AppendItem(delete) edit.AppendSeparator() edit.Append(110, 'Select &All\tCtrl+A', 'Select the entire text') view = wx.Menu() view.Append(111, '&Statusbar', 'Show StatusBar') help = wx.Menu() about = wx.MenuItem(help, 112, '&About\tF1', 'About Editor') about.SetBitmap(wx.Bitmap('icons/stock_about-16.png')) help.AppendItem(about) menubar.Append(file, '&File') menubar.Append(edit, '&Edit') menubar.Append(view, '&View') menubar.Append(help, '&Help') self.SetMenuBar(menubar) self.Bind(wx.EVT_MENU, self.NewApplication, id=101) self.Bind(wx.EVT_MENU, self.OnOpenFile, id=102) self.Bind(wx.EVT_MENU, self.OnSaveFile, id=103) self.Bind(wx.EVT_MENU, self.OnSaveAsFile, id=104) self.Bind(wx.EVT_MENU, self.QuitApplication, id=105) self.Bind(wx.EVT_MENU, self.OnCut, id=106) self.Bind(wx.EVT_MENU, self.OnCopy, id=107) self.Bind(wx.EVT_MENU, self.OnPaste, id=108) self.Bind(wx.EVT_MENU, self.OnDelete, id=109) self.Bind(wx.EVT_MENU, self.OnSelectAll, id=110) self.Bind(wx.EVT_MENU, self.ToggleStatusBar, id=111) self.Bind(wx.EVT_MENU, self.OnAbout, id=112) # setting up toolbar self.toolbar = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT ) self.toolbar.AddSimpleTool(801, wx.Bitmap('icons/stock_new.png'), 'New', '') self.toolbar.AddSimpleTool(802, wx.Bitmap('icons/stock_open.png'), 'Open', '') self.toolbar.AddSimpleTool(803, wx.Bitmap('icons/stock_save.png'), 'Save', '') self.toolbar.AddSeparator() self.toolbar.AddSimpleTool(804, wx.Bitmap('icons/stock_cut.png'), 'Cut', '') self.toolbar.AddSimpleTool(805, wx.Bitmap('icons/stock_copy.png'), 'Copy', '') self.toolbar.AddSimpleTool(806, wx.Bitmap('icons/stock_paste.png'), 'Paste', '') self.toolbar.AddSeparator() self.toolbar.AddSimpleTool(807, wx.Bitmap('icons/stock_exit.png'), 'Exit', '') self.toolbar.Realize() self.Bind(wx.EVT_TOOL, self.NewApplication, id=801) self.Bind(wx.EVT_TOOL, self.OnOpenFile, id=802) self.Bind(wx.EVT_TOOL, self.OnSaveFile, id=803) self.Bind(wx.EVT_TOOL, self.OnCut, id=804) self.Bind(wx.EVT_TOOL, self.OnCopy, id=805) self.Bind(wx.EVT_TOOL, self.OnPaste, id=806) self.Bind(wx.EVT_TOOL, self.QuitApplication, id=807) self.text = wx.TextCtrl(self, 1000, '', size=(-1, -1), style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER) self.text.SetFocus() self.text.Bind(wx.EVT_TEXT, self.OnTextChanged, id=1000) self.text.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wx.EVT_CLOSE, self.QuitApplication) self.StatusBar() self.Centre() self.Show(True) def NewApplication(self, event): editor = Editor(None, -1, 'Editor') editor.Centre() editor.Show() def OnOpenFile(self, event): file_name = os.path.basename(self.last_name_saved) if self.modify: dlg = wx.MessageDialog(self, 'Save changes?', '', wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION) val = dlg.ShowModal() if val == wx.ID_YES: self.OnSaveFile(event) self.DoOpenFile() elif val == wx.ID_CANCEL: dlg.Destroy() else: self.DoOpenFile() else: self.DoOpenFile() def DoOpenFile(self): wcd = 'All files (*)|*|Editor files (*.ef)|*.ef|' dir = os.getcwd() open_dlg = wx.FileDialog(self, message='Choose a file', defaultDir=dir, defaultFile='', wildcard=wcd, style=wx.OPEN|wx.CHANGE_DIR) if open_dlg.ShowModal() == wx.ID_OK: path = open_dlg.GetPath() try: file = open(path, 'r') text = file.read() file.close() if self.text.GetLastPosition(): self.text.Clear() self.text.WriteText(text) self.last_name_saved = path self.statusbar.SetStatusText('', 1) self.modify = False except IOError, error: dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error)) dlg.ShowModal() except UnicodeDecodeError, error: dlg = wx.MessageDialog(self, 'Error opening file\n' + str(error)) dlg.ShowModal() open_dlg.Destroy() def OnSaveFile(self, event): if self.last_name_saved: try: file = open(self.last_name_saved, 'w') text = self.text.GetValue() file.write(text) file.close() self.statusbar.SetStatusText(os.path.basename(self.last_name_saved) + ' saved', 0) self.modify = False self.statusbar.SetStatusText('', 1) except IOError, error: dlg = wx.MessageDialog(self, 'Error saving file\n' + str(error)) dlg.ShowModal() else: self.OnSaveAsFile(event) def OnSaveAsFile(self, event): wcd='All files(*)|*|Editor files (*.ef)|*.ef|' dir = os.getcwd() save_dlg = wx.FileDialog(self, message='Save file as...', defaultDir=dir, defaultFile='', wildcard=wcd, style=wx.SAVE | wx.OVERWRITE_PROMPT) if save_dlg.ShowModal() == wx.ID_OK: path = save_dlg.GetPath() try: file = open(path, 'w') text = self.text.GetValue() file.write(text) file.close() self.last_name_saved = os.path.basename(path) self.statusbar.SetStatusText(self.last_name_saved + ' saved', 0) self.modify = False self.statusbar.SetStatusText('', 1) except IOError, error: dlg = wx.MessageDialog(self, 'Error saving file\n' + str(error)) dlg.ShowModal() save_dlg.Destroy() def OnCut(self, event): self.text.Cut() def OnCopy(self, event): self.text.Copy() def OnPaste(self, event): self.text.Paste() def QuitApplication(self, event): if self.modify: dlg = wx.MessageDialog(self, 'Save before Exit?', '', wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION) val = dlg.ShowModal() if val == wx.ID_YES: self.OnSaveFile(event) if not self.modify: wx.Exit() elif val == wx.ID_CANCEL: dlg.Destroy() else: self.Destroy() else: self.Destroy() def OnDelete(self, event): frm, to = self.text.GetSelection() self.text.Remove(frm, to) def OnSelectAll(self, event): self.text.SelectAll() def OnTextChanged(self, event): self.modify = True self.statusbar.SetStatusText(' modified', 1) event.Skip() def OnKeyDown(self, event): keycode = event.GetKeyCode() if keycode == wx.WXK_INSERT: if not self.replace: self.statusbar.SetStatusText('INS', 2) self.replace = True else: self.statusbar.SetStatusText('', 2) self.replace = False event.Skip() def ToggleStatusBar(self, event): if self.statusbar.IsShown(): self.statusbar.Hide() else: self.statusbar.Show() def StatusBar(self): self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount(3) self.statusbar.SetStatusWidths([-5, -2, -1]) def OnAbout(self, event): dlg = wx.MessageDialog(self, '\tEditor\t\n Another Tutorial\njan bodnar 2005-2006', 'About Editor', wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() app = wx.App() Editor(None, -1, 'Editor') app.MainLoop() </code> ==== Kika ==== Kika is a gript that connects to an ftp site. If a login is successfull, Kika shows a connected icon on the statusbar. Otherwise, a disconnected icon is displayed. We use an ftplib module from the python standard library. If you do not have an ftp account, you can try to login to some anonymous ftp sites. {{ http://zetcode.com/wxpython/images/kika.jpg }} <code python> #!/usr/bin/python # kika.py from ftplib import FTP, all_errors import wx class MyStatusBar(wx.StatusBar): def __init__(self, parent): wx.StatusBar.__init__(self, parent) self.SetFieldsCount(2) self.SetStatusText('Welcome to Kika', 0) self.SetStatusWidths([-5, -2]) self.icon = wx.StaticBitmap(self, -1, wx.Bitmap('icons/disconnected.png')) self.Bind(wx.EVT_SIZE, self.OnSize) self.PlaceIcon() def PlaceIcon(self): rect = self.GetFieldRect(1) self.icon.SetPosition((rect.x+3, rect.y+3)) def OnSize(self, event): self.PlaceIcon() class Kika(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(250, 270)) wx.StaticText(self, -1, 'Ftp site', (10, 20)) wx.StaticText(self, -1, 'Login', (10, 60)) wx.StaticText(self, -1, 'Password', (10, 100)) self.ftpsite = wx.TextCtrl(self, -1, '', (110, 15), (120, -1)) self.login = wx.TextCtrl(self, -1, '', (110, 55), (120, -1)) self.password = wx.TextCtrl(self, -1, '', (110, 95), (120, -1), style=wx.TE_PASSWORD) self.ftp = None con = wx.Button(self, 1, 'Connect', (10, 160)) discon = wx.Button(self, 2, 'DisConnect', (120, 160)) self.Bind(wx.EVT_BUTTON, self.OnConnect, id=1) self.Bind(wx.EVT_BUTTON, self.OnDisConnect, id=2) self.statusbar = MyStatusBar(self) self.SetStatusBar(self.statusbar) self.Centre() self.Show() def OnConnect(self, event): if not self.ftp: ftpsite = self.ftpsite.GetValue() login = self.login.GetValue() password = self.password.GetValue() try: self.ftp = FTP(ftpsite) var = self.ftp.login(login, password) self.statusbar.SetStatusText('User connected') self.statusbar.icon.SetBitmap(wx.Bitmap('icons/connected.png')) except AttributeError: self.statusbar.SetForegroundColour(wx.RED) self.statusbar.SetStatusText('Incorrect params') self.ftp = None except all_errors, err: self.statusbar.SetStatusText(str(err)) self.ftp = None def OnDisConnect(self, event): if self.ftp: self.ftp.quit() self.ftp = None self.statusbar.SetStatusText('User disconnected') self.statusbar.icon.SetBitmap(wx.Bitmap('icons/disconnected.png')) app = wx.App() Kika(None, -1, 'Kika') app.MainLoop() </code> Notice that each time the window is resized, we must position our icon to a new place. <code python> def PlaceIcon(self): rect = self.GetFieldRect(1) self.icon.SetPosition((rect.x+3, rect.y+3)) </code> ==== Puzzle ==== In this gript, we introduce a puzzle game. We have an image of a Sid character from the Ice Age movie. It is cut into 9 pieces and shuffled. The goal is to form the picture. {{ http://zetcode.com/wxpython/images/puzzle.jpg }} <code python> #!/usr/bin/python # puzzle.py import wx import random class Puzzle(wx.Dialog): def __init__(self, parent, id, title): wx.Dialog.__init__(self, parent, id, title) images = ['images/one.jpg', 'images/two.jpg', 'images/three.jpg', 'images/four.jpg', 'images/five.jpg', 'images/six.jpg', 'images/seven.jpg', 'images/eight.jpg'] self.pos = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ] self.sizer = wx.GridSizer(3, 3, 0, 0) numbers = [0, 1, 2, 3, 4, 5, 6, 7] random.shuffle(numbers) for i in numbers: button = wx.BitmapButton(self, i, wx.Bitmap(images[i])) button.Bind(wx.EVT_BUTTON, self.OnPressButton, id=button.GetId()) self.sizer.Add(button) self.panel = wx.Button(self, -1, size=(112, 82)) self.sizer.Add(self.panel) self.SetSizerAndFit(self.sizer) self.Centre() self.ShowModal() self.Destroy() def OnPressButton(self, event): button = event.GetEventObject() sizeX = self.panel.GetSize().x sizeY = self.panel.GetSize().y buttonX = button.GetPosition().x buttonY = button.GetPosition().y panelX = self.panel.GetPosition().x panelY = self.panel.GetPosition().y buttonPosX = buttonX / sizeX buttonPosY = buttonY / sizeY buttonIndex = self.pos[buttonPosY][buttonPosX] if (buttonX == panelX) and (panelY - buttonY) == sizeY: self.sizer.Remove(self.panel) self.sizer.Remove(button) self.sizer.Insert(buttonIndex, self.panel) self.sizer.Insert(buttonIndex+3, button) self.sizer.Layout() if (buttonX == panelX) and (panelY - buttonY) == -sizeY: self.sizer.Remove(self.panel) self.sizer.Remove(button) self.sizer.Insert(buttonIndex-3, button) self.sizer.Insert(buttonIndex, self.panel) self.sizer.Layout() if (buttonY == panelY) and (panelX - buttonX) == sizeX: self.sizer.Remove(self.panel) self.sizer.Remove(button) self.sizer.Insert(buttonIndex, self.panel) self.sizer.Insert(buttonIndex+1, button) self.sizer.Layout() if (buttonY == panelY) and (panelX - buttonX) == -sizeX: self.sizer.Remove(self.panel) self.sizer.Remove(button) self.sizer.Insert(buttonIndex-1, button) self.sizer.Insert(buttonIndex, self.panel) self.sizer.Layout() app = wx.App() Puzzle(None, -1, 'Puzzle') app.MainLoop() </code> <code python> images = ['images/one.jpg', 'images/two.jpg', 'images/three.jpg', 'images/four.jpg', 'images/five.jpg', 'images/six.jpg', 'images/seven.jpg', 'images/eight.jpg'] </code> The picture was cut into 9 parts of 100x70 size. I did it with the Gimp program. Each part of the picture is placed on one button widget. Except one. <code python> self.sizer = wx.GridSizer(3, 3, 0, 0) </code> For this gript, //wx.GridSizer// fits ideally. <code python> numbers = [0, 1, 2, 3, 4, 5, 6, 7] random.shuffle(numbers) </code> We have eight numbers. Those numbers are shuffled so that we have a random number order. Each time we start the gript, we will have a different order of bitmaps. <code python> self.panel = wx.Button(self, -1, size=(112, 82)) self.sizer.Add(self.panel) </code> This button has no bitmap. It is the 'travelling' button. It always exchanges it's position with the hitted button. </html> ===== The Tetris game in wxPython ===== ==== Tetris ==== The tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a russian programmer **Alexey Pajitnov** in 1985. Since then, tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the tetris game. Tetris is called a falling block puzzle game. In this game, we have seven different shapes called **tetrominoes**. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the tetris game is to move and rotate the shapes, so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the tetris game until we top out. {{ http://zetcode.com/wxpython/images/tetrominoes.png |Tetrominoes}} wxPython is a toolkit designed to create applications. There are other libraries which are targeted at creating computer games. Nevertheless, wxPython and other application toolkits can be used to create games. ==== The development ==== We do not have images for our tetris game, we draw the tetrominoes using the drawing API available in the wxPython programming toolkit. Behind every computer game, there is a mathematical model. So it is in tetris. Some ideas behind the game. * We use **wx.Timer** to create a game cycle * The tetrominoes are drawn * The shapes move on a square by square basis (not pixel by pixel) * Mathematically a board is a simple list of numbers The following example is a modified version of the tetris game, available with PyQt4 installation files. <code python> #!/usr/bin/python # tetris.py import wx import random class Tetris(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title, size=(180, 380)) self.statusbar = self.CreateStatusBar() self.statusbar.SetStatusText('0') self.board = Board(self) self.board.SetFocus() self.board.start() self.Centre() self.Show(True) class Board(wx.Panel): BoardWidth = 10 BoardHeight = 22 Speed = 300 ID_TIMER = 1 def __init__(self, parent): wx.Panel.__init__(self, parent) self.timer = wx.Timer(self, Board.ID_TIMER) self.isWaitingAfterLine = False self.curPiece = Shape() self.nextPiece = Shape() self.curX = 0 self.curY = 0 self.numLinesRemoved = 0 self.board = [] self.isStarted = False self.isPaused = False self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wx.EVT_TIMER, self.OnTimer, id=Board.ID_TIMER) self.clearBoard() def shapeAt(self, x, y): return self.board[(y * Board.BoardWidth) + x] def setShapeAt(self, x, y, shape): self.board[(y * Board.BoardWidth) + x] = shape def squareWidth(self): return self.GetClientSize().GetWidth() / Board.BoardWidth def squareHeight(self): return self.GetClientSize().GetHeight() / Board.BoardHeight def start(self): if self.isPaused: return self.isStarted = True self.isWaitingAfterLine = False self.numLinesRemoved = 0 self.clearBoard() self.newPiece() self.timer.Start(Board.Speed) def pause(self): if not self.isStarted: return self.isPaused = not self.isPaused statusbar = self.GetParent().statusbar if self.isPaused: self.timer.Stop() statusbar.SetStatusText('paused') else: self.timer.Start(Board.Speed) statusbar.SetStatusText(str(self.numLinesRemoved)) self.Refresh() def clearBoard(self): for i in range(Board.BoardHeight * Board.BoardWidth): self.board.append(Tetrominoes.NoShape) def OnPaint(self, event): dc = wx.PaintDC(self) size = self.GetClientSize() boardTop = size.GetHeight() - Board.BoardHeight * self.squareHeight() for i in range(Board.BoardHeight): for j in range(Board.BoardWidth): shape = self.shapeAt(j, Board.BoardHeight - i - 1) if shape != Tetrominoes.NoShape: self.drawSquare(dc, 0 + j * self.squareWidth(), boardTop + i * self.squareHeight(), shape) if self.curPiece.shape() != Tetrominoes.NoShape: for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY - self.curPiece.y(i) self.drawSquare(dc, 0 + x * self.squareWidth(), boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(), self.curPiece.shape()) def OnKeyDown(self, event): if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape: event.Skip() return keycode = event.GetKeyCode() if keycode == ord('P') or keycode == ord('p'): self.pause() return if self.isPaused: return elif keycode == wx.WXK_LEFT: self.tryMove(self.curPiece, self.curX - 1, self.curY) elif keycode == wx.WXK_RIGHT: self.tryMove(self.curPiece, self.curX + 1, self.curY) elif keycode == wx.WXK_DOWN: self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY) elif keycode == wx.WXK_UP: self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY) elif keycode == wx.WXK_SPACE: self.dropDown() elif keycode == ord('D') or keycode == ord('d'): self.oneLineDown() else: event.Skip() def OnTimer(self, event): if event.GetId() == Board.ID_TIMER: if self.isWaitingAfterLine: self.isWaitingAfterLine = False self.newPiece() else: self.oneLineDown() else: event.Skip() def dropDown(self): newY = self.curY while newY > 0: if not self.tryMove(self.curPiece, self.curX, newY - 1): break newY -= 1 self.pieceDropped() def oneLineDown(self): if not self.tryMove(self.curPiece, self.curX, self.curY - 1): self.pieceDropped() def pieceDropped(self): for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY - self.curPiece.y(i) self.setShapeAt(x, y, self.curPiece.shape()) self.removeFullLines() if not self.isWaitingAfterLine: self.newPiece() def removeFullLines(self): numFullLines = 0 statusbar = self.GetParent().statusbar rowsToRemove = [] for i in range(Board.BoardHeight): n = 0 for j in range(Board.BoardWidth): if not self.shapeAt(j, i) == Tetrominoes.NoShape: n = n + 1 if n == 10: rowsToRemove.append(i) rowsToRemove.reverse() for m in rowsToRemove: for k in range(m, Board.BoardHeight): for l in range(Board.BoardWidth): self.setShapeAt(l, k, self.shapeAt(l, k + 1)) numFullLines = numFullLines + len(rowsToRemove) if numFullLines > 0: self.numLinesRemoved = self.numLinesRemoved + numFullLines statusbar.SetStatusText(str(self.numLinesRemoved)) self.isWaitingAfterLine = True self.curPiece.setShape(Tetrominoes.NoShape) self.Refresh() def newPiece(self): self.curPiece = self.nextPiece statusbar = self.GetParent().statusbar self.nextPiece.setRandomShape() self.curX = Board.BoardWidth / 2 + 1 self.curY = Board.BoardHeight - 1 + self.curPiece.minY() if not self.tryMove(self.curPiece, self.curX, self.curY): self.curPiece.setShape(Tetrominoes.NoShape) self.timer.Stop() self.isStarted = False statusbar.SetStatusText('Game over') def tryMove(self, newPiece, newX, newY): for i in range(4): x = newX + newPiece.x(i) y = newY - newPiece.y(i) if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: return False if self.shapeAt(x, y) != Tetrominoes.NoShape: return False self.curPiece = newPiece self.curX = newX self.curY = newY self.Refresh() return True def drawSquare(self, dc, x, y, shape): colors = ['#000000', '#CC6666', '#66CC66', '#6666CC', '#CCCC66', '#CC66CC', '#66CCCC', '#DAAA00'] light = ['#000000', '#F89FAB', '#79FC79', '#7979FC', '#FCFC79', '#FC79FC', '#79FCFC', '#FCC600'] dark = ['#000000', '#803C3B', '#3B803B', '#3B3B80', '#80803B', '#803B80', '#3B8080', '#806200'] pen = wx.Pen(light[shape]) pen.SetCap(wx.CAP_PROJECTING) dc.SetPen(pen) dc.DrawLine(x, y + self.squareHeight() - 1, x, y) dc.DrawLine(x, y, x + self.squareWidth() - 1, y) darkpen = wx.Pen(dark[shape]) darkpen.SetCap(wx.CAP_PROJECTING) dc.SetPen(darkpen) dc.DrawLine(x + 1, y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + self.squareHeight() - 1) dc.DrawLine(x + self.squareWidth() - 1, y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1) dc.SetPen(wx.TRANSPARENT_PEN) dc.SetBrush(wx.Brush(colors[shape])) dc.DrawRectangle(x + 1, y + 1, self.squareWidth() - 2, self.squareHeight() - 2) class Tetrominoes(object): NoShape = 0 ZShape = 1 SShape = 2 LineShape = 3 TShape = 4 SquareShape = 5 LShape = 6 MirroredLShape = 7 class Shape(object): coordsTable = ( ((0, 0), (0, 0), (0, 0), (0, 0)), ((0, -1), (0, 0), (-1, 0), (-1, 1)), ((0, -1), (0, 0), (1, 0), (1, 1)), ((0, -1), (0, 0), (0, 1), (0, 2)), ((-1, 0), (0, 0), (1, 0), (0, 1)), ((0, 0), (1, 0), (0, 1), (1, 1)), ((-1, -1), (0, -1), (0, 0), (0, 1)), ((1, -1), (0, -1), (0, 0), (0, 1)) ) def __init__(self): self.coords = [[0,0] for i in range(4)] self.pieceShape = Tetrominoes.NoShape self.setShape(Tetrominoes.NoShape) def shape(self): return self.pieceShape def setShape(self, shape): table = Shape.coordsTable[shape] for i in range(4): for j in range(2): self.coords[i][j] = table[i][j] self.pieceShape = shape def setRandomShape(self): self.setShape(random.randint(1, 7)) def x(self, index): return self.coords[index][0] def y(self, index): return self.coords[index][1] def setX(self, index, x): self.coords[index][0] = x def setY(self, index, y): self.coords[index][1] = y def minX(self): m = self.coords[0][0] for i in range(4): m = min(m, self.coords[i][0]) return m def maxX(self): m = self.coords[0][0] for i in range(4): m = max(m, self.coords[i][0]) return m def minY(self): m = self.coords[0][1] for i in range(4): m = min(m, self.coords[i][1]) return m def maxY(self): m = self.coords[0][1] for i in range(4): m = max(m, self.coords[i][1]) return m def rotatedLeft(self): if self.pieceShape == Tetrominoes.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, self.y(i)) result.setY(i, -self.x(i)) return result def rotatedRight(self): if self.pieceShape == Tetrominoes.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, -self.y(i)) result.setY(i, self.x(i)) return result app = wx.App() Tetris(None, -1, 'Tetris') app.MainLoop() </code> I have simplified the game a bit, so that it is easier to understand. The game starts immediately, after it is launched. We can pause the game by pressing the p key. The space key will drop the tetris piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The score is the number of lines, that we have removed. <code python> ... self.curX = 0 self.curY = 0 self.numLinesRemoved = 0 self.board = [] ... </code> Before we start the game cycle, we initialize some important variables. The **self.board** variable is a list of numbers from 0 ... 7. It represents the position of various shapes and remains of the shapes on the board. <code python> for i in range(Board.BoardHeight): for j in range(Board.BoardWidth): shape = self.shapeAt(j, Board.BoardHeight - i - 1) if shape != Tetrominoes.NoShape: self.drawSquare(dc, 0 + j * self.squareWidth(), boardTop + i * self.squareHeight(), shape) </code> The painting of the game is divided into two steps. In the first step, we draw all the shapes, or remains of the shapes, that have been dropped to the bottom of the board. All the squares are rememberd in the **self.board** list variable. We access it using the shapeAt() method. <code python> if self.curPiece.shape() != Tetrominoes.NoShape: for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY - self.curPiece.y(i) self.drawSquare(dc, 0 + x * self.squareWidth(), boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(), self.curPiece.shape()) </code> The next step is drawing of the actual piece, that is falling down. <code python> elif keycode == wx.WXK_LEFT: self.tryMove(self.curPiece, self.curX - 1, self.curY) </code> In the **OnKeyDown()** method we check for pressed keys. If we press the left arrow key, we try to move the piece to the left. We say try, because the piece might not be able to move. <code python> def tryMove(self, newPiece, newX, newY): for i in range(4): x = newX + newPiece.x(i) y = newY - newPiece.y(i) if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: return False if self.shapeAt(x, y) != Tetrominoes.NoShape: return False self.curPiece = newPiece self.curX = newX self.curY = newY self.Refresh() return True </code> In the **tryMove()** method we try to move our shapes. If the shape is at the edge of the board or is adjacent to some other piece, we return false. Otherwise we place the current falling piece to a new position and return true. <code python> def OnTimer(self, event): if event.GetId() == Board.ID_TIMER: if self.isWaitingAfterLine: self.isWaitingAfterLine = False self.newPiece() else: self.oneLineDown() else: event.Skip() </code> In the **OnTimer()** method we either create a new piece, after the previous one was dropped to the bottom, or we move a falling piece one line down. <code python> def removeFullLines(self): numFullLines = 0 rowsToRemove = [] for i in range(Board.BoardHeight): n = 0 for j in range(Board.BoardWidth): if not self.shapeAt(j, i) == Tetrominoes.NoShape: n = n + 1 if n == 10: rowsToRemove.append(i) rowsToRemove.reverse() for m in rowsToRemove: for k in range(m, Board.BoardHeight): for l in range(Board.BoardWidth): self.setShapeAt(l, k, self.shapeAt(l, k + 1)) ... </code> If the piece hits the bottom, we call the **removeFullLines()** method. First we find out all full lines. And we remove them. We do it by moving all lines above the current full line to be removed one line down. Notice, that we reverse the order of the lines to be removed. Otherwise, it would not work correctly. In our case we use a **naive gravity**. This means, that the pieces may be floating above empty gaps. <code python> def newPiece(self): self.curPiece = self.nextPiece statusbar = self.GetParent().statusbar self.nextPiece.setRandomShape() self.curX = Board.BoardWidth / 2 + 1 self.curY = Board.BoardHeight - 1 + self.curPiece.minY() if not self.tryMove(self.curPiece, self.curX, self.curY): self.curPiece.setShape(Tetrominoes.NoShape) self.timer.Stop() self.isStarted = False statusbar.SetStatusText('Game over') </code> The **newPiece()** method creates randomly a new tetris piece. If the piece cannot go into it's initial position, the game is over. The **Shape** class saves information about the tetris piece. <code python> self.coords = [[0,0] for i in range(4)] </code> Upon creation we create an empty coordinates list. The list will save the coordinates of the tetris piece. For example, these tuples (0, -1), (0, 0), (1, 0), (1, 1) represent a rotated S-shape. The following diagram illustrates the shape. {{ http://zetcode.com/wxpython/images/coordinates.png |Coordinates}} When we draw the current falling piece, we draw it at **self.curX**, **self.curY position**. Then we look at the coordinates table and draw all the four squares. {{ http://zetcode.com/wxpython/images/tetris.png |Tetris}}

نقاش

بدري دركوش, 2009/06/24 04:46

بسم الله الرحمن الرحيم سأبدأ من الفصل الاول و الثاني بدري دركوش

بدري دركوش, 2009/06/30 08:27

سؤال ؟ widgets تترجم قطع أم هناك مصطلح آخر ؟

مؤيد السعدي, 2009/06/30 16:13, 2009/06/30 16:14

هي اختصار لكمة window gadget

تترجم ودجات وهي كلمة عربية تعني وسيلة ترجمناها في تعليم البرمجة الرسومية في PyQt4 وعلقنا هناك

الودجة هي الكائن الرسومي لأنها وسيلة وجاء في لسان العرب “ووَدَجَ بين القوم وَدْجاً أَصلح وفلانٌ وَدَجِي إِلى فلان أَي وسيلتي” انظر http://wiki.arabeyes.org/techdict_talk:Widget

naji, 2010/01/20 18:26

شرح رائع لكن هل يوجد كتاب قبل هذا الكتاب يشرح كل شي بالتفصيل وليس باقتضاب كما هنا ولكم الشكر

ترجمة سيئة وشرح كارثي كيف لمبتدا ان يتعلم بهذه الطريقة , 2010/07/22 08:07

افهم شيئا ترجمة سيئة للغاية من المفروض خطوة بخطوة حتي تسهل علي المبتدئين الاستعاب علي كل حال مشكور علي المجهود

أنس أحمد, 2010/07/27 16:31

هل تعلمت بايثون أولاً يا هذا؟؟؟ ربما الخطأ بسبب عدم تعلمك بايثون فأنا لا أجد في الشرح صعوبة !!

sachin, 2012/04/13 05:53

MS Access Repair Software MS Access Password Recovery Tool Microsoft Access Password Recovery Tool Software to Recover MDB Password excel fix Free Keylogger Download Access Password Recovery Chat Archive Recovery Database conversion software free download Repair Corrupt Excel File Data recovery trial version software download Web Hosting Data Wiping Software Digital Camera Recovery Disk Recovery Software tools Free Download Data Recovery Software Database Converters Monitoring Software Monitoring Software Access Unlocker Software excel recovery Keylogger Download Floppy Disk Recovery Tool Excel File Repair File Repair Software MSN Messenger Password Recovery Download fix damaged excel file How To Fix Freeware Floppy Disk Recovery Hard Disk Recovery keylogger freeware Hard Disk Drive Data Recovery free damaged excel file repair Free hard drive data recovery software Internet password recovery tool iPod Recovery Software Download Keylogger free excel recovery software Best Keylogger Software Download Keyloggers Key logger Software Download Remote keylogger spy software Key Logging Software keystroke capture keylogger Password Recovery Software KeyStroke Logger Download Capture keystrokes software MSN Password Recovery Best Outlook Password Recovery Tool Free Download password breaker KeyStroke Logger Download Password Recovery freeware password recovery password recovery freeware Free USB flash pen drive data recovery software How to Recover Data from Pen Drive How to fix a corrupt PowerPoint file Free Yahoo Chat History Reader Downloads recover excel file freeware recover corrupt excel file tool Recover emails file Recover SMS Messages recovery excel free Repair Excel File excel repair tool Download Data Recovery Software Sim Card Recovery Sim Card Data Recovery Download Free Keylogger Software Undetected KeyLogger Undetected KeyLogger Keylogger Freeware Data Recovery Advance keylogger software downloads Keylogger Spyware InvisibleKeylogger free keylogger Sim card SMS data recovery program SIM Card SMS recovery Tool Backlink Watch Hidden key logger USB Recovery Software spy software Backlink Checking Tool File recovery zip repair Keylogger Download data recovery trial software Recover My Files Data Recovery Software Download Free Trial Data Recovery seo backlink checker SIM Recovery Word Repair Program data recovery freetrial

Very interesting article. There’s some great information…..

sinan, 2012/06/03 14:31

رابط خارجيhi

احمد, 2017/02/20 11:12
السلام عليكم ورحمة الله وبركاته . شكرا لجهودكم وإبداعاتكم ...
أحببت أن ألتمسس منكم ترك الأرقام العربية في الترجمات "12345678" بدل الأرقام الهندية "٨٢٤١"
وتقبلوا تحياتي وتقديري
salm, 2017/06/26 07:41
شكرا
IvanRbq, 2017/09/19 20:34
http://recoverperu.org
أدخل تعليقك:
36 +14 = ?
 
docs/wxpython.txt · آخر تعديل: 2015/04/23 00:20 (تحرير خارجي)