Различия между версиями 2 и 3
Версия 2 от 2010-05-24 14:25:54
Размер: 28042
Редактор: alafin
Комментарий:
Версия 3 от 2010-05-24 20:02:40
Размер: 69873
Редактор: alafin
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 197: Строка 197:
||'''Функция'''||'''Назначение'''||
||Blit(xdest, ydest, width, height, source, xsrc, ysrc)||Копирует биты непосредственно из контекста устройства source (источник) в контекст устройства, выполняющий данный вызов (приемник). Параметры xdest и ydest являются начальной точкой копии в контексте приемника. Следующие два параметра определяют ширину и высоту области копирования. Параметр source является исходным контекстом устройства, а xsrc и ysrc – начальная точка копии в этом контексте. Имеются дополнительные параметры для определения логической оверлейной функции и маски.||
||Clear()||Очищает контекст устройства, закрашивая всю его область текущей кистью фона.||
||DrawArc(x1, y1, x2, y2, xc, yc)||Рисует дугу окружности из начальной точки (x1, y1) в конечную точку (x2, y2). Точка (xc, yc) - центр окружности, которой принадлежит рисуемая дуга. Результирующая дуга заполняется текущей кистью. Эта функция рисует дугу из начальной точки в конечную точку против часовой стрелки. Имеется родственный метод DrawEllipticalArc().||
||DrawBitmap(bitmap, x, y, transparent)||Копирует объект wx.Bitmap, начиная с точки (x, y). Если параметр transparent установлен в True, битовое изображение будет нарисовано прозрачным.||
||DrawCircle(x, y, radius) DrawCircle(point, radius)||Рисует окружность с центром в указанной точке и указанным радиусом. Имеется родственный метод DrawEllipse.||
||DrawIcon(icon, x, y)||Рисует объект wx.Icon в точке контекста (x, y).||
||DrawLine(x1, y1, x2, y2)||Рисует линию из точки (x1, y1) в точку (x2, y2). Имеется смежный метод DrawLines(), который берет список точек, состоящий из объектов Python wx.Point и соединяет их.||
||DrawPolygon(points)||Рисует полигон (многоугольник) на основании списка Python, состоящего из объектов-точек wx.Point. Отличается от DrawLines() в том, что конечная точка соединяется с первой точкой, а результирующая форма закрашивается текущей кистью. Опциональные параметры задают смещение по x и y, а также стиль закрашивания.||
||DrawRectangle(x, y, width, height)||Рисует прямоугольник, имеющий левый верхний угол в точке (x, y), ширину width и высоту height. Прямоугольник закрашивается.||
||DrawText(text, x, y)||Используя текущий шрифт, рисует строку текста в точке (x, y). Смежные функции: DrawRotatedText() и GetTextExtent(). Текстовые элементы имеют независимые свойства цвета текста и цвета фона.||
||FloodFill(x, y, color, style)||Выполняет закрашивание области, начиная в точке (x, y) и используя цвет текущей кисти. Параметр style опциональный. Его значение по умолчанию wx.FLOOD_SURFACE указывает, что параметр color определяет поверхность закрашивания, которое прекращается, когда обнаруживается любой другой цвет. Другое значение, wx.FLOOD_BORDER, определяет, что цвет является границей закрашиваемой формы, и при обнаружения этого цвета закрашивание прекращается.||
||GetBackground() SetBackground(brush)||Фоновая кисть является объектом wx.Brush и используется при вызове метода Clear(). ||GetBrush() SetBrush(brush)||Эта кисть является объектом wx.Brush и используется для закрашивания любых фигур, которые рисуются в контексте устройства.||
||GetFont() SetFont(font)||Это шрифт, являющийся объектом wx.Font и используется для всех операций рисования текста.||
||GetPen() SetPen(pen)||Это перо является объектом wx.Pen и используется во всех операциях рисования, где рисуются линии.||
||GetPixel(x, y)||Возвращает объект wx.Colour для пикселя в точке (x, y).||
||GetSize() GetSizeTuple()||Возвращает размер пикселя в контексте устройства в виде объекта wx.Size или как кортеж Python.||

Это не полный список. В целях упрощения, были пропущены некоторые менее примечательные методы рисования, какими являются функции текстовой обработки и пиксельной манипуляции. Эти методы будут описаны в главе 12.

== Добавление оконных элементов оформления ==

При рисовании на экране существенная часть программы рисования далека от тех вещей, которые придают Вашему приложению изящный вид. В этом подразделе мы обсудим общие элементы оформления окна: панель состояния (status bar), полосу меню (menubar) и инструментальную панель (toolbar). К тому же, в главе 10 мы обсудим эти возможности более подробно.

=== Как добавить и доработать панель состояния? ===

В wxPython Вы можете добавить и разместить панель состояния внизу фрейма, вызывая метод фрейма CreateStatusBar(). Панель состояния автоматически изменяет свои размеры вместе с родительским фреймом. По умолчанию панель состояния является экземпляром класса wx.StatusBar. Для создания пользовательского подкласса панели состояния подключите его к Вашему фрейму при помощи метода SetStatusBar(), передавая в качестве аргумента экземпляр Вашего нового класса.

Для отображения в Вашей панели состояния отдельного фрагмента текста, Вы можете использовать метод SetStatusText() класса wx.StatusBar. Листинг 6.2 расширяет приведенный в листинге 6.1 класс SketchFrame, для отображения в панели состояния текущей позиции указателя мыши.

'''Листинг 6.2 Добавление к фрейму простой панели состояния'''
{{{#!highlight python
import wx
from example1 import SketchWindow
class SketchFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "Sketch Frame",
                size=(800,600))
        self.sketch = SketchWindow(self, -1)
        self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
        self.statusbar = self.CreateStatusBar()
    def OnSketchMotion(self, event):
        self.statusbar.SetStatusText(str(event.GetPositionTuple()))
        event.Skip()
if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = SketchFrame(None)
    frame.Show(True)
    app.MainLoop()
}}}

Мы подключили панель состояния, при этом фрейм также может обрабатывать событие wx.EVT_MOTION нашего окна рисования. Обработчик события устанавливает текст панели состояния на основании предоставляемых событием данных. Затем он вызывает метод Skip(), обеспечивающий вызов другого метода OnMotion(), в противном случае линия не будет нарисована.

Помещая на панель состояния дополнительные объекты, Вы можете сделать её похожей на любой другой виджет. Если Вы захотите вывести в панели состояния несколько текстовых элементов, можете создать множество текстовых полей. Чтобы использовать эту возможность, вызовите метод SetFieldsCount() с необходимым Вам количеством полей; значение по умолчанию – одно поле. После это, используйте SetStatusText() как в ранее приведенном примере, но со вторым аргументом, задающим номер устанавливаемого методом поля. Нумерация полей начинается с нуля. Если Вы не укажите номер поля, то по умолчанию устанавливается поле с номером 0. Вот почему работает предыдущий пример, где мы так и поступили.

По умолчанию, все поля имеют равную ширину. Тем не менее, это не всегда удобно. Для того чтобы отрегулировать размеры текстовых полей, wxPython предоставляет метод SetStatusWidth(). Этот метод принимает список целых значений, определяющих длину полей панели состояния. Данный список целых значений используется, чтобы надлежащим образом вычислять ширину полей. Если целое положительно, оно определяет фиксированную ширину поля. Если Вы хотите, чтобы ширина поля изменялась с фреймом, укажите отрицательное целое. Абсолютная величина отрицательного целого указывает относительный размер поля, выраженный количеством отводимых полю частей в общей ширине панели. Например, вызов statusbar.SetStatusWidth([-1, -2, -3]) отводит для самого правого поля половину ширины (3/6 части), центральная область получает треть ширины (2/6 части), а крайнее левое поле получает шестую часть ширины (1/6 часть). Результат показан на рисунке 6.2.

'''Рисунок 6.2 Пример панели состояния с полями в 1/6, 2/3 и 1/2 от общей ширины'''

Листинг 6.3 добавляет поддержку двух дополнительных полей панели состояния, одно из который показывает количество точек в текущей проведенной линии, а другое показывает количество линий в текущем чертеже. Этот листинг воспроизводит панель состояния, показанную на рисунке 6.2.

'''Листинг 6.3 Поддержка множества полей состояния'''
{{{#!highlight python
import wx
from example1 import SketchWindow
class SketchFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "Sketch Frame",
                size=(800,600))
        self.sketch = SketchWindow(self, -1)
        self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetFieldsCount(3)
        self.statusbar.SetStatusWidths([-1, -2, -3])
    def OnSketchMotion(self, event):
        self.statusbar.SetStatusText("Pos: %s" %
                str(event.GetPositionTuple()), 0)
        self.statusbar.SetStatusText("Current Pts: %s" %
                len(self.sketch.curLine), 1)
        self.statusbar.SetStatusText("Line Count: %s" %
                len(self.sketch.lines), 2)
        event.Skip()
if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = SketchFrame(None)
    frame.Show(True)
    app.MainLoop()
}}}

Класс wx.StatusBar позволяет Вам рассматривать поля панели состояния как стек типа «последний вошёл - первый вышел» (LIFO). Методы PushStatusText() и PopStatusText() позволять после временного отображения нового текста вернуться к предыдущему тексту поля панели состояния (правда, в демонстрационном приложении данной главы этого не требуется). Оба этих метода принимают дополнительный номер поля, поэтому они могут использоваться и в случае панели с множеством полей.

Таблица 6.3 приводит наиболее употребительные методы класса wx.StatusBar.

'''Таблица 6.3 Методы класса wx.StatusBar'''
||'''Метод'''||'''Описание'''||
||GetFieldsCount() SetFieldsCount(count)||Свойство, определяющее количество полей в панели состояния.||
||GetStatusText(field=0) SetStatusText(text, field=0)||Свойство, определяющее выводимый в указанном поле текст. Индекс 0 (значение по умолчанию) указывает крайнее левое поле.||
||PopStatusText(field=0)||Извлекает текст из стека указанного поля панели состояния, замещая текст этого поля извлеченным значением.||
||PushStatusText(text, field=0)||Замещает вид указанного поля панели состояния данным текстом и помещает это новое значение в вершину стека данного поля.||
||SetStatusWidths(widths)||Берёт список целых значений и определяет ширину полей панели состояния. Положительное число указывает фиксированную ширину в пикселях, а отрицательное число указывает динамическую часть ширины пропорциональную абсолютной величине числа.||

В главе 10 будут предоставлены дополнительные сведения о панелях состояния. А сейчас мы обсудим меню.

=== Как добавить подменю или меню с флажками? ===

В этом подразделе, мы представим два распространенных приёма для меню - это подменю и меню в виде флажков (checked menu) или переключателей (radio menu). Подменю – это меню, которое доступно в одном из меню верхнего уровня. Меню-флажок или меню-переключатель – это группа пунктов меню, которые ведут себя подобно группе флажков или переключателей. Рисунок 6.3 показывает строку меню, включающее подменю с пунктами в виде переключателей.

Для создания подменю, сформируйте его, как и любое другое меню, и добавьте в родительское меню при помощи метода wx.Меню.AppendMenu().
    
Пункты меню с украшениями в виде флажков или переключателей могут также создаваться при помощи методов AppendCheckItem() и AppendRadioItem() класса wx.Menu, или передавая атрибутом типа в конструкторе wx.MenuItem одно из следующих значений: wx.ITEM_NORMAL, wx.ITEM_CHECKBOX или wx.ITEM_RADIO. Пункт меню в виде флажка отображает отметку, которая автоматически включается или выключается при выборе этого пункта; Вам не нужно вручную управлять этим процессом. Начальное значение пункта меню в виде флажка - выключено. Пункты меню в виде переключателей сгруппированы. Предполагается, что последовательные пункты-переключатели будут частью одной и той же группы (разделитель меню разрывает группу). По умолчанию, отмечен самый верхний элемент группы, а при выборе любого элемента группы отметка автоматически передаётся на соответствующий выбранный пункт. Для того чтобы программно отметить пункт меню, используйте метод Check(id, bool) класса wx.Menu, где id – wxPython-идентификатор того пункта меню, который нужно изменить, а bool определяет состояние этого пункта.

'''Рисунок 6.3 Меню и подменю с пунктами-переключателями'''


Листинг 6.4 добавляет меню к фрейму приложения. Функциональное назначение этого кода – развитие предшествующего рефакторизованного кода из листинга 5.5. Для создания подменю здесь усовершенствован формат данных, а код создания при необходимости создает подменю рекурсивно. Также добавлена поддержка меню с переключателями и флажками.

'''Листинг 6.4 Поддержка в приложении меню'''
{{{#!highlight python
import wx
from example1 import SketchWindow
class SketchFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "Sketch Frame",
                     size=(800,600))
        self.sketch = SketchWindow(self, -1)
        self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
        self.initStatusBar() # (1) Небольшой рефакторинг
        self.createMenuBar()
                                                                            
    def initStatusBar(self):
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetFieldsCount(3)
        self.statusbar.SetStatusWidths([-1, -2, -3])
    def OnSketchMotion(self, event):
        self.statusbar.SetStatusText("Pos: %s" %
                     str(event.GetPositionTuple()), 0)
        self.statusbar.SetStatusText("Current Pts: %s" %
                     len(self.sketch.curLine), 1)
        self.statusbar.SetStatusText("Line Count: %s" %
                     len(self.sketch.lines), 2)
        event.Skip()
                                                                                 
                                                                                 
    def menuData(self): # (2) Идентификация данных меню
        return [("&File", (
                            ("&New", "New Sketch file", self.OnNew),
                            ("&Open", "Open sketch file", self.OnOpen),
                            ("&Save", "Save sketch file", self.OnSave),
                            ("", "", ""),
                            ("&Color", (
                                 ("&Black", "", self.OnColor,
                                       wx.ITEM_RADIO),
                                 ("&Red", "", self.OnColor,
                                       wx.ITEM_RADIO),
                                 ("&Green", "", self.OnColor,
                                       wx.ITEM_RADIO),
                                 ("&Blue", "", self.OnColor,
                                       wx.ITEM_RADIO))),
                            ("", "", ""),
                        ("&Quit", "Quit", self.OnCloseWindow)))]
    def createMenuBar(self):
        menuBar = wx.MenuBar()
        for eachMenuData in self.menuData():
              menuLabel = eachMenuData[0]
              menuItems = eachMenuData[1]
              menuBar.Append(self.createMenu(menuItems), menuLabel)
        self.SetMenuBar(menuBar)
    def createMenu(self, menuData):
        menu = wx.Menu()
        for eachItem in menuData:
              if len(eachItem) == 2: # (3) Создание подменю
                     label = eachItem[0]
                     subMenu = self.createMenu(eachItem[1])
                     menu.AppendMenu(wx.NewId(), label, subMenu)
              else:
                     self.createMenuItem(menu, *eachItem)
        return menu
    def createMenuItem(self, menu, label, status, handler,
                                kind=wx.ITEM_NORMAL):
        if not label:
              menu.AppendSeparator()
              return
        menuItem = menu.Append(-1, label, status, kind) # (4) Создание элементов меню с типом
        self.Bind(wx.EVT_MENU, handler, menuItem)
        
    def OnNew(self, event): pass
    def OnOpen(self, event): pass
    def OnSave(self, event): pass
    def OnColor(self, event): # (5) Обработка изменения цвета
        menubar = self.GetMenuBar()
        itemId = event.GetId()
        item = menubar.FindItemById(itemId)
        color = item.GetLabel()
        self.sketch.SetColor(color)
    def OnCloseWindow(self, event):
        self.Destroy()
if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = SketchFrame(None)
    frame.Show(True)
    app.MainLoop()
}}}

'''(1)''' Теперь метод __init__ более функционален и мы инкапсулировали инициализацию панели состояния в отдельный метод.
  
'''(2)''' Здесь определяется формат данных меню в виде (метка, (пункты)), где каждый пункт – это или список вида (метка, текст панели состояния, обработчик, опциональный тип) или меню с меткой и пунктами. Для определения того, какие подпункты данных являются меню или пунктами меню, помните, что меню содержит 2, а пункты 3 или 4 элемента данных. В промышленной системе, где данные имеют более сложную структуру, я рекомендую использовать XML или какой-нибудь другой внешний формат.
  
'''(3)''' Когда данные имеет длину 2, что означает подменю, производится разделение меню тем же способом, как и для меню верхнего уровня, при этом рекурсивно вызывается добавляющий подменю метод createMenu.
  
'''(4)''' При такой реализации меню проще добавить в конструктор wx.MenuItem параметр типа, чем использовать специальные методы wx.Menu.
  
'''(5)''' Для обработки изменения цвета у всех пунктов меню установлен метод OnColor, при этом не требуется установки отдельных обработчиков для каждого пункта. В этом случае код получает идентификатор (id) пункта из события и использует метод FindItemById() для получения соответствующего пункта меню (обратите внимание, что от нас не требуется поддерживать отдельную хеш-таблицу идентификаторов пунктов – мы используем полосу меню как структуру данных). Этот метод полагает, что метка пункта меню является наименованием цвета wxPython и он передает эту метку окну рисования, где изменяется перо.

=== Как добавить панель инструментов? ===
      
Строка меню и панель инструментов часто тесно взаимосвязаны, причем большая часть или даже вся функциональность панели инструментов доступна и через пункты меню. В wxPython это сходство расширено кнопками панели инструментов, генерирующими при нажатии события wx.EVT_MENU, что облегчает использование одних и тех же методов, как для обработки выбора пунктов меню, так и щелчков на панели инструментов. Панель инструментов wxPython – это экземпляр класса wx.ToolBar, и как мы увидели в главе 2, она может быть создана при помощи метода фрейма CreateToolBar(). Подобно панели состояния, инструментальная панель автоматически меняет размеры вместе с родительским фреймом. Инструментальная панель подобна другим окнам wxPython в том, что она может иметь произвольные дочерние окна. Панели инструментов содержат также методы для создания инструментальных кнопок. Рисунок 6.4 показывает часть окна с панелью инструментов, которая дублирует функциональность созданного нами меню.

'''Рисунок 6.4 Типичная панель инструментов с регулярными и переключающими кнопками'''

Как и в коде меню, цветные битовые изображения представляют собой переключатели, и переключение одного из них приводит к изменению выделения. В листинге 6.5 мы не повторяем код меню, а включили новые и измененные методы SketchFrame.

'''Листинг 6.5 Добавление в приложение панели инструментов'''
{{{#!highlight python
def __init__(self, parent):
    wx.Frame.__init__(self, parent, -1, "Sketch Frame",
            size=(800,600))
    self.sketch = SketchWindow(self, -1)
    self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
    self.initStatusBar()
    self.createMenuBar()
    self.createToolBar()
                                                      
def createToolBar(self): # (1) Создание панели инструментов
    toolbar = self.CreateToolBar()
    for each in self.toolbarData():
        self.createSimpleTool(toolbar, *each)
    toolbar.AddSeparator()
    for each in self.toolbarColorData():
        self.createColorTool(toolbar, each)
    toolbar.Realize() # (2) Реализация панели инструментов
    
def createSimpleTool(self, toolbar, label, filename,
        help, handler): # (3) Создание простых инструментов
    if not label:
        toolbar.AddSeparator()
        return
                                                         
    bmp = wx.Image(filename,
                                                         
            wx.BITMAP_TYPE_BMP).ConvertToBitmap()
    tool = toolbar.AddSimpleTool(-1, bmp, label, help)
    self.Bind(wx.EVT_MENU, handler, tool)
def toolbarData(self):
    return (("New", "new.bmp", "Create new sketch",
                 self.OnNew),
            ("", "", "", ""),
            ("Open", "open.bmp", "Open existing sketch",
                 self.OnOpen),
            ("Save", "save.bmp", "Save existing sketch",
                 self.OnSave))
                                              
                                              
def createColorTool(self, toolbar, color): # (4) Создание инструментов выбора цвета
    bmp = self.MakeBitmap(color)
    newId = wx.NewId()
    tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color)
    self.Bind(wx.EVT_MENU, self.OnColor, tool)
    
def MakeBitmap(self, color): # (5) Создание сплошного битового изображения
    bmp = wx.EmptyBitmap(16, 15)
    dc = wx.MemoryDC()
    dc.SelectObject(bmp)
    dc.SetBackground(wx.Brush(color))
    dc.Clear()
    dc.SelectObject(wx.NullBitmap)
    return bmp

def toolbarColorData(self):
    return ("Black", "Red", "Green", "Blue")

def OnColor(self, event):
    menubar = self.GetMenuBar()
    itemId = event.GetId()
    item = menubar.FindItemById(itemId)
    if not item: # (6) Изменение цвета при щелчке на панели инструментов
        toolbar = self.GetToolBar()
        item = toolbar.FindById(itemId)
        color = item.GetShortHelp()
    else:
        color = item.GetLabel()
    self.sketch.SetColor(color)
}}}


'''(1)''' Код для панели инструментов аналогичен по настройке коду для меню в том, что он также управляется на основе данных. Тем не менее, в этом случае, мы установили другие циклы для обычных кнопок и для кнопок-переключателей, поскольку они не внедрены в панель инструментов.

'''(2)''' Метод Realize() размещает объекты внутри инструментальной панели. Он должен быть вызван до отображения панели, и повторно вызывается, если в панель добавляются или удаляются любые инструменты.

'''(3)''' Этот метод похож на метод создания пунктов меню. Основное отличие в том, что для инструментов панели требуются битовые изображения. В данном случае, мы разместили три основных битовых изображения в том же каталоге, где размещается код примера. В конце метода мы назначаем тоже событие wx.EVT_MENU, которое использовано для пунктов меню. Для расшифровки метода AddTool, который обеспечивает более специфические функции инструментов, используйте таблицу 6.4.
  
'''(4)''' Инструментальные кнопки для выбора цвета создаются аналогично простым инструментам, просто панели разным способом сообщается о том, что они являются кнопками-переключателями. Сплошные битовые изображения созданы методом MakeBitmap().

'''(5)''' Этот метод создает сплошное (solid) битовое изображение соответствующего размера, создавая обычное битовое изображение wx.EmptyBitmap, подключая к нему wx.MemoryDC, и очищая битовое изображение нужным цветом при помощи фоновой кисти.
  
'''(6)''' Небольшое дополнение к методу OnColor() ищет в панели соответствующий инструмент и устанавливает необходимый цвет. Однако в коде имеется одна проблема - изменение цвета через пункт меню не изменяет состояние переключателей панели инструментов, и наоборот.

Панели инструментов имеют возможность гибкого управления событиями, которую не имеют пункты меню. Они могут сгенерировать событие типа wx.EVT_TOOL_RCLICKED, когда на инструменте нажимается правая кнопка мыши. К тому же, панели инструментов имеют несколько различных стилей, которые передаются как битовые изображения в качестве аргументов в CreateToolBar(). Таблица 6.4 перечисляет некоторые стили панели инструментов.

'''Таблица 6.4 Стили класса wx.ToolBar'''
||'''Стиль'''||'''Описание'''||
||wx.TB_3DBUTTONS||Представляет инструменты в виде 3D||
||wx.TB_HORIZONTAL||Этот стиль по умолчанию размещает панель инструментов горизонтально||
||wx.TB_NOICONS||Не отображает битовые изображения для каждого инструмента||
||wx.TB_TEXT||Панель инструментов будет показывать короткий вспомогательный текст вместе со встроенными битовыми изображениями||
||wx.TB_VERTICAL||Размещает панель инструментов вертикально||

Панели инструментов несколько сложнее панелей состояния. Таблица 6.5 отображает некоторые широко используемые методы панелей инструментов.

'''Таблица 6.5 Методы общего назначения класса wx.ToolBar'''

||'''Функция'''||'''Описание'''||
||AddControl(control)||Добавляет в панель инструментов произвольный управляющий виджет wxPython. Также смотрите связанный метод InsertControl().||
||AddSeparator()||Устанавливает между инструментами пустое пространство.||
||AddSimpleTool(id, bitmap, shortHelpString="", kind=wx.ITEM_NORMAL) AddTool(id, bitmap, bitmap2=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelpString="", longHelpString="", clientData=None)||Добавляет в панель инструментов простую инструментальную кнопку с данным битовым изображением. В качестве подсказки (tooltip) отображается shortHelpString. Параметр kind может иметь значение wx.ITEM_NORMAL, wx.ITEM_CHECKBOX или wx.ITEM_RADIO. Дополнительные параметры для простых инструментов: bitmap2 отображается, когда инструмент нажат; longHelpString отображается на панели состояния, когда указатель находится на инструменте, а clientData может быть использован, чтобы ассоциировать с инструментом произвольную часть данных. Имеется связанный метод InsertTool().||
||AddCheckTool(...)||Добавляет инструмент в виде флажка, с теми же параметрами, как и у AddTool().||
||AddRadioTool(...)||Добавляет инструмент в виде переключателя, с теми же параметрами, как и у AddTool(). Непрерывная последовательность переключателей объединяется в группу.||
||DeleteTool(toolId) DeleteToolByPosition(x, y)||Удаляет инструмент с данным идентификатором toolId, или тот, который отображен в данной точке.||
||FindControl(toolId) FindToolForPosition(x, y)||Находит и возвращает инструмент с данным идентификатором toolId, или тот, который отображен в данной точке.||
||ToggleTool(toolId, toggle)||Если инструмент с указанным идентификатором toolId является переключателем или флажком, этот метод устанавливает переключатель данного инструмента, полагаясь на булевое значение аргумента toggle.||

В следующем разделе мы покажем Вам, как использовать стандартные диалоги, для того чтобы получать информацию от пользователя. В большинстве операционных систем Вы можете использовать стандартные диалоги, чтобы обеспечивать Вашего пользователя знакомым интерфейсом при решении общих задач, таких как, например, выбор файла.

== Получение стандартной информации ==












Работа с базовыми составными блоками

Эта глава включает:

  • Использование контекста устройства для рисования на экране
  • Добавление на фрейм оконных украшений
  • Работа со стандартными диалогами выбора файлов и цвета
  • Размещение виджетов и создание координатора
  • Создание диалогов About и splash-окон

Даже простая программа на wxPython нуждается в применении стандартных элементов, например, таких как меню и диалогов. Все эти элементы выступают в роли составных блоков любого GUI-приложения, и вместе с такими виджетами как splash-окно (заставка), строка статуса (status bar) или диалог About, они обеспечивают более дружественную среду и придают Вашему приложению профессиональный вид и восприятие. В завершение первой части книги, мы обсудим с Вами создание программы, использующей все эти компоненты. Мы построим простую программу рисования, затем добавим эти блочные составные элементы и разъясним некоторые вопросы их использования. Мы закрепим фундаментальные понятия, затронутые в предшествующих главах, и в конечном счете у Вас получится простое, но в тоже время профессиональное приложение. Эта глава является промежуточным уровнем между базовыми понятиями, обсужденными в предшествующих главах и более подробным рассмотрением функциональности wxPython в частях 2 и 3.

Приложение, которое мы создадим в этой главе основано на примерах Doodle и Super Doodle, поставляемых вместе с wxPython в директории wxPython/samples. Это очень простая программа рисования, которая отслеживает указатель мыши и рисует линию при нажатии левой кнопки мыши. Рисунок 6.1 показывает простое исходное окно этой программы.

Рисунок 6.1 Образец окна без украшений

Мы выбрали такой пример, поскольку эта довольно простая программа иллюстрирует многие вопросы, возникающие при создании более сложных приложений. В этой главе мы покажем Вам, как нарисовать на экране линии, добавить панель состояния (status bar), панель инструментов (toolbar) и меню (menubar). Вы увидите, как можно использовать стандартные диалоги, такие как диалоги выбора файлов и цвета. Для размещения составных наборов виджетов мы используем координатор (sizer), а также добавим панель About и splash-окно. В конце главы Вы получите великолепную на вид заготовку программы.

Рисование на экране

Первоочередная задача Вашей программы для зарисовок состоит в рисовании на экране линии. Подобно другим GUI-инструментам, wxPython обеспечивает независимый от устройства набор инструментальных средств для рисования на различных типах дисплеев. В следующем разделе мы обсудим, как рисовать на экране.

Как рисовать на экране?

Для рисования на экране мы используем объект wxPython, называющийся контекстом устройства. Контекст устройства абстрагирует дисплейное устройство, предоставляя каждому устройству общий набор методов рисования, поэтому Ваш код рисования не зависит от типа целевого устройства. Контекст устройства в wxPython представлен абстрактным классом wx.DC и его подклассами. Так как wx.DC абстрактный класс, в своём приложении Вам нужно использовать один из его подклассов.

Использование контекста устройства

Таблица 6.1 отображает справочник по подклассам wx.DC и их использованию. Контексты устройств, которые используются для рисования в виджетах wxPython, должны всегда создаваться локально в виде временных объектов и не должны храниться между вызовами метода в виде глобальной переменной или другим способом. На некоторых платформах контексты устройства - это ограниченный ресурс и такое удержание ссылки на wx.DC может сделать программу нестабильной. Из-за способа, которым wxPython непосредственно использует контексты устройства, подклассы wx.DC имеют несколько тонких различий, которые учитываются при рисовании в виджетах. В главе 12 эти различия объясняются более подробно.

Таблица 6.1 Краткое руководство по подклассам контекста устройства wx.DC

Контект устройства

Использование

wx.BufferedDC

Используется для буферизации ряда команд рисования, до их завершения и готовности к дальнейшему рисованию на экране. Это предотвращает нежелательное мерцание при отображении.

wx.BufferedPaintDC

Тоже, что и wx.BufferedDC, но используется только в пределах wx.PaintEvent. Создавайте только временные экземпляры этого класса.

wx.ClientDC

Используется для рисования в оконном объекте. Применяйте этот класс, когда Вам необходимо рисовать в основной области виджета, т.е. не на границе или любых элементах оформления. Основная область иногда называется клиентской областью, отсюда и имя этого DC. Класс wx.ClientDC должен создаваться только временно. Этот класс используется только вне тела wx.PaintEvent.

wx.MemoryDC

Используется для рисования графики на сохраненном в памяти битовом изображении (bitmap), без его отображения. Вы можете затем выбрать битовое изображение и использовать метод wx.DC.Blit(), чтобы нарисовать его в окне.

wx.MetafileDC

В операционных системах Windows этот контекст устройства позволяет Вам создавать стандартные метафайлы данных.

wx.PaintDC

Идентичен контексту wx.ClientDC, за исключением того, что он используется только в пределах wx.PaintEvent. Создавайте только временные экземпляры этого класса.

wx.PostScriptDC

Используется для записи инкапсулированных файлов PostScript.

wx.PrinterDC

Используется в операционных системах Windows для вывода на принтер.

wx.ScreenDC

Используется для рисования непосредственно на экране, поверх и вне любого отображаемого окна. Этот класс должен создаваться только временно.

wx.WindowDC

Используется для рисования на всей области оконного объекта, включая границу и любые другие элементы оформления, не относящиеся к клиентской области. Операционные системы отличные от Windows могут не поддерживать этот класс.

Листинг 6.1 содержит начальный код изображенного на рисунке 6.1 окна рисования. Поскольку этот код показывает приёмы рисования в контексте устройства, мы его подробно прокомментируем.

Листинг 6.1 Начальный код окна рисования

   1 import wx
   2 class SketchWindow(wx.Window):
   3     def __init__(self, parent, ID):
   4         wx.Window.__init__(self, parent, ID)
   5         self.SetBackgroundColour("White")
   6         self.color = "Black"                                               
   7         self.thickness = 1
   8         self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) # (1) Создание объекта wx.Pen
   9         self.lines = []
  10         self.curLine = []
  11         self.pos = (0, 0)                                  
  12         self.InitBuffer()                                   
  13         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)            # (2) Присоединение событий
  14         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
  15         self.Bind(wx.EVT_MOTION, self.OnMotion)
  16         self.Bind(wx.EVT_SIZE, self.OnSize)
  17         self.Bind(wx.EVT_IDLE, self.OnIdle)
  18         self.Bind(wx.EVT_PAINT, self.OnPaint)
  19                                      
  20     def InitBuffer(self):                                       # (3) Создание контекста устройства с буферизацией 
  21         size = self.GetClientSize()
  22         self.buffer = wx.EmptyBitmap(size.width, size.height)
  23         dc = wx.BufferedDC(None, self.buffer)
  24         dc.SetBackground(wx.Brush(self.GetBackgroundColour()))  # (4) Использование контекста устройства
  25         dc.Clear()
  26         self.DrawLines(dc)
  27         self.reInitBuffer = False                                  
  28 
  29     def GetLinesData(self):
  30         return self.lines[:]
  31     def SetLinesData(self, lines):
  32         self.lines = lines[:]
  33         self.InitBuffer()
  34         self.Refresh()
  35     def OnLeftDown(self, event):
  36         self.curLine = []
  37         self.pos = event.GetPositionTuple()                     # (5) Получение позиции мыши
  38         self.CaptureMouse()                                     # (6) Захват мыши
  39     def OnLeftUp(self, event):
  40         if self.HasCapture():
  41             self.lines.append((self.color,
  42                 self.thickness,
  43                 self.curLine))
  44             self.curLine = []
  45             self.ReleaseMouse()                                 # (7) Освобождение мыши
  46                                                             
  47     def OnMotion(self, event):
  48                                                             
  49         if event.Dragging() and event.LeftIsDown():             # (8) Определение продолжения операции перетаскивания
  50             dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)  # (9) Создание другого контекста с буферизацией
  51             self.drawMotion(dc, event)
  52         event.Skip()                                   
  53                                                       
  54     def drawMotion(self, dc, event):                            # (10) Рисование в контексте устройства 
  55         dc.SetPen(self.pen)
  56         newPos = event.GetPositionTuple()    
  57         coords = self.pos + newPos              
  58         self.curLine.append(coords)                
  59         dc.DrawLine(*coords)
  60         self.pos = newPos
  61 
  62     def OnSize(self, event):                                   # (11) Обработка события изменения размера
  63         self.reInitBuffer = True
  64  
  65     def OnIdle(self, event):                                   # (12) Обработка простоя
  66         if self.reInitBuffer:
  67             self.InitBuffer()
  68             self.Refresh(False)
  69 
  70     def OnPaint(self, event):                                  # (13) Обработка запроса на прорисовку
  71         dc = wx.BufferedPaintDC(self, self.buffer)
  72 
  73     def DrawLines(self, dc):                                   # (14) Рисование всех линий
  74         for colour, thickness, line in self.lines:
  75             pen = wx.Pen(colour, thickness, wx.SOLID)
  76             dc.SetPen(pen)           
  77             for coords in line:
  78                 dc.DrawLine(*coords)
  79 
  80     def SetColor(self, color):
  81         self.color = color
  82         self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
  83 
  84     def SetThickness(self, num):
  85         self.thickness = num
  86         self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
  87 
  88 class SketchFrame(wx.Frame):
  89     def __init__(self, parent):
  90         wx.Frame.__init__(self, parent, -1, "Sketch Frame",
  91             size=(800,600))
  92         self.sketch = SketchWindow(self, -1)
  93 
  94 if __name__ == '__main__':
  95     app = wx.PySimpleApp()
  96     frame = SketchFrame(None)
  97     frame.Show(True)
  98     app.MainLoop()

(1) Экземпляр wx.Pen определяет цвет, толщину и стиль нарисованных в контексте устройства линий. Стили отличные от wx.SOLID включают wx.DOT, wx.LONGDASH и wx.SHORTDASH.

(2) Для рисования этому окну необходимо отвечать на различные типы событий мыши. Оно реагирует на нажатие и отпускание левой кнопки мыши, движение мыши, изменение размеров окна и перерисовку окна. Также определяется обработка, которая происходит в течение простоя.

(3) Контекст устройства с буферизацией создается в два этапа: (1) Создается пустое битовое изображение, которое служит в качестве внеэкранного буфера и (2) На основе внеэкранного буфера создается буферизованный контекст устройства. Буферизованный контекст используется для того, чтобы избежать перерисовки проведенных линий рисунка и исключить вызываемое этим мерцание экрана. Позже в этом подразделе мы подробнее обсудим контексты устройства с буферизацией.

(4)' Эти строки инициируют команды рисования в контексте устройства, а именно – устанавливается кисть заполнения фона и очищается устройство. Объект wx.Brush определяет цвет и стиль фона для команд заливки.

(5) Метод GetPositionTuple() объекта event возвращает кортеж Python, содержащий точную позицию, в которой выполнен щелчок мыши.

(6) Метод CaptureMouse() направляет весь ввод мыши в данное окно, даже если Вы перемещаете мышь за пределами границы окна. Этот вызов должен отменяться последующим вызовом в программе метода ReleaseMouse().

(7) Вызов ReleaseMouse() возвращает систему в состояние предшествующее вызову CaptureMouse(). Для отслеживания окон, которые захватили мышь, приложение wxPython использует стек, а вызов ReleaseMouse() эквивалентен высвобождению этого стека. Это подразумевает, что Вам нужно равное количество вызовов CaptureMouse() и ReleaseMouse().

(8) Эта строка определяет, является ли событие перемещения мыши частью процесса проведения линии, характеризующегося происходящим событием движения мыши при ее нажатой левой кнопке. Методы Dragging() и LeftIsDown() являются методами wx.MouseEvent и возвращают True, если во время перемещения данное условие выполняется.

(9) Поскольку wx.BufferedDC – один из временно создающихся контекстов устройства, перед тем как мы нарисуем линии, нам нужно создать другой контекст. В данном случае, в качестве основного контекста устройства мы создаем новый wx.ClientDC и снова используем в качестве буфера наш экземпляр переменной битового изображения.

(10) Эти строки фактически используют контекст устройства для рисования на экране только что проведенной линии. Сначала, мы создаем кортеж координат coords, который является комбинацией кортежей self.pos и newPos. Здесь, новая точка поступает из события GetPositionTuple(), а старая точка была определена при последнем вызове OnMotion(). Мы сохраняем этот кортеж в списке self.curLine, а затем используем функциональный вызов с синтаксисом распаковки (unpack syntax) для вызова DrawLine() с элементами кортежа в качестве аргументов. Метод DrawLine() принимает в качестве параметров (x1, y1, x2, y2) и рисует линию из точки (x1, y1) в точку (x2, y2). Частота, с которой происходит событие перемещения и появляется новая точка линии, зависит от базовой скорости системы.

(11) Если размеры окна изменились, мы отмечаем это, сохраняя величину True в атрибуте self.reInitBuffer. Фактически же, мы ничего не делаем до следующего события простоя (idle event).

(12) Когда придет событие простоя, приложение получает возможность реагировать на одно или несколько имевших место событий изменения размеров. Причина, по которой мы реагируем на событие простоя, а не на собственно событие изменения размеров, состоит в том, чтобы разрешить многократным событиям изменения размеров проследовать друг за другом максимально быстро без необходимости каждый раз выполнять перерисовку.

(13) Обработка запроса на перерисовку изображения удивительно проста: для рисунка создается буферизованный контекст устройства. Создается реальный wx.PaintDC (так как мы находимся в обработчике прорисовки, нам нужен именно wx.PaintDC, а не экземпляр wx.ClientDC), и затем, после удаления экземпляра dc, битовое изображение блиттируется (копируется) в него. Более подробная информация о буферизации приведена в следующих разделах.

(14) Этот метод используется, когда в результате изменения размеров (и последующей загрузки из файла) приложению необходимо перерисовать линии из экземпляра данных. Кроме того, мы используем надстройку DrawFunc(). В этом случае, мы проходим список линий, хранящийся в атрибуте coords, повторно создаем перо для каждой линии (скоро будет добавлена поддержка изменения характеристик пера), а затем рисуем все принадлежащие этой линии координатные кортежи.

Использование буфера рисования в этот пример обеспечивается двумя специальными подклассами wx.DC. Буфер рисования – это неотображаемая область, где все Ваши базовые команды рисования выполняются поочередно, а затем копируются на экран за один шаг. Преимущество буфера - в том, что пользователь не замечает отдельно происходящих команд рисования, и таким образом, экран обновляется с меньшим мерцанием. По этой причине, буферизация обычно используется при анимации или когда чертеж состоит из множества мелких частей.

В wxPython имеется два класса, которые используются для буферизации: обычно используется для буферизации wx.ClientDC, а wx.BufferDC wx.BufferPaintDC используется для буферизации wx.PaintDC. Работают они практически одинаково. Буферизованный контекст устройства создается двумя аргументами. Первый аргумент – это контекст целевого устройства соответствующего типа (например, в строке 9 листинга 6.1, это новый экземпляр wx.ClientDC). Второй аргумент – это объект wx.Bitmap. В листинге 6.1, мы создаем битовое изображение при помощи функции wx.EmptyBitmap. Когда команды рисования относятся к буферизованному контексту устройства, для рисования битового изображения используется встроенный контекст wx.MemoryDC. При уничтожении буферного объекта, деструктор C++ использует метод Blit(), чтобы автоматически скопировать битовое изображение на целевое устройство. В wxPython уничтожение обычно происходит, когда объект покидает область действия. Поэтому буферизованные контексты устройства полезны только при их временном создании, при котором они уничтожаются, и выполняется блитирование (blit).

Например, в методе OnPaint() листинга 6.1, битовое изображение self.buffer уже определено. Буферный объект необходимо просто создать, устанавливая этим самым связь между существующим битовым изображением и временным контекстом wx.PaintDC() окна. Метод завершается, буферизованный DC немедленно покидает область, запускается деструктор и битовое изображение копируется на экран.

Функции контекста устройства

При использовании контекстов устройства не забывайте использовать корректный контекст в зависимости от вида выполняемого Вами рисования (особо помните различие между wx.PaintDC и wx.ClientDC). Только в случае, когда у Вас будет корректный контекст устройства, Вы сможете что-либо с ним делать. Таблица 6.2 перечисляет некоторые наиболее интересные методы wx.DC.

Таблица 6.2 Методы общего назначения wx.DC

Функция

Назначение

Blit(xdest, ydest, width, height, source, xsrc, ysrc)

Копирует биты непосредственно из контекста устройства source (источник) в контекст устройства, выполняющий данный вызов (приемник). Параметры xdest и ydest являются начальной точкой копии в контексте приемника. Следующие два параметра определяют ширину и высоту области копирования. Параметр source является исходным контекстом устройства, а xsrc и ysrc – начальная точка копии в этом контексте. Имеются дополнительные параметры для определения логической оверлейной функции и маски.

Clear()

Очищает контекст устройства, закрашивая всю его область текущей кистью фона.

DrawArc(x1, y1, x2, y2, xc, yc)

Рисует дугу окружности из начальной точки (x1, y1) в конечную точку (x2, y2). Точка (xc, yc) - центр окружности, которой принадлежит рисуемая дуга. Результирующая дуга заполняется текущей кистью. Эта функция рисует дугу из начальной точки в конечную точку против часовой стрелки. Имеется родственный метод DrawEllipticalArc().

DrawBitmap(bitmap, x, y, transparent)

Копирует объект wx.Bitmap, начиная с точки (x, y). Если параметр transparent установлен в True, битовое изображение будет нарисовано прозрачным.

DrawCircle(x, y, radius) DrawCircle(point, radius)

Рисует окружность с центром в указанной точке и указанным радиусом. Имеется родственный метод DrawEllipse.

DrawIcon(icon, x, y)

Рисует объект wx.Icon в точке контекста (x, y).

DrawLine(x1, y1, x2, y2)

Рисует линию из точки (x1, y1) в точку (x2, y2). Имеется смежный метод DrawLines(), который берет список точек, состоящий из объектов Python wx.Point и соединяет их.

DrawPolygon(points)

Рисует полигон (многоугольник) на основании списка Python, состоящего из объектов-точек wx.Point. Отличается от DrawLines() в том, что конечная точка соединяется с первой точкой, а результирующая форма закрашивается текущей кистью. Опциональные параметры задают смещение по x и y, а также стиль закрашивания.

DrawRectangle(x, y, width, height)

Рисует прямоугольник, имеющий левый верхний угол в точке (x, y), ширину width и высоту height. Прямоугольник закрашивается.

DrawText(text, x, y)

Используя текущий шрифт, рисует строку текста в точке (x, y). Смежные функции: DrawRotatedText() и GetTextExtent(). Текстовые элементы имеют независимые свойства цвета текста и цвета фона.

FloodFill(x, y, color, style)

Выполняет закрашивание области, начиная в точке (x, y) и используя цвет текущей кисти. Параметр style опциональный. Его значение по умолчанию wx.FLOOD_SURFACE указывает, что параметр color определяет поверхность закрашивания, которое прекращается, когда обнаруживается любой другой цвет. Другое значение, wx.FLOOD_BORDER, определяет, что цвет является границей закрашиваемой формы, и при обнаружения этого цвета закрашивание прекращается.

GetBackground() SetBackground(brush)

Фоновая кисть является объектом wx.Brush и используется при вызове метода Clear().

GetBrush() SetBrush(brush)

Эта кисть является объектом wx.Brush и используется для закрашивания любых фигур, которые рисуются в контексте устройства.

GetFont() SetFont(font)

Это шрифт, являющийся объектом wx.Font и используется для всех операций рисования текста.

GetPen() SetPen(pen)

Это перо является объектом wx.Pen и используется во всех операциях рисования, где рисуются линии.

GetPixel(x, y)

Возвращает объект wx.Colour для пикселя в точке (x, y).

GetSize() GetSizeTuple()

Возвращает размер пикселя в контексте устройства в виде объекта wx.Size или как кортеж Python.

Это не полный список. В целях упрощения, были пропущены некоторые менее примечательные методы рисования, какими являются функции текстовой обработки и пиксельной манипуляции. Эти методы будут описаны в главе 12.

Добавление оконных элементов оформления

При рисовании на экране существенная часть программы рисования далека от тех вещей, которые придают Вашему приложению изящный вид. В этом подразделе мы обсудим общие элементы оформления окна: панель состояния (status bar), полосу меню (menubar) и инструментальную панель (toolbar). К тому же, в главе 10 мы обсудим эти возможности более подробно.

Как добавить и доработать панель состояния?

В wxPython Вы можете добавить и разместить панель состояния внизу фрейма, вызывая метод фрейма CreateStatusBar(). Панель состояния автоматически изменяет свои размеры вместе с родительским фреймом. По умолчанию панель состояния является экземпляром класса wx.StatusBar. Для создания пользовательского подкласса панели состояния подключите его к Вашему фрейму при помощи метода SetStatusBar(), передавая в качестве аргумента экземпляр Вашего нового класса.

Для отображения в Вашей панели состояния отдельного фрагмента текста, Вы можете использовать метод SetStatusText() класса wx.StatusBar. Листинг 6.2 расширяет приведенный в листинге 6.1 класс SketchFrame, для отображения в панели состояния текущей позиции указателя мыши.

Листинг 6.2 Добавление к фрейму простой панели состояния

   1 import wx
   2 from example1 import SketchWindow
   3 class SketchFrame(wx.Frame):
   4     def __init__(self, parent):
   5         wx.Frame.__init__(self, parent, -1, "Sketch Frame",
   6                 size=(800,600))
   7         self.sketch = SketchWindow(self, -1)
   8         self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
   9         self.statusbar = self.CreateStatusBar()
  10     def OnSketchMotion(self, event):
  11         self.statusbar.SetStatusText(str(event.GetPositionTuple()))
  12         event.Skip()
  13 if __name__ == '__main__':
  14     app = wx.PySimpleApp()
  15     frame = SketchFrame(None)
  16     frame.Show(True)
  17     app.MainLoop()

Мы подключили панель состояния, при этом фрейм также может обрабатывать событие wx.EVT_MOTION нашего окна рисования. Обработчик события устанавливает текст панели состояния на основании предоставляемых событием данных. Затем он вызывает метод Skip(), обеспечивающий вызов другого метода OnMotion(), в противном случае линия не будет нарисована.

Помещая на панель состояния дополнительные объекты, Вы можете сделать её похожей на любой другой виджет. Если Вы захотите вывести в панели состояния несколько текстовых элементов, можете создать множество текстовых полей. Чтобы использовать эту возможность, вызовите метод SetFieldsCount() с необходимым Вам количеством полей; значение по умолчанию – одно поле. После это, используйте SetStatusText() как в ранее приведенном примере, но со вторым аргументом, задающим номер устанавливаемого методом поля. Нумерация полей начинается с нуля. Если Вы не укажите номер поля, то по умолчанию устанавливается поле с номером 0. Вот почему работает предыдущий пример, где мы так и поступили.

По умолчанию, все поля имеют равную ширину. Тем не менее, это не всегда удобно. Для того чтобы отрегулировать размеры текстовых полей, wxPython предоставляет метод SetStatusWidth(). Этот метод принимает список целых значений, определяющих длину полей панели состояния. Данный список целых значений используется, чтобы надлежащим образом вычислять ширину полей. Если целое положительно, оно определяет фиксированную ширину поля. Если Вы хотите, чтобы ширина поля изменялась с фреймом, укажите отрицательное целое. Абсолютная величина отрицательного целого указывает относительный размер поля, выраженный количеством отводимых полю частей в общей ширине панели. Например, вызов statusbar.SetStatusWidth([-1, -2, -3]) отводит для самого правого поля половину ширины (3/6 части), центральная область получает треть ширины (2/6 части), а крайнее левое поле получает шестую часть ширины (1/6 часть). Результат показан на рисунке 6.2.

Рисунок 6.2 Пример панели состояния с полями в 1/6, 2/3 и 1/2 от общей ширины

Листинг 6.3 добавляет поддержку двух дополнительных полей панели состояния, одно из который показывает количество точек в текущей проведенной линии, а другое показывает количество линий в текущем чертеже. Этот листинг воспроизводит панель состояния, показанную на рисунке 6.2.

Листинг 6.3 Поддержка множества полей состояния

   1 import wx
   2 from example1 import SketchWindow
   3 class SketchFrame(wx.Frame):
   4     def __init__(self, parent):
   5         wx.Frame.__init__(self, parent, -1, "Sketch Frame",
   6                 size=(800,600))
   7         self.sketch = SketchWindow(self, -1)
   8         self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
   9         self.statusbar = self.CreateStatusBar()
  10         self.statusbar.SetFieldsCount(3)
  11         self.statusbar.SetStatusWidths([-1, -2, -3])
  12     def OnSketchMotion(self, event):
  13         self.statusbar.SetStatusText("Pos: %s" %
  14                 str(event.GetPositionTuple()), 0)
  15         self.statusbar.SetStatusText("Current Pts: %s" %
  16                 len(self.sketch.curLine), 1)
  17         self.statusbar.SetStatusText("Line Count: %s" %
  18                 len(self.sketch.lines), 2)
  19         event.Skip()
  20 if __name__ == '__main__':
  21     app = wx.PySimpleApp()
  22     frame = SketchFrame(None)
  23     frame.Show(True)
  24     app.MainLoop()

Класс wx.StatusBar позволяет Вам рассматривать поля панели состояния как стек типа «последний вошёл - первый вышел» (LIFO). Методы PushStatusText() и PopStatusText() позволять после временного отображения нового текста вернуться к предыдущему тексту поля панели состояния (правда, в демонстрационном приложении данной главы этого не требуется). Оба этих метода принимают дополнительный номер поля, поэтому они могут использоваться и в случае панели с множеством полей.

Таблица 6.3 приводит наиболее употребительные методы класса wx.StatusBar.

Таблица 6.3 Методы класса wx.StatusBar

Метод

Описание

GetFieldsCount() SetFieldsCount(count)

Свойство, определяющее количество полей в панели состояния.

GetStatusText(field=0) SetStatusText(text, field=0)

Свойство, определяющее выводимый в указанном поле текст. Индекс 0 (значение по умолчанию) указывает крайнее левое поле.

PopStatusText(field=0)

Извлекает текст из стека указанного поля панели состояния, замещая текст этого поля извлеченным значением.

PushStatusText(text, field=0)

Замещает вид указанного поля панели состояния данным текстом и помещает это новое значение в вершину стека данного поля.

SetStatusWidths(widths)

Берёт список целых значений и определяет ширину полей панели состояния. Положительное число указывает фиксированную ширину в пикселях, а отрицательное число указывает динамическую часть ширины пропорциональную абсолютной величине числа.

В главе 10 будут предоставлены дополнительные сведения о панелях состояния. А сейчас мы обсудим меню.

Как добавить подменю или меню с флажками?

В этом подразделе, мы представим два распространенных приёма для меню - это подменю и меню в виде флажков (checked menu) или переключателей (radio menu). Подменю – это меню, которое доступно в одном из меню верхнего уровня. Меню-флажок или меню-переключатель – это группа пунктов меню, которые ведут себя подобно группе флажков или переключателей. Рисунок 6.3 показывает строку меню, включающее подменю с пунктами в виде переключателей.

Для создания подменю, сформируйте его, как и любое другое меню, и добавьте в родительское меню при помощи метода wx.Меню.AppendMenu().

Пункты меню с украшениями в виде флажков или переключателей могут также создаваться при помощи методов AppendCheckItem() и AppendRadioItem() класса wx.Menu, или передавая атрибутом типа в конструкторе wx.MenuItem одно из следующих значений: wx.ITEM_NORMAL, wx.ITEM_CHECKBOX или wx.ITEM_RADIO. Пункт меню в виде флажка отображает отметку, которая автоматически включается или выключается при выборе этого пункта; Вам не нужно вручную управлять этим процессом. Начальное значение пункта меню в виде флажка - выключено. Пункты меню в виде переключателей сгруппированы. Предполагается, что последовательные пункты-переключатели будут частью одной и той же группы (разделитель меню разрывает группу). По умолчанию, отмечен самый верхний элемент группы, а при выборе любого элемента группы отметка автоматически передаётся на соответствующий выбранный пункт. Для того чтобы программно отметить пункт меню, используйте метод Check(id, bool) класса wx.Menu, где id – wxPython-идентификатор того пункта меню, который нужно изменить, а bool определяет состояние этого пункта.

Рисунок 6.3 Меню и подменю с пунктами-переключателями

Листинг 6.4 добавляет меню к фрейму приложения. Функциональное назначение этого кода – развитие предшествующего рефакторизованного кода из листинга 5.5. Для создания подменю здесь усовершенствован формат данных, а код создания при необходимости создает подменю рекурсивно. Также добавлена поддержка меню с переключателями и флажками.

Листинг 6.4 Поддержка в приложении меню

   1 import wx
   2 from example1 import SketchWindow
   3 class SketchFrame(wx.Frame):
   4     def __init__(self, parent):
   5         wx.Frame.__init__(self, parent, -1, "Sketch Frame",
   6                      size=(800,600))
   7         self.sketch = SketchWindow(self, -1)
   8         self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
   9         self.initStatusBar()                                              # (1) Небольшой рефакторинг                                                
  10         self.createMenuBar()
  11                                                                             
  12     def initStatusBar(self):
  13         self.statusbar = self.CreateStatusBar()
  14         self.statusbar.SetFieldsCount(3)
  15         self.statusbar.SetStatusWidths([-1, -2, -3])
  16     def OnSketchMotion(self, event):
  17         self.statusbar.SetStatusText("Pos: %s" %
  18                      str(event.GetPositionTuple()), 0)
  19         self.statusbar.SetStatusText("Current Pts: %s" %
  20                      len(self.sketch.curLine), 1)
  21         self.statusbar.SetStatusText("Line Count: %s" %
  22                      len(self.sketch.lines), 2)
  23         event.Skip()
  24                                                                                  
  25                                                                                  
  26     def menuData(self):                                                    # (2) Идентификация данных меню
  27         return [("&File", (
  28                             ("&New", "New Sketch file", self.OnNew),
  29                             ("&Open", "Open sketch file", self.OnOpen),
  30                             ("&Save", "Save sketch file", self.OnSave),
  31                             ("", "", ""),
  32                             ("&Color", (
  33                                  ("&Black", "", self.OnColor,
  34                                        wx.ITEM_RADIO),
  35                                  ("&Red", "", self.OnColor,
  36                                        wx.ITEM_RADIO),
  37                                  ("&Green", "", self.OnColor,
  38                                        wx.ITEM_RADIO),
  39                                  ("&Blue", "", self.OnColor,
  40                                        wx.ITEM_RADIO))),
  41                             ("", "", ""),
  42                         ("&Quit", "Quit", self.OnCloseWindow)))]
  43     def createMenuBar(self):
  44         menuBar = wx.MenuBar()
  45         for eachMenuData in self.menuData():
  46               menuLabel = eachMenuData[0]
  47               menuItems = eachMenuData[1]
  48               menuBar.Append(self.createMenu(menuItems), menuLabel)
  49         self.SetMenuBar(menuBar)
  50     def createMenu(self, menuData):
  51         menu = wx.Menu()                                                                        
  52         for eachItem in menuData:                                                               
  53               if len(eachItem) == 2:                                        # (3) Создание подменю
  54                      label = eachItem[0]
  55                      subMenu = self.createMenu(eachItem[1])
  56                      menu.AppendMenu(wx.NewId(), label, subMenu)
  57               else:
  58                      self.createMenuItem(menu, *eachItem)
  59         return menu
  60     def createMenuItem(self, menu, label, status, handler,
  61                                 kind=wx.ITEM_NORMAL):
  62         if not label:
  63               menu.AppendSeparator()
  64               return
  65         menuItem = menu.Append(-1, label, status, kind)                     # (4) Создание элементов меню с типом
  66         self.Bind(wx.EVT_MENU, handler, menuItem)
  67         
  68     def OnNew(self, event): pass                                                                 
  69     def OnOpen(self, event): pass                                                                
  70     def OnSave(self, event): pass
  71     def OnColor(self, event):                                               # (5) Обработка изменения цвета
  72         menubar = self.GetMenuBar()
  73         itemId = event.GetId()
  74         item = menubar.FindItemById(itemId)
  75         color = item.GetLabel()
  76         self.sketch.SetColor(color)
  77     def OnCloseWindow(self, event):
  78         self.Destroy()
  79 if __name__ == '__main__':
  80     app = wx.PySimpleApp()
  81     frame = SketchFrame(None)
  82     frame.Show(True)
  83     app.MainLoop()

(1) Теперь метод init более функционален и мы инкапсулировали инициализацию панели состояния в отдельный метод.

(2) Здесь определяется формат данных меню в виде (метка, (пункты)), где каждый пункт – это или список вида (метка, текст панели состояния, обработчик, опциональный тип) или меню с меткой и пунктами. Для определения того, какие подпункты данных являются меню или пунктами меню, помните, что меню содержит 2, а пункты 3 или 4 элемента данных. В промышленной системе, где данные имеют более сложную структуру, я рекомендую использовать XML или какой-нибудь другой внешний формат.

(3) Когда данные имеет длину 2, что означает подменю, производится разделение меню тем же способом, как и для меню верхнего уровня, при этом рекурсивно вызывается добавляющий подменю метод createMenu.

(4) При такой реализации меню проще добавить в конструктор wx.MenuItem параметр типа, чем использовать специальные методы wx.Menu.

(5) Для обработки изменения цвета у всех пунктов меню установлен метод OnColor, при этом не требуется установки отдельных обработчиков для каждого пункта. В этом случае код получает идентификатор (id) пункта из события и использует метод FindItemById() для получения соответствующего пункта меню (обратите внимание, что от нас не требуется поддерживать отдельную хеш-таблицу идентификаторов пунктов – мы используем полосу меню как структуру данных). Этот метод полагает, что метка пункта меню является наименованием цвета wxPython и он передает эту метку окну рисования, где изменяется перо.

Как добавить панель инструментов?

Строка меню и панель инструментов часто тесно взаимосвязаны, причем большая часть или даже вся функциональность панели инструментов доступна и через пункты меню. В wxPython это сходство расширено кнопками панели инструментов, генерирующими при нажатии события wx.EVT_MENU, что облегчает использование одних и тех же методов, как для обработки выбора пунктов меню, так и щелчков на панели инструментов. Панель инструментов wxPython – это экземпляр класса wx.ToolBar, и как мы увидели в главе 2, она может быть создана при помощи метода фрейма CreateToolBar(). Подобно панели состояния, инструментальная панель автоматически меняет размеры вместе с родительским фреймом. Инструментальная панель подобна другим окнам wxPython в том, что она может иметь произвольные дочерние окна. Панели инструментов содержат также методы для создания инструментальных кнопок. Рисунок 6.4 показывает часть окна с панелью инструментов, которая дублирует функциональность созданного нами меню.

Рисунок 6.4 Типичная панель инструментов с регулярными и переключающими кнопками

Как и в коде меню, цветные битовые изображения представляют собой переключатели, и переключение одного из них приводит к изменению выделения. В листинге 6.5 мы не повторяем код меню, а включили новые и измененные методы SketchFrame.

Листинг 6.5 Добавление в приложение панели инструментов

   1 def __init__(self, parent):
   2     wx.Frame.__init__(self, parent, -1, "Sketch Frame",
   3             size=(800,600))
   4     self.sketch = SketchWindow(self, -1)
   5     self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)
   6     self.initStatusBar()
   7     self.createMenuBar() 
   8     self.createToolBar()
   9                                                       
  10 def createToolBar(self):                                  # (1) Создание панели инструментов
  11     toolbar = self.CreateToolBar()
  12     for each in self.toolbarData():
  13         self.createSimpleTool(toolbar, *each)
  14     toolbar.AddSeparator()
  15     for each in self.toolbarColorData():
  16         self.createColorTool(toolbar, each)
  17     toolbar.Realize()                                     # (2) Реализация панели инструментов
  18     
  19 def createSimpleTool(self, toolbar, label, filename,     
  20         help, handler):                                   # (3) Создание простых инструментов 
  21     if not label:
  22         toolbar.AddSeparator()                                        
  23         return
  24                                                          
  25     bmp = wx.Image(filename,
  26                                                          
  27             wx.BITMAP_TYPE_BMP).ConvertToBitmap()
  28     tool = toolbar.AddSimpleTool(-1, bmp, label, help)
  29     self.Bind(wx.EVT_MENU, handler, tool)
  30 def toolbarData(self):
  31     return (("New", "new.bmp", "Create new sketch",
  32                  self.OnNew),
  33             ("", "", "", ""),
  34             ("Open", "open.bmp", "Open existing sketch",
  35                  self.OnOpen),
  36             ("Save", "save.bmp", "Save existing sketch",
  37                  self.OnSave))
  38                                               
  39                                               
  40 def createColorTool(self, toolbar, color):                # (4) Создание инструментов выбора цвета
  41     bmp = self.MakeBitmap(color)
  42     newId = wx.NewId()
  43     tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color)
  44     self.Bind(wx.EVT_MENU, self.OnColor, tool)
  45     
  46 def MakeBitmap(self, color):                              # (5) Создание сплошного битового изображения
  47     bmp = wx.EmptyBitmap(16, 15)
  48     dc = wx.MemoryDC()          
  49     dc.SelectObject(bmp)        
  50     dc.SetBackground(wx.Brush(color))
  51     dc.Clear()
  52     dc.SelectObject(wx.NullBitmap)
  53     return bmp
  54 
  55 def toolbarColorData(self):
  56     return ("Black", "Red", "Green", "Blue")
  57 
  58 def OnColor(self, event):
  59     menubar = self.GetMenuBar()
  60     itemId = event.GetId()
  61     item = menubar.FindItemById(itemId)
  62     if not item:                                           # (6) Изменение цвета при щелчке на панели инструментов
  63         toolbar = self.GetToolBar()
  64         item = toolbar.FindById(itemId)
  65         color = item.GetShortHelp()
  66     else:
  67         color = item.GetLabel()
  68     self.sketch.SetColor(color)

(1) Код для панели инструментов аналогичен по настройке коду для меню в том, что он также управляется на основе данных. Тем не менее, в этом случае, мы установили другие циклы для обычных кнопок и для кнопок-переключателей, поскольку они не внедрены в панель инструментов.

(2) Метод Realize() размещает объекты внутри инструментальной панели. Он должен быть вызван до отображения панели, и повторно вызывается, если в панель добавляются или удаляются любые инструменты.

(3) Этот метод похож на метод создания пунктов меню. Основное отличие в том, что для инструментов панели требуются битовые изображения. В данном случае, мы разместили три основных битовых изображения в том же каталоге, где размещается код примера. В конце метода мы назначаем тоже событие wx.EVT_MENU, которое использовано для пунктов меню. Для расшифровки метода AddTool, который обеспечивает более специфические функции инструментов, используйте таблицу 6.4.

(4) Инструментальные кнопки для выбора цвета создаются аналогично простым инструментам, просто панели разным способом сообщается о том, что они являются кнопками-переключателями. Сплошные битовые изображения созданы методом MakeBitmap().

(5) Этот метод создает сплошное (solid) битовое изображение соответствующего размера, создавая обычное битовое изображение wx.EmptyBitmap, подключая к нему wx.MemoryDC, и очищая битовое изображение нужным цветом при помощи фоновой кисти.

(6) Небольшое дополнение к методу OnColor() ищет в панели соответствующий инструмент и устанавливает необходимый цвет. Однако в коде имеется одна проблема - изменение цвета через пункт меню не изменяет состояние переключателей панели инструментов, и наоборот.

Панели инструментов имеют возможность гибкого управления событиями, которую не имеют пункты меню. Они могут сгенерировать событие типа wx.EVT_TOOL_RCLICKED, когда на инструменте нажимается правая кнопка мыши. К тому же, панели инструментов имеют несколько различных стилей, которые передаются как битовые изображения в качестве аргументов в CreateToolBar(). Таблица 6.4 перечисляет некоторые стили панели инструментов.

Таблица 6.4 Стили класса wx.ToolBar

Стиль

Описание

wx.TB_3DBUTTONS

Представляет инструменты в виде 3D

wx.TB_HORIZONTAL

Этот стиль по умолчанию размещает панель инструментов горизонтально

wx.TB_NOICONS

Не отображает битовые изображения для каждого инструмента

wx.TB_TEXT

Панель инструментов будет показывать короткий вспомогательный текст вместе со встроенными битовыми изображениями

wx.TB_VERTICAL

Размещает панель инструментов вертикально

Панели инструментов несколько сложнее панелей состояния. Таблица 6.5 отображает некоторые широко используемые методы панелей инструментов.

Таблица 6.5 Методы общего назначения класса wx.ToolBar

Функция

Описание

AddControl(control)

Добавляет в панель инструментов произвольный управляющий виджет wxPython. Также смотрите связанный метод InsertControl().

AddSeparator()

Устанавливает между инструментами пустое пространство.

AddSimpleTool(id, bitmap, shortHelpString="", kind=wx.ITEM_NORMAL) AddTool(id, bitmap, bitmap2=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelpString="", longHelpString="", clientData=None)

Добавляет в панель инструментов простую инструментальную кнопку с данным битовым изображением. В качестве подсказки (tooltip) отображается shortHelpString. Параметр kind может иметь значение wx.ITEM_NORMAL, wx.ITEM_CHECKBOX или wx.ITEM_RADIO. Дополнительные параметры для простых инструментов: bitmap2 отображается, когда инструмент нажат; longHelpString отображается на панели состояния, когда указатель находится на инструменте, а clientData может быть использован, чтобы ассоциировать с инструментом произвольную часть данных. Имеется связанный метод InsertTool().

AddCheckTool(...)

Добавляет инструмент в виде флажка, с теми же параметрами, как и у AddTool().

AddRadioTool(...)

Добавляет инструмент в виде переключателя, с теми же параметрами, как и у AddTool(). Непрерывная последовательность переключателей объединяется в группу.

DeleteTool(toolId) DeleteToolByPosition(x, y)

Удаляет инструмент с данным идентификатором toolId, или тот, который отображен в данной точке.

FindControl(toolId) FindToolForPosition(x, y)

Находит и возвращает инструмент с данным идентификатором toolId, или тот, который отображен в данной точке.

ToggleTool(toolId, toggle)

Если инструмент с указанным идентификатором toolId является переключателем или флажком, этот метод устанавливает переключатель данного инструмента, полагаясь на булевое значение аргумента toggle.

В следующем разделе мы покажем Вам, как использовать стандартные диалоги, для того чтобы получать информацию от пользователя. В большинстве операционных систем Вы можете использовать стандартные диалоги, чтобы обеспечивать Вашего пользователя знакомым интерфейсом при решении общих задач, таких как, например, выбор файла.

Получение стандартной информации

Перевод: Савицкий Юрий

Книги/WxPythonInAction/Работа с базовыми составными блоками (последним исправлял пользователь alafin 2010-05-30 08:02:59)