├── .gitattributes ├── 1.介绍.md ├── 10.图形.md ├── 11.自定义组件.md ├── 12.程序骨架.md ├── 13.俄罗斯方块.md ├── 2.第一步.md ├── 3.菜单和工具栏.md ├── 4.布局.md ├── 5.事件.md ├── 6.对话框.md ├── 7.组件.md ├── 8.高级组件.md ├── 9.拖放.md ├── README.md └── assets ├── 1527417412183.png ├── 1527417584346.png ├── 1527417657992.png ├── 1527561443370.png ├── 1527561475646.png ├── 1527561515784.png ├── 1527561810112.png ├── absolute.png ├── aline.png ├── bars.jpg ├── base.jpg ├── border.png ├── browser.png ├── brushes.png ├── burning.png ├── buttonwid.png ├── calculator.png ├── checkbox.png ├── checkmenuitem.png ├── colours.png ├── combobox.png ├── containers.jpg ├── contextmenu.png ├── coordinates.png ├── cpuwidget.png ├── customdialog.png ├── custompatterns.png ├── dynamic.jpg ├── filehunter.png ├── focusevent.png ├── gauge.png ├── gdi2.png ├── gotoclass.png ├── gradients.png ├── helpwindow.png ├── htmlwin.png ├── hunter.png ├── hyperlink.png ├── iconsshortcuts.png ├── inheritance.png ├── joinscaps.png ├── lines.png ├── listbox2.png ├── messagebox.png ├── modules.jpg ├── moveevent.png ├── newclass.png ├── pens.png ├── player.png ├── points.png ├── radiobutton.png ├── region_operations.png ├── rename.png ├── repository.png ├── review.png ├── ruler.png ├── shapes.png ├── simple.png ├── simplemenu.png ├── simpletoolbar.png ├── slider.png ├── spinctrl.png ├── spreadsheet.png ├── standardidentifiers.png ├── star.png ├── staticbox.png ├── staticline.png ├── statictext.png ├── staticwidgets.jpg ├── statusbar.png ├── submenu.png ├── tetris.png ├── tetrominoes.png ├── togglebuttons.png ├── toolbars.png ├── toplevel.jpg └── undoredo.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /1.介绍.md: -------------------------------------------------------------------------------- 1 | ## wxPython 简介 2 | 3 | wxPython 是一个用于创建桌面 GUI 应用程序的跨平台工具包。 wxPython 的主要作者是 *Robin Dunn*。 借助wxPython,开发人员可以在 Windows,Mac 和各种 Unix 系统上创建应用程序。 wxPython 封装了 wxWidgets,而 wxWidgets 是一个成熟的跨平台 C ++ 库。 4 | 5 | ## wxPython 模块 6 | 7 | wxPython 由五个基本模块组成。 8 | 9 | ![img](assets/modules.jpg) 10 | 11 | 12 | 13 | Controls 模块:提供了图形应用程序中常见的 widgets。例如 Button、Toolbar 或 Notebook。Widgets 在 Windows 上叫做 controls。 14 | 15 | Core 模块:由开发的基本中用到的基本类组成。这些类包括 Object 类,它是所有类的基类;Sizers,用于 widget 布局;Events;像 Point 和 Rectangle 这样的基本几何类。 16 | 17 | GDI 模块:The Graphics Device Interface,图形设备接口。是用于绘制 widgets 的一组类。 18 | 19 | Misc 模块:包含各种其他类和模块功能。 这些类用于记录日志,应用程序配置,系统设置,显示或操纵杆。 20 | 21 | Windows 模块:由形成应用程序的各种窗口组成,例如 面板 Panel,对话框 Dialog,框架 Frame 或滚动窗口 Scrolled Window。 22 | 23 | ## wxPython API 24 | 25 | wxPython API 是一组方法和对象。 组件 Widgets 是 GUI 应用程序的重要组成部分。组件 Widgets 在 Windows 下被称作控件 controls。 我们大致可以将程序员分成两组:编写应用程序或库。 在我们的例子中,wxPython 是应用程序员用来编写应用程序的库。 从技术上讲,wxPython 是一个名为 wxWidgets 的 C ++ GUI API 的封装。 所以它不是一个本地 API; 即它不是直接用 Python 编写的。 26 | 27 | 在 wxPython 中,我们有很多组件。 这些可以分成一些逻辑组。 28 | 29 | ### 基本 Widgets 30 | 31 | 这些组件为派生组件提供基本功能。 他们被称为祖先 ancestors。 他们通常不直接使用。 32 | 33 | ![img](assets/base.jpg) 34 | 35 | ### 顶层 Widgets 36 | 37 | 这些组件彼此独立存在。 38 | 39 | ![img](assets/toplevel.jpg) 40 | 41 | ### Containers 42 | 43 | 容器 Containers 包含其他组件 Widgets。 44 | 45 | ![img](assets/containers.jpg) 46 | 47 | ### 动态 Widgets 48 | 49 | 这些组件可以由用户编辑。 50 | 51 | ![img](assets/dynamic.jpg) 52 | 53 | ### 静态 Static Widgets 54 | 55 | 这些组件显示信息。 它们不能由用户编辑。 56 | 57 | ![img](assets/staticwidgets.jpg) 58 | 59 | ### 其他 Widgets 60 | 61 | 这些组件在应用程序中实现状态栏,工具栏和菜单栏。 62 | 63 | ![img](assets/bars.jpg) 64 | 65 | ### 继承关系 66 | 67 | wxPython 中的组件 widgets 之间有特定的关系。 这种关系是通过继承来发展的。 继承是面向对象编程的关键部分。 组件形成一个层次结构。 组件可以继承其他组件的功能。现有的类被称为基类,父母或祖先。 继承我们的组件称为派生组件,子组件或后代。 68 | 69 | ![img](assets/inheritance.png) 70 | 71 | 假设我们在应用程序中使用了一个按钮组件按钮构件继承自四个不同的基类。 最接近的类是 `wx.Control` 类。 按钮组件是一种小窗口。所有出现在屏幕上的组件都是窗口。 因此它们从 `wx.Window` 类继承而来。 有些 objects 是不可见的。 比如 sizers,设备上下文 device context  或 locale 对象。 也有可见的类,但它们不是窗口。 例如,颜色对象 a colour object,插入符号对象 caret object 或游标对象 cursor object。 并非所有的组件都是控件。 例如 `wx.Dialog` 不是一种控件。控件是放置在其他称为容器 Containers 的组件上的组件。 这就是为什么我们有一个单独的 `wx.Control` 基类。 72 | 73 | 每个窗口都能对事件做出反应。 按钮组件也是如此。 通过点击按钮,我们启动 `wx.EVT_COMMAND_BUTTON_CLICKED` 事件。 按钮组件通过 `wx.Window` 类继承 `wx.EvtHandler`。 每个对事件作出反应的组件都必须从 `wx.EvtHandler` 类继承。 最后,所有对象都从 `wx.Object` 类继承。 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /11.自定义组件.md: -------------------------------------------------------------------------------- 1 | # Creating custom widgets 2 | 3 | 工具包 Toolkits 通常只提供最常用的组件,如按钮 buttons、文本组件 text widgets、滚动条 scrollbars、滑块 sliders 等。没有工具包可以提供所有可能的组件。 wxPython 有很多组件;更多专用的组件需要客户端程序员自己创建。 4 | 5 | 自定义组件有两种创建方式:我们修改或增强现有的组件,或者我们从头创建一个自定义组件。 6 | 7 | ## A hyperlink widget 8 | 9 | 第一个例子将创建一个超链接。 超链接组件将基于现有的 `wx.lib.stattext.GenStaticText` 组件。 10 | 11 | **hyperlink.py** 12 | 13 | ```python 14 | #!/usr/bin/env python3 15 | # -*- coding: utf-8 -*- 16 | 17 | """ 18 | ZetCode wxPython tutorial 19 | 20 | This program creates a Hyperlink widget. 21 | 22 | author: Jan Bodnar 23 | website: zetcode.com 24 | last edited: May 2018 25 | """ 26 | 27 | import wx 28 | from wx.lib.stattext import GenStaticText 29 | import webbrowser 30 | 31 | 32 | class Link(GenStaticText): 33 | 34 | def __init__(self, *args, **kw): 35 | super(Link, self).__init__(*args, **kw) 36 | 37 | self.font1 = wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana') 38 | self.font2 = wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana') 39 | 40 | self.SetFont(self.font2) 41 | self.SetForegroundColour('#0000ff') 42 | 43 | self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent) 44 | self.Bind(wx.EVT_MOTION, self.OnMouseEvent) 45 | 46 | def SetUrl(self, url): 47 | 48 | self.url = url 49 | 50 | 51 | def OnMouseEvent(self, e): 52 | 53 | if e.Moving(): 54 | 55 | self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) 56 | self.SetFont(self.font1) 57 | 58 | elif e.LeftUp(): 59 | 60 | webbrowser.open_new(self.url) 61 | 62 | else: 63 | self.SetCursor(wx.NullCursor) 64 | self.SetFont(self.font2) 65 | 66 | e.Skip() 67 | 68 | 69 | class Example(wx.Frame): 70 | 71 | def __init__(self, *args, **kw): 72 | super(Example, self).__init__(*args, **kw) 73 | 74 | self.InitUI() 75 | 76 | def InitUI(self): 77 | 78 | panel = wx.Panel(self) 79 | 80 | vbox = wx.BoxSizer(wx.VERTICAL) 81 | hbox = wx.BoxSizer(wx.HORIZONTAL) 82 | 83 | st = GenStaticText(panel, label='Go to web site:') 84 | st.SetFont(wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana')) 85 | hbox.Add(st, flag=wx.LEFT, border=20) 86 | 87 | link_wid = Link(panel, label='ZetCode') 88 | link_wid.SetUrl('http://www.zetcode.com') 89 | hbox.Add(link_wid, flag=wx.LEFT, border=20) 90 | 91 | vbox.Add(hbox, flag=wx.TOP, border=30) 92 | panel.SetSizer(vbox) 93 | 94 | self.SetTitle('A Hyperlink') 95 | self.Centre() 96 | 97 | 98 | def main(): 99 | 100 | app = wx.App() 101 | ex = Example(None) 102 | ex.Show() 103 | app.MainLoop() 104 | 105 | 106 | if __name__ == '__main__': 107 | main() 108 | ``` 109 | 110 | 此超链接组件基于现有的组件。在这个例子中,我们不绘制任何东西,我们只是使用一个现有的组件,我们稍微修改一下。 111 | 112 | ```python 113 | from wx.lib.stattext import GenStaticText 114 | import webbrowser 115 | ``` 116 | 117 | 在这里我们导入了我们从中派生出的超链接组件和 webbrowser 模块的基础组件。webbrowser模块是 Python 的标准模块。 我们将使用它在默认浏览器中打开链接。 118 | 119 | ```python 120 | self.SetFont(self.font2) 121 | self.SetForegroundColour('#0000ff') 122 | ``` 123 | 124 | 创建超链接组件的想法很简单。 我们从基础 `wx.lib.stattext.GenStaticText` 组件类继承。 所以我们有一个文本组件。然后我们修改一下。 我们改变文字的字体和颜色。 125 | 126 | ```python 127 | if e.Moving(): 128 | 129 | self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) 130 | self.SetFont(self.font1) 131 | ``` 132 | 133 | 如果我们将鼠标指针悬停在链接上,我们给字体带下划线,并将鼠标指针更改为手形光标。 134 | 135 | ```python 136 | elif e.LeftUp(): 137 | 138 | webbrowser.open_new(self.url) 139 | ``` 140 | 141 | 如果我们点击链接,我们会在默认浏览器中打开链接。 142 | 143 | ![Hyperlink widget](assets/hyperlink.png) 144 | 145 | ## Burning widget 146 | 147 | 这是我们从头开始创建的组件示例。我们在窗口的底部放置一个 `wx.Panel` 并手动绘制整个组件。如果您曾烧制过 CD 或 DVD,您已经看到了这种组件。 148 | 149 | **burning.py** 150 | 151 | ```python 152 | #!/usr/bin/env python3 153 | # -*- coding: utf-8 -*- 154 | 155 | """ 156 | ZetCode wxPython tutorial 157 | 158 | This program creates a Burning widget. 159 | 160 | author: Jan Bodnar 161 | website: zetcode.com 162 | last edited: May 2018 163 | """ 164 | 165 | import wx 166 | 167 | class Burning(wx.Panel): 168 | def __init__(self, parent): 169 | wx.Panel.__init__(self, parent, size=(-1, 30), style=wx.SUNKEN_BORDER) 170 | 171 | self.parent = parent 172 | self.font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, 173 | wx.FONTWEIGHT_NORMAL, False, 'Courier 10 Pitch') 174 | 175 | self.Bind(wx.EVT_PAINT, self.OnPaint) 176 | self.Bind(wx.EVT_SIZE, self.OnSize) 177 | 178 | 179 | def OnPaint(self, e): 180 | 181 | num = range(75, 700, 75) 182 | dc = wx.PaintDC(self) 183 | dc.SetFont(self.font) 184 | w, h = self.GetSize() 185 | 186 | self.cw = self.parent.GetParent().cw 187 | 188 | step = int(round(w / 10.0)) 189 | 190 | j = 0 191 | 192 | till = (w / 750.0) * self.cw 193 | full = (w / 750.0) * 700 194 | 195 | if self.cw >= 700: 196 | 197 | dc.SetPen(wx.Pen('#FFFFB8')) 198 | dc.SetBrush(wx.Brush('#FFFFB8')) 199 | dc.DrawRectangle(0, 0, full, 30) 200 | dc.SetPen(wx.Pen('#ffafaf')) 201 | dc.SetBrush(wx.Brush('#ffafaf')) 202 | dc.DrawRectangle(full, 0, till-full, 30) 203 | else: 204 | 205 | dc.SetPen(wx.Pen('#FFFFB8')) 206 | dc.SetBrush(wx.Brush('#FFFFB8')) 207 | dc.DrawRectangle(0, 0, till, 30) 208 | 209 | 210 | dc.SetPen(wx.Pen('#5C5142')) 211 | 212 | for i in range(step, 10*step, step): 213 | 214 | dc.DrawLine(i, 0, i, 6) 215 | width, height = dc.GetTextExtent(str(num[j])) 216 | dc.DrawText(str(num[j]), i-width/2, 8) 217 | j = j + 1 218 | 219 | def OnSize(self, e): 220 | 221 | self.Refresh() 222 | 223 | 224 | class Example(wx.Frame): 225 | 226 | def __init__(self, *args, **kwargs): 227 | super(Example, self).__init__(*args, **kwargs) 228 | 229 | self.InitUI() 230 | 231 | def InitUI(self): 232 | 233 | self.cw = 75 234 | 235 | panel = wx.Panel(self) 236 | CenterPanel = wx.Panel(panel) 237 | 238 | self.sld = wx.Slider(CenterPanel, value=75, maxValue=750, size=(200, -1), 239 | style=wx.SL_LABELS) 240 | 241 | vbox = wx.BoxSizer(wx.VERTICAL) 242 | hbox = wx.BoxSizer(wx.HORIZONTAL) 243 | hbox2 = wx.BoxSizer(wx.HORIZONTAL) 244 | hbox3 = wx.BoxSizer(wx.HORIZONTAL) 245 | 246 | self.wid = Burning(panel) 247 | hbox.Add(self.wid, 1, wx.EXPAND) 248 | 249 | hbox2.Add(CenterPanel, 1, wx.EXPAND) 250 | hbox3.Add(self.sld, 0, wx.LEFT|wx.TOP, 35) 251 | 252 | CenterPanel.SetSizer(hbox3) 253 | 254 | vbox.Add(hbox2, 1, wx.EXPAND) 255 | vbox.Add(hbox, 0, wx.EXPAND) 256 | 257 | self.Bind(wx.EVT_SCROLL, self.OnScroll) 258 | 259 | panel.SetSizer(vbox) 260 | 261 | self.sld.SetFocus() 262 | 263 | self.SetTitle("Burning widget") 264 | self.Centre() 265 | 266 | def OnScroll(self, e): 267 | 268 | self.cw = self.sld.GetValue() 269 | self.wid.Refresh() 270 | 271 | 272 | def main(): 273 | 274 | app = wx.App() 275 | ex = Example(None) 276 | ex.Show() 277 | app.MainLoop() 278 | 279 | 280 | if __name__ == '__main__': 281 | main() 282 | ``` 283 | 284 | 此组件以图形方式显示介质的总容量和可用空间。组件由滑块 slider 控制。 我们的自定义组件的最小值为0,最大值为 750。如果值达到 700,我们开始绘制红色。 这通常表示烧制过量。 285 | 286 | ```python 287 | w, h = self.GetSize() 288 | self.cw = self.parent.GetParent().cw 289 | ... 290 | till = (w / 750.0) * self.cw 291 | full = (w / 750.0) * 700 292 | ``` 293 | 294 | 我们动态地绘制组件。窗口越大,烧制的组件就越大。 反之亦然。 这就是为什么我们必须计算我们绘制自定义组件的 `wx.Panel` 的尺寸。 `till` 参数确定要绘制的总尺寸。 该值来自滑块组件。这是整个区域 area 的一部分。 `full` 参数决定了我们开始用红色绘制的点。 注意使用浮点数算术。这是为了达到更高的精度。 295 | 296 | 实际绘图由三个步骤组成。我们绘制黄色或红色和黄色的矩形。然后我们绘制垂直线,它将组件分成几个部分。 最后,我们画出数字,指示介质的使用量。 297 | 298 | ```python 299 | def OnSize(self, e): 300 | 301 | self.Refresh() 302 | ``` 303 | 304 | 每次调整窗口大小时,我们都会刷新窗口组件。 这会导致组件重新绘制自己。 305 | 306 | ```python 307 | def OnScroll(self, e): 308 | 309 | self.cw = self.sld.GetValue() 310 | self.wid.Refresh() 311 | ``` 312 | 313 | 如果我们滚动滑块,我们会得到当前实际值并将其保存到 `self.cw` 参数中。当烧制组件被绘制时,这个值被使用。 然后我们让该组件被重绘。 314 | 315 | ![Burning widget](assets/burning.png) 316 | 317 | ## The CPU widget 318 | 319 | 有系统应用程序可以测量系统资源,如温度、内存或 CPU 消耗。专用组件的创建使应用程序更具吸引力。 320 | 321 | 以下组件经常用于系统应用程序。 322 | 323 | **cpu.py** 324 | 325 | ```python 326 | #!/usr/bin/env python3 327 | # -*- coding: utf-8 -*- 328 | 329 | """ 330 | ZetCode wxPython tutorial 331 | 332 | This program creates a CPU widget. 333 | 334 | author: Jan Bodnar 335 | website: zetcode.com 336 | last edited: May 2018 337 | """ 338 | 339 | import wx 340 | 341 | 342 | class CPU(wx.Panel): 343 | 344 | def __init__(self, parent): 345 | wx.Panel.__init__(self, parent, size=(80, 110)) 346 | 347 | self.parent = parent 348 | self.SetBackgroundColour('#000000') 349 | self.Bind(wx.EVT_PAINT, self.OnPaint) 350 | 351 | 352 | def OnPaint(self, e): 353 | 354 | dc = wx.PaintDC(self) 355 | 356 | dc.SetDeviceOrigin(0, 100) 357 | dc.SetAxisOrientation(True, True) 358 | 359 | pos = self.parent.GetParent().GetParent().sel 360 | rect = pos / 5 361 | 362 | for i in range(1, 21): 363 | 364 | if i > rect: 365 | 366 | dc.SetBrush(wx.Brush('#075100')) 367 | dc.DrawRectangle(10, i*4, 30, 5) 368 | dc.DrawRectangle(41, i*4, 30, 5) 369 | 370 | else: 371 | dc.SetBrush(wx.Brush('#36ff27')) 372 | dc.DrawRectangle(10, i*4, 30, 5) 373 | dc.DrawRectangle(41, i*4, 30, 5) 374 | 375 | 376 | class Example(wx.Frame): 377 | 378 | def __init__(self, *args, **kwargs): 379 | super(Example, self).__init__(*args, **kwargs) 380 | 381 | self.InitUI() 382 | 383 | def InitUI(self): 384 | 385 | self.sel = 0 386 | 387 | panel = wx.Panel(self) 388 | centerPanel = wx.Panel(panel) 389 | 390 | self.cpu = CPU(centerPanel) 391 | 392 | hbox = wx.BoxSizer(wx.HORIZONTAL) 393 | 394 | self.slider = wx.Slider(panel, value=self.sel, maxValue=100, size=(-1, 100), 395 | style=wx.VERTICAL | wx.SL_INVERSE) 396 | self.slider.SetFocus() 397 | 398 | hbox.Add(centerPanel, 0, wx.LEFT | wx.TOP, 20) 399 | hbox.Add(self.slider, 0, wx.LEFT | wx.TOP, 30) 400 | 401 | self.Bind(wx.EVT_SCROLL, self.OnScroll) 402 | 403 | panel.SetSizer(hbox) 404 | 405 | self.SetTitle("CPU") 406 | self.Centre() 407 | 408 | 409 | def OnScroll(self, e): 410 | 411 | self.sel = e.GetInt() 412 | self.cpu.Refresh() 413 | 414 | 415 | def main(): 416 | 417 | app = wx.App() 418 | ex = Example(None) 419 | ex.Show() 420 | app.MainLoop() 421 | 422 | 423 | if __name__ == '__main__': 424 | main() 425 | ``` 426 | 427 | 我们创建一个黑色面板。然后我们在这个面板上绘制小矩形。矩形的颜色取决于滑块的值。颜色可以是深绿色 dark green 或亮绿色 bright green。 428 | 429 | ```python 430 | dc.SetDeviceOrigin(0, 100) 431 | dc.SetAxisOrientation(True, True) 432 | ``` 433 | 434 | 在这里,我们将默认坐标系更改为笛卡尔坐标系。 这是为了使绘图直观。 435 | 436 | ```python 437 | pos = self.parent.GetParent().GetParent().sel 438 | rect = pos / 5 439 | ``` 440 | 441 | 这里我们得到了 sizer 的值。 我们每列有 20 个矩形。 滑块有 100 个数字。 rect 参数将滑块值转换为将以亮绿色绘制的矩形。 442 | 443 | ```python 444 | for i in range(1, 21): 445 | 446 | if i > rect: 447 | dc.SetBrush(wx.Brush('#075100')) 448 | dc.DrawRectangle(10, i*4, 30, 5) 449 | dc.DrawRectangle(41, i*4, 30, 5) 450 | 451 | else: 452 | dc.SetBrush(wx.Brush('#36ff27')) 453 | dc.DrawRectangle(10, i*4, 30, 5) 454 | dc.DrawRectangle(41, i*4, 30, 5) 455 | ``` 456 | 457 | 在这里,我们绘制了 40 个矩形,每列 20 个。如果正在绘制的矩形的数量大于转换后的 rect 值,我们将它绘制成深绿色;否则以亮绿色。 458 | 459 | ![CPU widget](assets/cpuwidget.png) 460 | 461 | 在本章中,我们在 wxPython 中创建了自定义组件。 -------------------------------------------------------------------------------- /12.程序骨架.md: -------------------------------------------------------------------------------- 1 | # Application skeletons in wxPython 2 | 3 | 在本节中,我们将创建一些应用程序框架。 我们的脚本将解决界面,但不会实现功能。 目标是展示如何在 wxPython 中完成几个众所周知的 GUI 界面。 4 | 5 | ## File Manager 6 | 7 | File Hunter 是文件管理器的骨架。 它复制了 Unix 系统上的文件管理器 Krusader。 如果我们双击分离器组件,它会将 File Hunter 分成两部分,宽度相同。如果我们调整主窗口的大小,会发生同样的情况。 8 | 9 | **file_hunter.py** 10 | 11 | ```python 12 | #!/usr/bin/env python3 13 | # -*- coding: utf-8 -*- 14 | 15 | """ 16 | ZetCode wxPython tutorial 17 | 18 | This program creates a skeleton 19 | of a file manager UI. 20 | 21 | author: Jan Bodnar 22 | website: zetcode.com 23 | last edited: May 2018 24 | """ 25 | 26 | import wx 27 | import os 28 | import time 29 | 30 | ID_BUTTON=100 31 | ID_EXIT=200 32 | ID_SPLITTER=300 33 | 34 | 35 | class MyListCtrl(wx.ListCtrl): 36 | 37 | def __init__(self, parent): 38 | wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT) 39 | 40 | images = ['images/empty.png', 'images/folder.png', 'images/source-py.png', 41 | 'images/image.png', 'images/pdf.png', 'images/up16.png'] 42 | 43 | self.InsertColumn(0, 'Name') 44 | self.InsertColumn(1, 'Ext') 45 | self.InsertColumn(2, 'Size', wx.LIST_FORMAT_RIGHT) 46 | self.InsertColumn(3, 'Modified') 47 | 48 | self.SetColumnWidth(0, 220) 49 | self.SetColumnWidth(1, 70) 50 | self.SetColumnWidth(2, 100) 51 | self.SetColumnWidth(3, 420) 52 | 53 | self.il = wx.ImageList(16, 16) 54 | 55 | for i in images: 56 | 57 | self.il.Add(wx.Bitmap(i)) 58 | 59 | self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) 60 | 61 | j = 1 62 | 63 | self.InsertItem(0, '..') 64 | self.SetItemImage(0, 5) 65 | 66 | files = os.listdir('.') 67 | 68 | for i in files: 69 | 70 | (name, ext) = os.path.splitext(i) 71 | ex = ext[1:] 72 | size = os.path.getsize(i) 73 | sec = os.path.getmtime(i) 74 | 75 | self.InsertItem(j, name) 76 | self.SetItem(j, 1, ex) 77 | self.SetItem(j, 2, str(size) + ' B') 78 | self.SetItem(j, 3, time.strftime('%Y-%m-%d %H:%M', time.localtime(sec))) 79 | 80 | if os.path.isdir(i): 81 | self.SetItemImage(j, 1) 82 | elif ex == 'py': 83 | self.SetItemImage(j, 2) 84 | elif ex == 'jpg': 85 | self.SetItemImage(j, 3) 86 | elif ex == 'pdf': 87 | self.SetItemImage(j, 4) 88 | else: 89 | self.SetItemImage(j, 0) 90 | 91 | if (j % 2) == 0: 92 | 93 | self.SetItemBackgroundColour(j, '#e6f1f5') 94 | 95 | j = j + 1 96 | 97 | 98 | class Example(wx.Frame): 99 | 100 | def __init__(self, *args, **kw): 101 | super(Example, self).__init__(*args, **kw) 102 | 103 | self.InitUI() 104 | 105 | def InitUI(self): 106 | 107 | self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER) 108 | self.splitter.SetMinimumPaneSize(50) 109 | 110 | p1 = MyListCtrl(self.splitter) 111 | p2 = MyListCtrl(self.splitter) 112 | self.splitter.SplitVertically(p1, p2) 113 | 114 | self.Bind(wx.EVT_SIZE, self.OnSize) 115 | self.Bind(wx.EVT_SPLITTER_DCLICK, self.OnDoubleClick, id=ID_SPLITTER) 116 | 117 | filemenu= wx.Menu() 118 | filemenu.Append(ID_EXIT, "E&xit"," Terminate the program") 119 | editmenu = wx.Menu() 120 | netmenu = wx.Menu() 121 | showmenu = wx.Menu() 122 | configmenu = wx.Menu() 123 | helpmenu = wx.Menu() 124 | 125 | menuBar = wx.MenuBar() 126 | menuBar.Append(filemenu, "&File") 127 | menuBar.Append(editmenu, "&Edit") 128 | menuBar.Append(netmenu, "&Net") 129 | menuBar.Append(showmenu, "&Show") 130 | menuBar.Append(configmenu, "&Config") 131 | menuBar.Append(helpmenu, "&Help") 132 | self.SetMenuBar(menuBar) 133 | self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) 134 | 135 | tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | 136 | wx.TB_FLAT) 137 | 138 | tb.AddTool(10, 'Previous', wx.Bitmap('images/previous.png'), shortHelp='Previous') 139 | tb.AddTool(20, 'Up', wx.Bitmap('images/up.png'), shortHelp='Up one directory') 140 | tb.AddTool(30, 'Home', wx.Bitmap('images/home.png'), shortHelp='Home') 141 | tb.AddTool(40, 'Refresh', wx.Bitmap('images/refresh.png'), shortHelp='Refresh') 142 | tb.AddSeparator() 143 | tb.AddTool(50, 'Edit text', wx.Bitmap('images/textedit.png'), shortHelp='Edit text') 144 | tb.AddTool(60, 'Terminal', wx.Bitmap('images/terminal.png'), shortHelp='Terminal') 145 | tb.AddSeparator() 146 | tb.AddTool(70, 'Help', wx.Bitmap('images/help.png'), shortHelp='Show help') 147 | tb.Realize() 148 | 149 | self.sizer2 = wx.BoxSizer(wx.HORIZONTAL) 150 | 151 | button1 = wx.Button(self, ID_BUTTON + 1, "F3 View") 152 | button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit") 153 | button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy") 154 | button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move") 155 | button5 = wx.Button(self, ID_BUTTON + 5, "F7 Mkdir") 156 | button6 = wx.Button(self, ID_BUTTON + 6, "F8 Delete") 157 | button7 = wx.Button(self, ID_BUTTON + 7, "F9 Rename") 158 | button8 = wx.Button(self, ID_EXIT, "F10 Quit") 159 | 160 | self.sizer2.Add(button1, 1, wx.EXPAND) 161 | self.sizer2.Add(button2, 1, wx.EXPAND) 162 | self.sizer2.Add(button3, 1, wx.EXPAND) 163 | self.sizer2.Add(button4, 1, wx.EXPAND) 164 | self.sizer2.Add(button5, 1, wx.EXPAND) 165 | self.sizer2.Add(button6, 1, wx.EXPAND) 166 | self.sizer2.Add(button7, 1, wx.EXPAND) 167 | self.sizer2.Add(button8, 1, wx.EXPAND) 168 | 169 | self.Bind(wx.EVT_BUTTON, self.OnExit, id=ID_EXIT) 170 | 171 | self.sizer = wx.BoxSizer(wx.VERTICAL) 172 | self.sizer.Add(self.splitter,1,wx.EXPAND) 173 | self.sizer.Add(self.sizer2,0,wx.EXPAND) 174 | self.SetSizer(self.sizer) 175 | 176 | # size = wx.DisplaySize() 177 | # self.SetSize(size) 178 | 179 | sb = self.CreateStatusBar() 180 | sb.SetStatusText(os.getcwd()) 181 | 182 | self.SetTitle("File Hunter") 183 | self.Center() 184 | 185 | 186 | def OnExit(self, e): 187 | 188 | self.Close(True) 189 | 190 | def OnSize(self, e): 191 | 192 | size = self.GetSize() 193 | self.splitter.SetSashPosition(size.x / 2) 194 | 195 | e.Skip() 196 | 197 | def OnDoubleClick(self, e): 198 | 199 | size = self.GetSize() 200 | self.splitter.SetSashPosition(size.x / 2) 201 | 202 | 203 | def main(): 204 | 205 | app = wx.App() 206 | ex = Example(None) 207 | ex.Show() 208 | app.MainLoop() 209 | 210 | 211 | if __name__ == '__main__': 212 | main() 213 | ``` 214 | 215 | 该示例创建一个双面板文件管理器 UI。 216 | 217 | ```python 218 | class MyListCtrl(wx.ListCtrl): 219 | 220 | def __init__(self, parent): 221 | wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT) 222 | ``` 223 | 224 | 该应用程序的主要区域被 `wx.ListCtrl` 组件占用。 225 | 226 | ```python 227 | self.il = wx.ImageList(16, 16) 228 | 229 | for i in images: 230 | 231 | self.il.Add(wx.Bitmap(i)) 232 | 233 | self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) 234 | ``` 235 | 236 | 列表控件包含一个列表图像,用于指示文件类型。 237 | 238 | ```python 239 | files = os.listdir('.') 240 | 241 | for i in files: 242 | 243 | (name, ext) = os.path.splitext(i) 244 | ex = ext[1:] 245 | size = os.path.getsize(i) 246 | sec = os.path.getmtime(i) 247 | ... 248 | ``` 249 | 250 | 我们得到当前工作目录的内容,并确定文件扩展名、大小和最后修改时间。 251 | 252 | ```python 253 | if os.path.isdir(i): 254 | self.SetItemImage(j, 1) 255 | elif ex == 'py': 256 | self.SetItemImage(j, 2) 257 | elif ex == 'jpg': 258 | self.SetItemImage(j, 3) 259 | elif ex == 'pdf': 260 | self.SetItemImage(j, 4) 261 | else: 262 | self.SetItemImage(j, 0) 263 | ``` 264 | 265 | 根据文件扩展名选择文件的图标。 266 | 267 | ```python 268 | self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER) 269 | self.splitter.SetMinimumPaneSize(50) 270 | 271 | p1 = MyListCtrl(self.splitter) 272 | p2 = MyListCtrl(self.splitter) 273 | self.splitter.SplitVertically(p1, p2) 274 | ``` 275 | 276 | 我们有两个由分离器小部件垂直分隔的列表控件。 277 | 278 | ```python 279 | menuBar = wx.MenuBar() 280 | menuBar.Append(filemenu, "&File") 281 | menuBar.Append(editmenu, "&Edit") 282 | ... 283 | ``` 284 | 285 | 我们有一个菜单栏 menubar。 286 | 287 | ```python 288 | tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | 289 | wx.TB_FLAT) 290 | 291 | tb.AddTool(10, 'Previous', wx.Bitmap('images/previous.png'), shortHelp='Previous') 292 | tb.AddTool(20, 'Up', wx.Bitmap('images/up.png'), shortHelp='Up one directory') 293 | ... 294 | ``` 295 | 296 | 我们有一个工具栏 toolbar. 297 | 298 | ```python 299 | self.sizer2 = wx.BoxSizer(wx.HORIZONTAL) 300 | 301 | button1 = wx.Button(self, ID_BUTTON + 1, "F3 View") 302 | button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit") 303 | button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy") 304 | button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move") 305 | ... 306 | ``` 307 | 308 | 八个按钮放置在水平 sizer 中, sizer 被添加到窗口的底部。 309 | 310 | ![File manager](assets/filehunter.png)manager 311 | 312 | ## SpreadSheet 313 | 314 | 以下示例创建电子表格应用程序的 UI。 315 | 316 | **spreadsheet.py** 317 | 318 | ```python 319 | #!/usr/bin/env python3 320 | # -*- coding: utf-8 -*- 321 | 322 | """ 323 | ZetCode wxPython tutorial 324 | 325 | This program creates a SpreadSheet UI. 326 | 327 | author: Jan Bodnar 328 | website: zetcode.com 329 | last edited: May 2018 330 | """ 331 | 332 | from wx.lib import sheet 333 | import wx 334 | 335 | 336 | class MySheet(wx.grid.Grid): 337 | 338 | def __init__(self, *args, **kw): 339 | super(MySheet, self).__init__(*args, **kw) 340 | 341 | self.InitUI() 342 | 343 | def InitUI(self): 344 | 345 | nOfRows = 55 346 | nOfCols = 25 347 | 348 | self.row = self.col = 0 349 | self.CreateGrid(nOfRows, nOfCols) 350 | 351 | self.SetColLabelSize(20) 352 | self.SetRowLabelSize(50) 353 | 354 | self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell) 355 | 356 | for i in range(nOfRows): 357 | self.SetRowSize(i, 20) 358 | 359 | for i in range(nOfCols): 360 | self.SetColSize(i, 75) 361 | 362 | def OnGridSelectCell(self, e): 363 | 364 | self.row, self.col = e.GetRow(), e.GetCol() 365 | 366 | control = self.GetParent().GetParent().position 367 | value = self.GetColLabelValue(self.col) + self.GetRowLabelValue(self.row) 368 | control.SetValue(value) 369 | 370 | e.Skip() 371 | 372 | 373 | class Example(wx.Frame): 374 | 375 | def __init__(self, *args, **kw): 376 | super(Example, self).__init__(*args, **kw) 377 | 378 | self.InitUI() 379 | 380 | def InitUI(self): 381 | 382 | fonts = ['Times New Roman', 'Times', 'Courier', 'Courier New', 'Helvetica', 383 | 'Sans', 'verdana', 'utkal', 'aakar', 'Arial'] 384 | font_sizes = ['10', '11', '12', '14', '16'] 385 | 386 | box = wx.BoxSizer(wx.VERTICAL) 387 | menuBar = wx.MenuBar() 388 | 389 | menu1 = wx.Menu() 390 | menuBar.Append(menu1, '&File') 391 | menu2 = wx.Menu() 392 | menuBar.Append(menu2, '&Edit') 393 | menu3 = wx.Menu() 394 | menuBar.Append(menu3, '&Edit') 395 | menu4 = wx.Menu() 396 | menuBar.Append(menu4, '&Insert') 397 | menu5 = wx.Menu() 398 | menuBar.Append(menu5, 'F&ormat') 399 | menu6 = wx.Menu() 400 | menuBar.Append(menu6, '&Tools') 401 | menu7 = wx.Menu() 402 | menuBar.Append(menu7, '&Data') 403 | menu8 = wx.Menu() 404 | menuBar.Append(menu8, '&Help') 405 | 406 | self.SetMenuBar(menuBar) 407 | 408 | toolbar1 = wx.ToolBar(self, style= wx.TB_HORIZONTAL) 409 | 410 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/new.png')) 411 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/open.png')) 412 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/save.png')) 413 | 414 | toolbar1.AddSeparator() 415 | 416 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/cut.png')) 417 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/copy.png')) 418 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/paste.png')) 419 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/delete.png')) 420 | 421 | toolbar1.AddSeparator() 422 | 423 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/undo.png')) 424 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/redo.png')) 425 | 426 | toolbar1.AddSeparator() 427 | 428 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/asc.png')) 429 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/desc.png')) 430 | 431 | toolbar1.AddSeparator() 432 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/chart.png')) 433 | 434 | toolbar1.AddSeparator() 435 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/exit.png')) 436 | 437 | toolbar1.Realize() 438 | 439 | toolbar2 = wx.ToolBar(self, wx.TB_HORIZONTAL | wx.TB_TEXT) 440 | 441 | self.position = wx.TextCtrl(toolbar2) 442 | 443 | font = wx.ComboBox(toolbar2, value='Times', choices=fonts, size=(100, -1), 444 | style=wx.CB_DROPDOWN) 445 | 446 | font_height = wx.ComboBox(toolbar2, value='10', choices=font_sizes, 447 | size=(50, -1), style=wx.CB_DROPDOWN) 448 | 449 | toolbar2.AddControl(self.position) 450 | toolbar2.AddControl(font) 451 | toolbar2.AddControl(font_height) 452 | 453 | toolbar2.AddSeparator() 454 | 455 | toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-bold.png')) 456 | toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-italic.png')) 457 | toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-underline.png')) 458 | 459 | toolbar2.AddSeparator() 460 | 461 | toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-left.png')) 462 | toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-center.png')) 463 | toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-right.png')) 464 | 465 | box.Add(toolbar1, border=5) 466 | box.Add((5,5) , 0) 467 | box.Add(toolbar2) 468 | box.Add((5,10) , 0) 469 | 470 | toolbar2.Realize() 471 | self.SetSizer(box) 472 | 473 | notebook = wx.Notebook(self, style=wx.RIGHT) 474 | 475 | sheet1 = MySheet(notebook) 476 | sheet2 = MySheet(notebook) 477 | sheet3 = MySheet(notebook) 478 | sheet1.SetFocus() 479 | 480 | notebook.AddPage(sheet1, 'Sheet1') 481 | notebook.AddPage(sheet2, 'Sheet2') 482 | notebook.AddPage(sheet3, 'Sheet3') 483 | 484 | box.Add(notebook, 1, wx.EXPAND) 485 | 486 | self.CreateStatusBar() 487 | 488 | self.SetSize((550, 550)) 489 | self.SetTitle("SpreadSheet") 490 | self.Centre() 491 | 492 | def main(): 493 | 494 | app = wx.App() 495 | ex = Example(None) 496 | ex.Show() 497 | app.MainLoop() 498 | 499 | 500 | if __name__ == '__main__': 501 | main() 502 | ``` 503 | 504 | 示例代码创建了 SpreadSheet 应用程序的 UI。 我有一个菜单栏 menubar,工具栏 toolbars 和一个中央网格组件 grid widget。 505 | 506 | ```python 507 | class MySheet(wx.grid.Grid): 508 | 509 | def __init__(self, *args, **kw): 510 | super(MySheet, self).__init__(*args, **kw) 511 | 512 | self.InitUI() 513 | 514 | def InitUI(self): 515 | 516 | nOfRows = 55 517 | nOfCols = 25 518 | 519 | self.row = self.col = 0 520 | self.CreateGrid(nOfRows, nOfCols) 521 | ... 522 | ``` 523 | 524 | 我们创建一个自定义的 `wx.grid.Grid` 小部件。我们的每张 sheets 都有五十五行和二十五列。使用 `CreateGrid()` 方法创建单元网格。 525 | 526 | ```python 527 | control = self.GetParent().GetParent().position 528 | ``` 529 | 530 | 位置文本控件 position text control 显示网格窗口组件 grid widget 选中的单元格。它是第二个工具栏的第一个组件。 在 `MySheet` 类中,我们需要获取 `Example` 类中定义的文本控件的引用。`MySheet` 是 notebook 的孩子。而 notebook 是 `Example` 的孩子。所以我们设法通过两次调用 `GetParent()` 方法来获得位置文本控件。 531 | 532 | ```v 533 | notebook = wx.Notebook(self, style=wx.RIGHT) 534 | 535 | sheet1 = MySheet(notebook) 536 | sheet2 = MySheet(notebook) 537 | sheet3 = MySheet(notebook) 538 | sheet1.SetFocus() 539 | 540 | notebook.AddPage(sheet1, 'Sheet1') 541 | notebook.AddPage(sheet2, 'Sheet2') 542 | notebook.AddPage(sheet3, 'Sheet3') 543 | ``` 544 | 545 | 三张 sheets 被创建并放入笔记本组件 notebook widget 中。 546 | 547 | ![Spreadsheet](assets/spreadsheet.png) 548 | 549 | ## Player 550 | 551 | 以下示例是常见的视频播放器的骨架。 552 | 553 | **player.py** 554 | 555 | ```python 556 | #!/usr/bin/env python3 557 | # -*- coding: utf-8 -*- 558 | 559 | """ 560 | ZetCode wxPython tutorial 561 | 562 | This program creates a Player UI. 563 | 564 | author: Jan Bodnar 565 | website: zetcode.com 566 | last edited: May 2018 567 | """ 568 | 569 | import wx 570 | 571 | 572 | class Example(wx.Frame): 573 | 574 | def __init__(self, *args, **kw): 575 | super(Example, self).__init__(*args, **kw) 576 | 577 | self.InitUI() 578 | 579 | def InitUI(self): 580 | 581 | self.CreateMenuBar() 582 | 583 | panel = wx.Panel(self) 584 | 585 | pnl1 = wx.Panel(self) 586 | pnl1.SetBackgroundColour(wx.BLACK) 587 | pnl2 = wx.Panel(self) 588 | 589 | slider1 = wx.Slider(pnl2, value=18, minValue=0, maxValue=1000) 590 | pause = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/pause.png')) 591 | play = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/play.png')) 592 | forw = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/forw.png')) 593 | back = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/back.png')) 594 | vol = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/volume.png')) 595 | slider2 = wx.Slider(pnl2, value=1, minValue=0, maxValue=100, 596 | size=(120, -1)) 597 | 598 | vbox = wx.BoxSizer(wx.VERTICAL) 599 | hbox1 = wx.BoxSizer(wx.HORIZONTAL) 600 | hbox2 = wx.BoxSizer(wx.HORIZONTAL) 601 | 602 | hbox1.Add(slider1, proportion=1) 603 | hbox2.Add(pause) 604 | hbox2.Add(play, flag=wx.RIGHT, border=5) 605 | hbox2.Add(forw, flag=wx.LEFT, border=5) 606 | hbox2.Add(back) 607 | hbox2.Add((-1, -1), proportion=1) 608 | hbox2.Add(vol) 609 | hbox2.Add(slider2, flag=wx.TOP|wx.LEFT, border=5) 610 | 611 | vbox.Add(hbox1, flag=wx.EXPAND|wx.BOTTOM, border=10) 612 | vbox.Add(hbox2, proportion=1, flag=wx.EXPAND) 613 | pnl2.SetSizer(vbox) 614 | 615 | sizer = wx.BoxSizer(wx.VERTICAL) 616 | sizer.Add(pnl1, proportion=1, flag=wx.EXPAND) 617 | sizer.Add(pnl2, flag=wx.EXPAND|wx.BOTTOM|wx.TOP, border=10) 618 | 619 | self.SetMinSize((350, 300)) 620 | self.CreateStatusBar() 621 | self.SetSizer(sizer) 622 | 623 | self.SetSize((350, 200)) 624 | self.SetTitle('Player') 625 | self.Centre() 626 | 627 | def CreateMenuBar(self): 628 | 629 | menubar = wx.MenuBar() 630 | filem = wx.Menu() 631 | play = wx.Menu() 632 | view = wx.Menu() 633 | tools = wx.Menu() 634 | favorites = wx.Menu() 635 | help = wx.Menu() 636 | 637 | filem.Append(wx.ID_ANY, '&Quit', 'Quit application') 638 | 639 | menubar.Append(filem, '&File') 640 | menubar.Append(play, '&Play') 641 | menubar.Append(view, '&View') 642 | menubar.Append(tools, '&Tools') 643 | menubar.Append(favorites, 'F&avorites') 644 | menubar.Append(help, '&Help') 645 | 646 | self.SetMenuBar(menubar) 647 | 648 | 649 | def main(): 650 | 651 | app = wx.App() 652 | ex = Example(None) 653 | ex.Show() 654 | app.MainLoop() 655 | 656 | 657 | if __name__ == '__main__': 658 | main() 659 | ``` 660 | 661 | 为了构建界面,我们使用了位图按钮 bitmap buttons、滑块 sliders、面板 panels 和菜单栏 menubar。 662 | 663 | ```python 664 | pnl1 = wx.Panel(self) 665 | pnl1.SetBackgroundColour(wx.BLACK) 666 | ``` 667 | 668 | 该应用程序的主要区域被黑色背景的面板占据。 669 | 670 | ```python 671 | slider1 = wx.Slider(pnl2, value=18, minValue=0, maxValue=1000) 672 | ``` 673 | 674 | `wx.Slider` 用于显示电影的进度。 675 | 676 | ```python 677 | pause = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/pause.png')) 678 | play = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/play.png')) 679 | ``` 680 | 681 | 位图按钮 Bitmap buttons 用于控制按钮 control buttons。 682 | 683 | ```python 684 | self.SetMinSize((350, 300)) 685 | ``` 686 | 687 | 这里我们设置了播放器的最小尺寸。将窗口缩小到某个值以下没什么意义。 688 | 689 | ![Player](assets/player.png) 690 | 691 | ## Browser 692 | 693 | 在以下示例中,我们模仿了经典浏览器 UI 的外观。 694 | 695 | **browser.py** 696 | 697 | ```python 698 | #!/usr/bin/env python3 699 | # -*- coding: utf-8 -*- 700 | 701 | """ 702 | ZetCode wxPython tutorial 703 | 704 | This program creates a browser UI. 705 | 706 | author: Jan Bodnar 707 | website: zetcode.com 708 | last edited: May 2018 709 | """ 710 | 711 | import wx 712 | from wx.lib.buttons import GenBitmapTextButton 713 | 714 | class Example(wx.Frame): 715 | 716 | def __init__(self, *args, **kw): 717 | super(Example, self).__init__(*args, **kw) 718 | 719 | self.InitUI() 720 | 721 | def InitUI(self): 722 | 723 | self.CreateMenuBar() 724 | 725 | panel = wx.Panel(self) 726 | # panel.SetBackgroundColour('white') 727 | 728 | vbox = wx.BoxSizer(wx.VERTICAL) 729 | hbox1 = wx.BoxSizer(wx.HORIZONTAL) 730 | hbox2 = wx.BoxSizer(wx.HORIZONTAL) 731 | 732 | line1 = wx.StaticLine(panel) 733 | vbox.Add(line1, 0, wx.EXPAND) 734 | 735 | toolbar1 = wx.Panel(panel, size=(-1, 30)) 736 | 737 | back = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/back.png'), 738 | style=wx.NO_BORDER) 739 | forward = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/forw.png'), 740 | style=wx.NO_BORDER) 741 | refresh = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/refresh.png'), 742 | style=wx.NO_BORDER) 743 | stop = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/stop.png'), 744 | style=wx.NO_BORDER) 745 | home = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/home.png'), 746 | style=wx.NO_BORDER) 747 | address = wx.ComboBox(toolbar1, size=(50, -1)) 748 | go = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/play.png'), 749 | style=wx.NO_BORDER) 750 | text = wx.TextCtrl(toolbar1, size=(150, -1)) 751 | 752 | hbox1.Add(back) 753 | hbox1.Add(forward) 754 | hbox1.Add(refresh) 755 | hbox1.Add(stop) 756 | hbox1.Add(home) 757 | hbox1.Add(address, 1, wx.TOP, 3) 758 | hbox1.Add(go, 0, wx.TOP | wx.LEFT, 3) 759 | hbox1.Add(text, 0, wx.TOP | wx.RIGHT, 3) 760 | 761 | toolbar1.SetSizer(hbox1) 762 | vbox.Add(toolbar1, 0, wx.EXPAND) 763 | line = wx.StaticLine(panel) 764 | vbox.Add(line, 0, wx.EXPAND) 765 | 766 | toolbar2 = wx.Panel(panel, size=(-1, 30)) 767 | bookmark1 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/love.png'), 768 | style=wx.NO_BORDER) 769 | bookmark2 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/book.png'), 770 | style=wx.NO_BORDER) 771 | bookmark3 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/sound.png'), 772 | style=wx.NO_BORDER) 773 | 774 | hbox2.Add(bookmark1, flag=wx.RIGHT, border=5) 775 | hbox2.Add(bookmark2, flag=wx.RIGHT, border=5) 776 | hbox2.Add(bookmark3) 777 | 778 | toolbar2.SetSizer(hbox2) 779 | vbox.Add(toolbar2, 0, wx.EXPAND) 780 | 781 | line2 = wx.StaticLine(panel) 782 | vbox.Add(line2, 0, wx.EXPAND) 783 | 784 | panel.SetSizer(vbox) 785 | 786 | self.CreateStatusBar() 787 | 788 | self.SetTitle("Browser") 789 | self.Centre() 790 | 791 | def CreateMenuBar(self): 792 | 793 | menubar = wx.MenuBar() 794 | file = wx.Menu() 795 | file.Append(wx.ID_ANY, '&Quit', '') 796 | edit = wx.Menu() 797 | view = wx.Menu() 798 | go = wx.Menu() 799 | bookmarks = wx.Menu() 800 | tools = wx.Menu() 801 | help = wx.Menu() 802 | 803 | menubar.Append(file, '&File') 804 | menubar.Append(edit, '&Edit') 805 | menubar.Append(view, '&View') 806 | menubar.Append(go, '&Go') 807 | menubar.Append(bookmarks, '&Bookmarks') 808 | menubar.Append(tools, '&Tools') 809 | menubar.Append(help, '&Help') 810 | 811 | self.SetMenuBar(menubar) 812 | 813 | 814 | def main(): 815 | 816 | app = wx.App() 817 | ex = Example(None) 818 | ex.Show() 819 | app.MainLoop() 820 | 821 | 822 | if __name__ == '__main__': 823 | main() 824 | ``` 825 | 826 | 为了创建一个 sizeable combo box,我们不能使用 `wx.Toolbar`。我们基于 `wx.Panel` 创建我们的自定义工具栏。 827 | 828 | ```python 829 | toolbar1 = wx.Panel(panel, size=(-1, 40)) 830 | ``` 831 | 832 | 我们创建一个普通的 `wx.Panel`。 833 | 834 | ```python 835 | hbox1 = wx.BoxSizer(wx.HORIZONTAL) 836 | ... 837 | hbox1.Add(back) 838 | hbox1.Add(forward) 839 | hbox1.Add(refresh) 840 | ``` 841 | 842 | 我们创建一个水平 sizer 并添加所有必要的按钮。 843 | 844 | ```python 845 | hbox1.Add(address, 1, wx.TOP, 4) 846 | ``` 847 | 848 | 然后我们将组合框 combo box 添加到 sizer 中。这种组合框通常称为地址栏。请注意,它是唯一将比例设置为 1 的组件。这对于使其可调整大小是必需的。 849 | 850 | ```python 851 | line2 = wx.StaticLine(panel) 852 | vbox.Add(line2, 0, wx.EXPAND) 853 | ``` 854 | 855 | 工具栏由一条线隔开。 856 | 857 | ![Browser UI](assets/browser.png) 858 | 859 | 在这部分 wxPython 教程中,我们创建了一些应用程序骨架。 -------------------------------------------------------------------------------- /13.俄罗斯方块.md: -------------------------------------------------------------------------------- 1 | # The Tetris game in wxPython 2 | 3 | 俄罗斯方块游戏是有史以来最受欢迎的电脑游戏之一。最初的游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。从那以后,俄罗斯方块在几乎所有的计算机平台上都有很多变种。 4 | 5 | 俄罗斯方块被称为下降块益智游戏。在这个游戏中,我们有七种不同的形状,称为 tetromino:S形、Z形、T形、L形、线形、Mirrored L形和方形。每个形状都由四个正方形构成。形状将跌落在板子 board 上。俄罗斯方块游戏的目标是移动和旋转形状,使它们尽可能适合。 如果我们设法形成一排,那么这一排就会被消掉,然后得分。我们玩俄罗斯方块游戏,直到最上面顶出去。 6 | 7 | ![Tetrominoes](assets/tetrominoes.png) 8 | 9 | wxPython 是一个用于创建应用程序的工具包。还有其他一些旨在创建电脑游戏的库。尽管如此,wxPython 和其他应用程序工具包都可用于创建游戏。 10 | 11 | ## The development 12 | 13 | 我们没有俄罗斯方块游戏的图像,我们使用 wxPython 中的绘图 API 绘制 tetrominoes。每个电脑游戏的背后都有一个数学模型。 俄罗斯方块也不例外。 14 | 15 | 游戏背后的一些思想: 16 | 17 | - 我们用 `wx.Timer` 创建游戏循环 18 | - 绘制 tetrominoes 19 | - 形状以方形为基础逐个移动(而不是逐个移动像素) 20 | - 在数学上, board 是一个简单的数字列表 21 | 22 | **tetris.py** 23 | 24 | ```python 25 | #!/usr/bin/env python3 26 | # -*- coding: utf-8 -*- 27 | 28 | """ 29 | ZetCode wxPython tutorial 30 | 31 | This is Tetris game clone in wxPython. 32 | 33 | author: Jan Bodnar 34 | website: www.zetcode.com 35 | last modified: May 2018 36 | """ 37 | 38 | import wx 39 | import random 40 | 41 | class Tetris(wx.Frame): 42 | 43 | def __init__(self, parent): 44 | wx.Frame.__init__(self, parent, size=(180, 380), 45 | style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) 46 | 47 | self.initFrame() 48 | 49 | def initFrame(self): 50 | 51 | self.statusbar = self.CreateStatusBar() 52 | self.statusbar.SetStatusText('0') 53 | self.board = Board(self) 54 | self.board.SetFocus() 55 | self.board.start() 56 | 57 | self.SetTitle("Tetris") 58 | self.Centre() 59 | 60 | 61 | class Board(wx.Panel): 62 | 63 | BoardWidth = 10 64 | BoardHeight = 22 65 | Speed = 300 66 | ID_TIMER = 1 67 | 68 | def __init__(self, *args, **kw): 69 | 70 | super(Board, self).__init__(*args, **kw) 71 | 72 | self.initBoard() 73 | 74 | def initBoard(self): 75 | 76 | self.timer = wx.Timer(self, Board.ID_TIMER) 77 | self.isWaitingAfterLine = False 78 | self.curPiece = Shape() 79 | self.nextPiece = Shape() 80 | self.curX = 0 81 | self.curY = 0 82 | self.numLinesRemoved = 0 83 | self.board = [] 84 | 85 | self.isStarted = False 86 | self.isPaused = False 87 | 88 | self.Bind(wx.EVT_PAINT, self.OnPaint) 89 | self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 90 | self.Bind(wx.EVT_TIMER, self.OnTimer, id=Board.ID_TIMER) 91 | 92 | self.clearBoard() 93 | 94 | def shapeAt(self, x, y): 95 | 96 | return self.board[(y * Board.BoardWidth) + x] 97 | 98 | def setShapeAt(self, x, y, shape): 99 | 100 | self.board[(y * Board.BoardWidth) + x] = shape 101 | 102 | def squareWidth(self): 103 | 104 | return self.GetClientSize().GetWidth() // Board.BoardWidth 105 | 106 | def squareHeight(self): 107 | 108 | return self.GetClientSize().GetHeight() // Board.BoardHeight 109 | 110 | def start(self): 111 | 112 | if self.isPaused: 113 | return 114 | 115 | self.isStarted = True 116 | self.isWaitingAfterLine = False 117 | self.numLinesRemoved = 0 118 | self.clearBoard() 119 | 120 | self.newPiece() 121 | self.timer.Start(Board.Speed) 122 | 123 | def pause(self): 124 | 125 | if not self.isStarted: 126 | return 127 | 128 | self.isPaused = not self.isPaused 129 | statusbar = self.GetParent().statusbar 130 | 131 | if self.isPaused: 132 | self.timer.Stop() 133 | statusbar.SetStatusText('paused') 134 | else: 135 | self.timer.Start(Board.Speed) 136 | statusbar.SetStatusText(str(self.numLinesRemoved)) 137 | 138 | self.Refresh() 139 | 140 | def clearBoard(self): 141 | 142 | for i in range(Board.BoardHeight * Board.BoardWidth): 143 | self.board.append(Tetrominoes.NoShape) 144 | 145 | def OnPaint(self, event): 146 | 147 | dc = wx.PaintDC(self) 148 | 149 | size = self.GetClientSize() 150 | boardTop = size.GetHeight() - Board.BoardHeight * self.squareHeight() 151 | 152 | for i in range(Board.BoardHeight): 153 | for j in range(Board.BoardWidth): 154 | 155 | shape = self.shapeAt(j, Board.BoardHeight - i - 1) 156 | 157 | if shape != Tetrominoes.NoShape: 158 | self.drawSquare(dc, 159 | 0 + j * self.squareWidth(), 160 | boardTop + i * self.squareHeight(), shape) 161 | 162 | if self.curPiece.shape() != Tetrominoes.NoShape: 163 | 164 | for i in range(4): 165 | 166 | x = self.curX + self.curPiece.x(i) 167 | y = self.curY - self.curPiece.y(i) 168 | 169 | self.drawSquare(dc, 0 + x * self.squareWidth(), 170 | boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(), 171 | self.curPiece.shape()) 172 | 173 | 174 | def OnKeyDown(self, event): 175 | 176 | if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape: 177 | event.Skip() 178 | return 179 | 180 | keycode = event.GetKeyCode() 181 | 182 | if keycode == ord('P') or keycode == ord('p'): 183 | self.pause() 184 | return 185 | 186 | if self.isPaused: 187 | return 188 | 189 | elif keycode == wx.WXK_LEFT: 190 | self.tryMove(self.curPiece, self.curX - 1, self.curY) 191 | 192 | elif keycode == wx.WXK_RIGHT: 193 | self.tryMove(self.curPiece, self.curX + 1, self.curY) 194 | 195 | elif keycode == wx.WXK_DOWN: 196 | self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY) 197 | 198 | elif keycode == wx.WXK_UP: 199 | self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY) 200 | 201 | elif keycode == wx.WXK_SPACE: 202 | self.dropDown() 203 | 204 | elif keycode == ord('D') or keycode == ord('d'): 205 | self.oneLineDown() 206 | 207 | else: 208 | event.Skip() 209 | 210 | 211 | def OnTimer(self, event): 212 | 213 | if event.GetId() == Board.ID_TIMER: 214 | 215 | if self.isWaitingAfterLine: 216 | self.isWaitingAfterLine = False 217 | self.newPiece() 218 | 219 | else: 220 | self.oneLineDown() 221 | 222 | else: 223 | event.Skip() 224 | 225 | 226 | def dropDown(self): 227 | 228 | newY = self.curY 229 | 230 | while newY > 0: 231 | if not self.tryMove(self.curPiece, self.curX, newY - 1): 232 | break 233 | newY -= 1 234 | 235 | self.pieceDropped() 236 | 237 | def oneLineDown(self): 238 | 239 | if not self.tryMove(self.curPiece, self.curX, self.curY - 1): 240 | self.pieceDropped() 241 | 242 | 243 | def pieceDropped(self): 244 | 245 | for i in range(4): 246 | 247 | x = self.curX + self.curPiece.x(i) 248 | y = self.curY - self.curPiece.y(i) 249 | self.setShapeAt(x, y, self.curPiece.shape()) 250 | 251 | self.removeFullLines() 252 | 253 | if not self.isWaitingAfterLine: 254 | self.newPiece() 255 | 256 | 257 | def removeFullLines(self): 258 | 259 | numFullLines = 0 260 | 261 | statusbar = self.GetParent().statusbar 262 | 263 | rowsToRemove = [] 264 | 265 | for i in range(Board.BoardHeight): 266 | n = 0 267 | for j in range(Board.BoardWidth): 268 | if not self.shapeAt(j, i) == Tetrominoes.NoShape: 269 | n = n + 1 270 | 271 | if n == 10: 272 | rowsToRemove.append(i) 273 | 274 | rowsToRemove.reverse() 275 | 276 | for m in rowsToRemove: 277 | for k in range(m, Board.BoardHeight): 278 | for l in range(Board.BoardWidth): 279 | self.setShapeAt(l, k, self.shapeAt(l, k + 1)) 280 | 281 | numFullLines = numFullLines + len(rowsToRemove) 282 | 283 | if numFullLines > 0: 284 | 285 | self.numLinesRemoved = self.numLinesRemoved + numFullLines 286 | statusbar.SetStatusText(str(self.numLinesRemoved)) 287 | self.isWaitingAfterLine = True 288 | self.curPiece.setShape(Tetrominoes.NoShape) 289 | self.Refresh() 290 | 291 | 292 | def newPiece(self): 293 | 294 | self.curPiece = self.nextPiece 295 | statusbar = self.GetParent().statusbar 296 | self.nextPiece.setRandomShape() 297 | 298 | self.curX = Board.BoardWidth // 2 + 1 299 | self.curY = Board.BoardHeight - 1 + self.curPiece.minY() 300 | 301 | if not self.tryMove(self.curPiece, self.curX, self.curY): 302 | 303 | self.curPiece.setShape(Tetrominoes.NoShape) 304 | self.timer.Stop() 305 | self.isStarted = False 306 | statusbar.SetStatusText('Game over') 307 | 308 | 309 | def tryMove(self, newPiece, newX, newY): 310 | 311 | for i in range(4): 312 | 313 | x = newX + newPiece.x(i) 314 | y = newY - newPiece.y(i) 315 | 316 | if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: 317 | return False 318 | 319 | if self.shapeAt(x, y) != Tetrominoes.NoShape: 320 | return False 321 | 322 | self.curPiece = newPiece 323 | self.curX = newX 324 | self.curY = newY 325 | self.Refresh() 326 | 327 | return True 328 | 329 | 330 | def drawSquare(self, dc, x, y, shape): 331 | 332 | colors = ['#000000', '#CC6666', '#66CC66', '#6666CC', 333 | '#CCCC66', '#CC66CC', '#66CCCC', '#DAAA00'] 334 | 335 | light = ['#000000', '#F89FAB', '#79FC79', '#7979FC', 336 | '#FCFC79', '#FC79FC', '#79FCFC', '#FCC600'] 337 | 338 | dark = ['#000000', '#803C3B', '#3B803B', '#3B3B80', 339 | '#80803B', '#803B80', '#3B8080', '#806200'] 340 | 341 | pen = wx.Pen(light[shape]) 342 | pen.SetCap(wx.CAP_PROJECTING) 343 | dc.SetPen(pen) 344 | 345 | dc.DrawLine(x, y + self.squareHeight() - 1, x, y) 346 | dc.DrawLine(x, y, x + self.squareWidth() - 1, y) 347 | 348 | darkpen = wx.Pen(dark[shape]) 349 | darkpen.SetCap(wx.CAP_PROJECTING) 350 | dc.SetPen(darkpen) 351 | 352 | dc.DrawLine(x + 1, y + self.squareHeight() - 1, 353 | x + self.squareWidth() - 1, y + self.squareHeight() - 1) 354 | dc.DrawLine(x + self.squareWidth() - 1, 355 | y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1) 356 | 357 | dc.SetPen(wx.TRANSPARENT_PEN) 358 | dc.SetBrush(wx.Brush(colors[shape])) 359 | dc.DrawRectangle(x + 1, y + 1, self.squareWidth() - 2, 360 | self.squareHeight() - 2) 361 | 362 | 363 | class Tetrominoes(object): 364 | 365 | NoShape = 0 366 | ZShape = 1 367 | SShape = 2 368 | LineShape = 3 369 | TShape = 4 370 | SquareShape = 5 371 | LShape = 6 372 | MirroredLShape = 7 373 | 374 | 375 | class Shape(object): 376 | 377 | coordsTable = ( 378 | ((0, 0), (0, 0), (0, 0), (0, 0)), 379 | ((0, -1), (0, 0), (-1, 0), (-1, 1)), 380 | ((0, -1), (0, 0), (1, 0), (1, 1)), 381 | ((0, -1), (0, 0), (0, 1), (0, 2)), 382 | ((-1, 0), (0, 0), (1, 0), (0, 1)), 383 | ((0, 0), (1, 0), (0, 1), (1, 1)), 384 | ((-1, -1), (0, -1), (0, 0), (0, 1)), 385 | ((1, -1), (0, -1), (0, 0), (0, 1)) 386 | ) 387 | 388 | def __init__(self): 389 | 390 | self.coords = [[0,0] for i in range(4)] 391 | self.pieceShape = Tetrominoes.NoShape 392 | 393 | self.setShape(Tetrominoes.NoShape) 394 | 395 | def shape(self): 396 | 397 | return self.pieceShape 398 | 399 | def setShape(self, shape): 400 | 401 | table = Shape.coordsTable[shape] 402 | for i in range(4): 403 | for j in range(2): 404 | self.coords[i][j] = table[i][j] 405 | 406 | self.pieceShape = shape 407 | 408 | def setRandomShape(self): 409 | 410 | self.setShape(random.randint(1, 7)) 411 | 412 | def x(self, index): 413 | 414 | return self.coords[index][0] 415 | 416 | def y(self, index): 417 | 418 | return self.coords[index][1] 419 | 420 | def setX(self, index, x): 421 | 422 | self.coords[index][0] = x 423 | 424 | def setY(self, index, y): 425 | 426 | self.coords[index][1] = y 427 | 428 | def minX(self): 429 | 430 | m = self.coords[0][0] 431 | for i in range(4): 432 | m = min(m, self.coords[i][0]) 433 | 434 | return m 435 | 436 | def maxX(self): 437 | 438 | m = self.coords[0][0] 439 | for i in range(4): 440 | m = max(m, self.coords[i][0]) 441 | 442 | return m 443 | 444 | def minY(self): 445 | 446 | m = self.coords[0][1] 447 | for i in range(4): 448 | m = min(m, self.coords[i][1]) 449 | 450 | return m 451 | 452 | def maxY(self): 453 | 454 | m = self.coords[0][1] 455 | 456 | for i in range(4): 457 | m = max(m, self.coords[i][1]) 458 | 459 | return m 460 | 461 | def rotatedLeft(self): 462 | 463 | if self.pieceShape == Tetrominoes.SquareShape: 464 | return self 465 | 466 | result = Shape() 467 | result.pieceShape = self.pieceShape 468 | 469 | for i in range(4): 470 | result.setX(i, self.y(i)) 471 | result.setY(i, -self.x(i)) 472 | 473 | return result 474 | 475 | def rotatedRight(self): 476 | 477 | if self.pieceShape == Tetrominoes.SquareShape: 478 | return self 479 | 480 | result = Shape() 481 | result.pieceShape = self.pieceShape 482 | 483 | for i in range(4): 484 | result.setX(i, -self.y(i)) 485 | result.setY(i, self.x(i)) 486 | 487 | return result 488 | 489 | 490 | def main(): 491 | 492 | app = wx.App() 493 | ex = Tetris(None) 494 | ex.Show() 495 | app.MainLoop() 496 | 497 | 498 | if __name__ == '__main__': 499 | main() 500 | ``` 501 | 502 | 游戏简化了一点,以便更容易理解。它在应用程序启动后立即启动。我们可以通过按 p 键暂停游戏。空格键立即将下降的俄罗斯方块将到底部。d 键将方块片段往下走下一行(它可以用来加速下降)。游戏以恒定速度进行,没有实现加速。得分是我们消掉的行数。 503 | 504 | ```python 505 | def __init__(self, *args, **kw): 506 | 507 | super(Board, self).__init__(*args, **kw) 508 | ``` 509 | 510 | Windows 用户注意事项:如果您不能使用箭头键,请将 `style = wx.WANTS_CHARS` 添加到 board 的构造函数中。 511 | 512 | ```python 513 | ... 514 | self.curX = 0 515 | self.curY = 0 516 | self.numLinesRemoved = 0 517 | self.board = [] 518 | ... 519 | ``` 520 | 521 | 在我们开始游戏循环之前,我们初始化一些重要变量。`self.board` 变量是一个从 0 到 7 的数字列表。它表示 board 上方块的各种形状和方块的残存形状。 522 | 523 | ```python 524 | for i in range(Board.BoardHeight): 525 | for j in range(Board.BoardWidth): 526 | 527 | shape = self.shapeAt(j, Board.BoardHeight - i - 1) 528 | 529 | if shape != Tetrominoes.NoShape: 530 | self.drawSquare(dc, 531 | 0 + j * self.squareWidth(), 532 | boardTop + i * self.squareHeight(), shape) 533 | ``` 534 | 535 | 游戏画分为两步。在第一步中,我们绘制所有形状,或者保留已经掉到板子 board 底部的残存形状。 所有的形状都记录在 `self.board` 列表变量中。 我们使用 `shapeAt()` 方法来访问它。 536 | 537 | ```python 538 | if self.curPiece.shape() != Tetrominoes.NoShape: 539 | 540 | for i in range(4): 541 | 542 | x = self.curX + self.curPiece.x(i) 543 | y = self.curY - self.curPiece.y(i) 544 | 545 | self.drawSquare(dc, 0 + x * self.squareWidth(), 546 | boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(), 547 | self.curPiece.shape()) 548 | ``` 549 | 550 | 下一步是绘制正在下将的实际方块片段。 551 | 552 | ```python 553 | elif keycode == wx.WXK_LEFT: 554 | self.tryMove(self.curPiece, self.curX - 1, self.curY) 555 | ``` 556 | 557 | 在 `OnKeyDown()` 方法中,我们检查按下的键。 如果我们按下左箭头键,我们试着将方块片段移向左边。 我们说试着,是因为方块片段可能无法移动。 558 | 559 | ```python 560 | def tryMove(self, newPiece, newX, newY): 561 | 562 | for i in range(4): 563 | x = newX + newPiece.x(i) 564 | y = newY - newPiece.y(i) 565 | if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: 566 | return False 567 | if self.shapeAt(x, y) != Tetrominoes.NoShape: 568 | return False 569 | 570 | self.curPiece = newPiece 571 | self.curX = newX 572 | self.curY = newY 573 | self.Refresh() 574 | 575 | return True 576 | ``` 577 | 578 | 在 `tryMove()` 方法中,我们尝试移动我们的形状。如果形状位于板子 board 的边缘或与其他方块片段相邻,则返回 False;否则我们将当前下降的棋子放到新的位置并返回 True。 579 | 580 | ```python 581 | def OnTimer(self, event): 582 | 583 | if event.GetId() == Board.ID_TIMER: 584 | if self.isWaitingAfterLine: 585 | self.isWaitingAfterLine = False 586 | self.newPiece() 587 | else: 588 | self.oneLineDown() 589 | else: 590 | event.Skip() 591 | ``` 592 | 593 | 在 `OnTimer()` 方法中,我们要么在前一个被放到底部之后,创建一个新的方块片段,或者我们将一个下降的方块片段向下移动一行。 594 | 595 | ```python 596 | def removeFullLines(self): 597 | 598 | numFullLines = 0 599 | 600 | rowsToRemove = [] 601 | 602 | for i in range(Board.BoardHeight): 603 | n = 0 604 | for j in range(Board.BoardWidth): 605 | if not self.shapeAt(j, i) == Tetrominoes.NoShape: 606 | n = n + 1 607 | 608 | if n == 10: 609 | rowsToRemove.append(i) 610 | 611 | rowsToRemove.reverse() 612 | 613 | for m in rowsToRemove: 614 | for k in range(m, Board.BoardHeight): 615 | for l in range(Board.BoardWidth): 616 | self.setShapeAt(l, k, self.shapeAt(l, k + 1)) 617 | ... 618 | ``` 619 | 620 | 如果方块片段碰到底部,我们调用 `removeFullLines()` 方法。 首先我们找出整行并删除它。 我们让当前行上方的所有行整体下移一行,来删除这一行。请注意,我们颠倒了要删除的行的顺序。否则,它将无法正确工作。在我们的情况下,我们使用 naive gravity。 也就是说这些方块片段可能会漂浮在空隙之上。 621 | 622 | ```python 623 | def newPiece(self): 624 | 625 | self.curPiece = self.nextPiece 626 | statusbar = self.GetParent().statusbar 627 | self.nextPiece.setRandomShape() 628 | self.curX = Board.BoardWidth / 2 + 1 629 | self.curY = Board.BoardHeight - 1 + self.curPiece.minY() 630 | 631 | if not self.tryMove(self.curPiece, self.curX, self.curY): 632 | 633 | self.curPiece.setShape(Tetrominoes.NoShape) 634 | self.timer.Stop() 635 | self.isStarted = False 636 | statusbar.SetStatusText('Game over') 637 | ``` 638 | 639 | `newPiece()` 方法随机创建一个新的俄罗斯方块片段。 如果方块片段无法进入初始位置,游戏结束。 640 | 641 | The `Shape` class saves information about the tetris piece. 642 | 643 | `Shape` 类保存有关俄罗斯方块的信息。 644 | 645 | ```python 646 | self.coords = [[0,0] for i in range(4)] 647 | ``` 648 | 649 | 创建形状后,我们创建一个空的坐标列表。 该列表将保存俄罗斯方块的坐标。 例如,元组 (0, -1)、(0, 0)、(-1, 0)、(-1, -1) 表示旋转的 S 形。 下图阐明了该形状。 650 | 651 | ![Coordinates](assets/coordinates.png) 652 | 653 | 当我们画出当前正在下落的方块片段时,我们在 `self.curX` 和 `self.curY` 位置绘制它。然后我们查看坐标表并绘制所有四个方块。 654 | 655 | ![Tetris](assets/tetris.png) 656 | 657 | 这是 wxPython 中的一个俄罗斯方块游戏。 -------------------------------------------------------------------------------- /2.第一步.md: -------------------------------------------------------------------------------- 1 | # First steps in wxPython 2 | 3 | ## 简单的例子 4 | 5 | 我们从一个非常简单的例子开始。 我们的第一个脚本只会显示一个小窗口。 6 | 7 | 它不会做太多。 我们将逐行分析脚本。 8 | 9 | **simple.py** 10 | 11 | ```python 12 | #!/usr/bin/env python3 13 | # -*- coding: utf-8 -*- 14 | 15 | import wx 16 | 17 | app = wx.App() 18 | 19 | frame = wx.Frame(None, title='Simple application') 20 | frame.Show() 21 | 22 | app.MainLoop() 23 | ``` 24 | 25 | 讲解: 26 | 27 | ```python 28 | import wx 29 | ``` 30 | 31 | 这一行导入基本的 wxPython 模块。 即 core、controls、gdi、misc 和 windows。 从技术上讲,`wx` 是一个命名空间。 基本模块中的所有功能和对象都将以 `wx.` 开头 `wx.`  字首。 32 | 33 | 下一行代码将创建一个应用程序对象。 34 | 35 | ```python 36 | app = wx.App() 37 | ``` 38 | 39 | 每个 wxPython 程序都必须有一个应用程序对象。 40 | 41 | ```python 42 | frame = wx.Frame(None, title='Simple application') 43 | frame.Show() 44 | ``` 45 | 46 | 这里我们创建一个 `wx.Frame` 对象。 `wx.Frame` 组件是一个重要的容器组件。 我们将在稍后详细分析这个组件。 `wx.Frame` 组件是其他组件的父组件。 它本身没有父母 parent。 如果我们为父参数 parent parameter 指定 `None` ,则表明我们的组件没有父母。 它是组件层次结构中的顶级组件。在我们创建了 `wx.Frame` 组件之后,我们必须调用 `Show()` 方法将其显示在屏幕上。 47 | 48 | ```python 49 | app.MainLoop() 50 | ``` 51 | 52 | 最后一行进入主循环。主循环是一个无限循环。它捕获和分发我们应用程序生命周期中存在的所有事件。 53 | 54 | 这是一个非常简单的例子。尽管这个简单,我们可以用这个窗口做很多事情。 我们可以调整窗口大小,最大限度地缩小窗口大小。这个功能需要编写很多代码。 所有这些都是隐藏的,并由 wxPython 工具箱默认提供。没有理由重新发明轮子。 55 | 56 | ![Simple example](assets/simple.png) 57 | 58 | ## wx.Frame 59 | 60 | wx.Frame 组件是 wxPython 中最重要的组件之一。它是一个容器组件。这意味着它可以包含其他组件。实际上它可以包含任何不是框架 frame 或对话框 dialog 的窗口。 `wx.Frame` 包含一个标题栏 title bar,边框 borders 和一个中央容器区域 central container area。标题栏和边框是可选的。他们可以通过各种标志 flags 去除。 61 | 62 | `wx.Frame` 具有以下构造函数: 63 | 64 | 展示形式:`类型 参数名=默认值` 65 | 66 | ```python 67 | wx.Frame(wx.Window parent, int id=-1, string title='', wx.Point pos=wx.DefaultPosition, 68 | wx.Size size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, string name="frame") 69 | ``` 70 | 71 | 构造函数有七个参数。第一个参数没有默认值。其他六个参数有。 72 | 73 | ### wx.DEFAULT_FRAME_STYLE 74 | 75 | `wx.DEFAULT_FRAME_STYLE` 是一组默认标志 flags: `wx.MINIMIZE_BOX` | `wx.MAXIMIZE_BOX` | `wx.RESIZE_BORDER` |`wx.SYSTEM_MENU` | `wx.CAPTION` | `wx.CLOSE_BOX` | `wx.CLIP_CHILDREN` 。通过组合各种样式,我们可以改变 `wx.Frame` 组件的样式。 76 | 77 | #### 默认 78 | 79 | 具备所有风格属性 80 | 81 | ```python 82 | import wx 83 | 84 | app = wx.App() 85 | frame = wx.Frame(None) 86 | frame.Show(True) 87 | 88 | app.MainLoop() 89 | ``` 90 | 91 | ![1527417412183](assets/1527417412183.png) 92 | 93 | #### wx.MINIMIZE_BOX 94 | 95 | ![1527417584346](assets/1527417584346.png) 96 | 97 | #### wx.RESIZE_BORDER 98 | 99 | 可以伸缩 100 | 101 | ![1527417657992](assets/1527417657992.png) 102 | 103 | 这些属性单独使用其实看不出效果,一般是组合使用 104 | 105 | #### 组合 106 | 107 | **no_minimize.py** 108 | 109 | ```python 110 | #!/usr/bin/env python3 111 | # -*- coding: utf-8 -*- 112 | 113 | # no_minimize.py 114 | 115 | import wx 116 | 117 | app = wx.App() 118 | frame = wx.Frame(None, style=wx.MAXIMIZE_BOX | wx.RESIZE_BORDER 119 | | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX) 120 | frame.Show(True) 121 | 122 | app.MainLoop() 123 | ``` 124 | 125 | 我们的目的是显示一个没有最小化框的窗口。 所以我们没有在 style 参数中指定这个标志。 126 | 127 | ## 大小和位置 128 | 129 | 我们可以通过两种方式指定应用程序的大小:在构造函数中使用 size 参数,或者我们可以调用 `SetSize()` 方法。 130 | 131 | **set_size.py** 132 | 133 | ```python 134 | #!/usr/bin/env python3 135 | # -*- coding: utf-8 -*- 136 | 137 | # set_size.py 138 | 139 | import wx 140 | 141 | 142 | class Example(wx.Frame): 143 | def __init__(self, parent, title): 144 | super(Example, self).__init__(parent, title=title, 145 | size=(350, 250)) 146 | 147 | 148 | def main(): 149 | app = wx.App() 150 | ex = Example(None, title='Sizing') 151 | ex.Show() 152 | app.MainLoop() 153 | 154 | 155 | if __name__ == '__main__': 156 | main() 157 | ``` 158 | 159 | 在这个例子中,应用程序的大小为 250 x 200 像素。 160 | 161 | ```python 162 | def __init__(self, parent, title): 163 | super(Example, self).__init__(parent, title=title, 164 | size=(350, 250)) 165 | ``` 166 | 167 | 在构造函数中,我们将 `wx.Frame` 组件的宽度设置为 350 像素。 组件的高度为 250 像素。 168 | 169 | 同样,我们可以将我们的应用程序放置在屏幕上。默认情况下,窗口放置在屏幕的左上角。但是在不同的操作系统平台甚至窗口管理器上它可能不同。一些窗口管理器自己放置应用程序窗。它们中的一些还做了一些优化,让窗口不重叠。程序员可以通过编程来定位窗口。我们已经在我们的 `wx.Frame` 组件的构造函数中看到了一个 `pos` 参数。通过提供非默认值,我们可以自己控制位置。 170 | 171 | | 方法 | 描述 | 172 | | ----------------------------------------------- | ------------------------ | 173 | | `Move(wx.Point point)` | 移动一个窗口到指定的位置 | 174 | | `MoveXY(int x, int y)` | 移动一个窗口到指定的位置 | 175 | | `SetPosition(wx.Point point)` | 设置窗口的位置 | 176 | | `SetDimensions(x, y, width, height, sizeFlags)` | 设置窗口的位置和大小 | 177 | 178 | 有几种方法可以做到这一点。 179 | 180 | **moving.py** 181 | 182 | ```python 183 | #!/usr/bin/env python3 184 | # -*- coding: utf-8 -*- 185 | 186 | # moving.py 187 | 188 | import wx 189 | 190 | 191 | class Example(wx.Frame): 192 | 193 | def __init__(self, parent, title): 194 | super(Example, self).__init__(parent, title=title, 195 | size=(300, 200)) 196 | 197 | self.Move((800, 250)) 198 | 199 | 200 | def main(): 201 | 202 | app = wx.App() 203 | ex = Example(None, title='Moving') 204 | ex.Show() 205 | app.MainLoop() 206 | 207 | 208 | if __name__ == '__main__': 209 | main() 210 | ``` 211 | 212 | 有一种特殊情况,我们可能希望我们的窗口最大化显示。在这种情况下,窗口位于 (0, 0) 并占据整个屏幕。wxPython 内部计算屏幕坐标。为了最大化我们的 `wx.Frame`,我们调用 `Maximize()` 方法。 213 | 214 | ## Centering on the screen 215 | 216 | 如果我们想要将我们的应用程序居中在屏幕上,wxPython 有一个方便的方法。Centre()` 方法将窗口置于屏幕中央。无需计算屏幕的宽度和高度。只需调用该方法即可。 217 | 218 | **centering.py** 219 | 220 | ```python 221 | #!/usr/bin/env python3 222 | # -*- coding: utf-8 -*- 223 | 224 | # centering.py 225 | 226 | import wx 227 | 228 | 229 | class Example(wx.Frame): 230 | 231 | def __init__(self, parent, title): 232 | super(Example, self).__init__(parent, title=title, 233 | size=(300, 200)) 234 | 235 | self.Centre() 236 | 237 | 238 | def main(): 239 | 240 | app = wx.App() 241 | ex = Example(None, title='Centering') 242 | ex.Show() 243 | app.MainLoop() 244 | 245 | 246 | if __name__ == '__main__': 247 | main() 248 | ``` 249 | 250 | 在这个例子中,我们在屏幕上居中放置一个小窗口。 251 | 252 | ```python 253 | self.Centre() 254 | ``` 255 | 256 | `Centre()` 方法将窗口在屏幕上居中。 257 | 258 | 在本章中,我们在 wxPython 中创建了一些简单的代码示例。 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /3.菜单和工具栏.md: -------------------------------------------------------------------------------- 1 | # Menus and toolbars 2 | 3 | GUI 应用程序中的一个常见部分是菜单栏。菜单栏由称为 menus 的对象组成。顶层菜单在菜单栏上有自己的标签 labels。菜单有菜单项 menu items。菜单项是在应用程序内部执行特定操作的命令。菜单也可以有子菜单 submenus,它们有自己的菜单项。在 wxPython 中有三个类用于创建菜单栏 menubars:`wx.MenuBar`,`wx.Menu` 和 `wx.MenuItem`。 4 | 5 | ## 简单的菜单 6 | 7 | 在我们的第一个例子中,我们将创建一个菜单文件。菜单将只有一个菜单项。 通过选择菜单项目让应用程序退出。 8 | 9 | **simple_menu.py** 10 | 11 | ```python 12 | #!/usr/bin/env python3 13 | # -*- coding: utf-8 -*- 14 | 15 | """ 16 | ZetCode wxPython tutorial 17 | 18 | This example shows a simple menu. 19 | 20 | author: Jan Bodnar 21 | website: www.zetcode.com 22 | last modified: April 2018 23 | """ 24 | 25 | import wx 26 | 27 | 28 | class Example(wx.Frame): 29 | 30 | def __init__(self, *args, **kwargs): 31 | super(Example, self).__init__(*args, **kwargs) 32 | 33 | self.InitUI() 34 | 35 | def InitUI(self): 36 | 37 | menubar = wx.MenuBar() 38 | fileMenu = wx.Menu() 39 | fileItem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application') 40 | menubar.Append(fileMenu, '&File') 41 | self.SetMenuBar(menubar) 42 | 43 | self.Bind(wx.EVT_MENU, self.OnQuit, fileItem) 44 | 45 | self.SetSize((300, 200)) 46 | self.SetTitle('Simple menu') 47 | self.Centre() 48 | 49 | def OnQuit(self, e): 50 | self.Close() 51 | 52 | 53 | def main(): 54 | 55 | app = wx.App() 56 | ex = Example(None) 57 | ex.Show() 58 | app.MainLoop() 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | ``` 64 | 65 | 这是一个最小菜单栏功能的小例子。 66 | 67 | ```python 68 | menubar = wx.MenuBar() 69 | ``` 70 | 71 | 首先我们创建一个菜单栏对象。 72 | 73 | ```python 74 | fileMenu = wx.Menu() 75 | ``` 76 | 77 | 接下来我们创建一个菜单对象。 78 | 79 | ```python 80 | fileItem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application') 81 | ``` 82 | 83 | 我们将菜单项追加到菜单对象中。 第一个参数是菜单项的 ID。 标准 id 会自动添加图标和快捷键,在我们的例子,是 Ctrl + Q(然而译者我用的 Windows 并没有生成图标和快捷键)。 第二个参数是菜单项的名称。 最后一个参数定义了选择菜单项时的简短帮助。 这里我们没有明确地创建一个 `wx.MenuItem` 。 它由幕后的 `Append()` 方法创建。 该方法返回创建的菜单项 menu item。 该引用在稍后将用于绑定事件。 84 | 85 | ```python 86 | self.Bind(wx.EVT_MENU, self.OnQuit, fileItem) 87 | ``` 88 | 89 | 我们将菜单项的 `wx.EVT_MENU` (菜单退出事件绑定器)绑定到自定义的 `OnQuit()` 方法。 该方法将关闭应用程序。 90 | 91 | ```python 92 | menubar.Append(fileMenu, '&File') 93 | self.SetMenuBar(menubar) 94 | ``` 95 | 96 | 之后,我们将菜单添加到菜单栏中。& 符号创建一个加速键,& 后面的字符加下划线(Windows 无效,Ubuntu 上也没看到效果,鸡肋,可以去掉)。这样菜单可以通过 Alt + F 快捷键访问。 最后,我们调用 `SetMenuBar()` 方法。此方法属于 `wx.Frame` 组件。 它设置菜单栏。 97 | 98 | ![A simple menu example](assets/simplemenu.png) 99 | 100 | ## 图标和快捷键 101 | 102 | 下一个例子与前一个例子基本相同。 这一次,我们手动创建一个 `wx.MenuItem` 。 103 | 104 | **icons_shortcuts.py** 105 | 106 | ```python 107 | #!/usr/bin/env python3 108 | # -*- coding: utf-8 -*- 109 | 110 | """ 111 | ZetCode wxPython tutorial 112 | 113 | In this example, we manually create 114 | a menu item. 115 | 116 | author: Jan Bodnar 117 | website: www.zetcode.com 118 | last modified: April 2018 119 | """ 120 | 121 | import wx 122 | 123 | APP_EXIT = 1 124 | 125 | 126 | class Example(wx.Frame): 127 | 128 | def __init__(self, *args, **kwargs): 129 | super(Example, self).__init__(*args, **kwargs) 130 | 131 | self.InitUI() 132 | 133 | def InitUI(self): 134 | 135 | menubar = wx.MenuBar() 136 | fileMenu = wx.Menu() 137 | qmi = wx.MenuItem(fileMenu, APP_EXIT, '&Quit\tCtrl+Q') 138 | qmi.SetBitmap(wx.Bitmap('exit.png')) 139 | fileMenu.Append(qmi) 140 | 141 | self.Bind(wx.EVT_MENU, self.OnQuit, id=APP_EXIT) 142 | 143 | menubar.Append(fileMenu, '&File') 144 | self.SetMenuBar(menubar) 145 | 146 | self.SetSize((350, 250)) 147 | self.SetTitle('Icons and shortcuts') 148 | self.Centre() 149 | 150 | def OnQuit(self, e): 151 | self.Close() 152 | 153 | 154 | def main(): 155 | 156 | app = wx.App() 157 | ex = Example(None) 158 | ex.Show() 159 | app.MainLoop() 160 | 161 | 162 | if __name__ == '__main__': 163 | main() 164 | ``` 165 | 166 | 在这个例子中,我们创建了一个退出菜单项。 我们选择一个自定义图标和菜单项的快捷键。 167 | 168 | ```python 169 | qmi = wx.MenuItem(fileMenu, APP_EXIT, '&Quit\tCtrl+Q') 170 | qmi.SetBitmap(wx.Bitmap('exit.png')) 171 | fileMenu.Append(qmi) 172 | ``` 173 | 174 | 我们创建一个 `wx.MenuItem` 对象。 & 符号指定一个加速键。 & 符号后面的字符加下划线(没啥用,去掉也不影响后面的快捷键)。实际的快捷键由字符组合定义。我们指定了 Ctrl + Q。 所以如果我们按 Ctrl + Q ,应用程序会关闭。我们在 & 符号和快捷方式之间放置了一个 tab 符。这样,我们设法在它们之间留出一些空间。要为菜单项提供图标,我们调用 `SetBitmap()` 方法,手动创建的菜单项通过调用 `AppendItem()` 方法附加到菜单。 175 | 176 | ```python 177 | self.Bind(wx.EVT_MENU, self.OnQuit, id=APP_EXIT) 178 | ``` 179 | 180 | 当我们选择菜单项时,`OnQuit()` 方法被调用。 181 | 182 | ![Icons and shortcuts](assets/iconsshortcuts.png) 183 | 184 | ## 子菜单和分隔符 185 | 186 | 每个菜单也可以有一个子菜单,这样我们可以将类似的命令放入组中。例如,它可以隐藏/显示各种工具栏(如个人栏,地址栏,状态栏或导航栏)到称为工具栏 toolbars 的子菜单 submenu 的命令。在菜单中,我们可以用分隔符分隔命令。这是一条简单的线路。通常的做法是使用单个分隔符来分隔诸如新建、打开、保存命令和打印、打印预览等命令。在我们的例子中,我们将看到,我们如何创建子菜单和菜单分隔符。 187 | 188 | **submenu.py** 189 | 190 | ```python 191 | #!/usr/bin/env python3 192 | # -*- coding: utf-8 -*- 193 | 194 | """ 195 | ZetCode wxPython tutorial 196 | 197 | In this example, we create a submenu and a menu 198 | separator. 199 | 200 | author: Jan Bodnar 201 | website: www.zetcode.com 202 | last modified: April 2018 203 | """ 204 | 205 | import wx 206 | 207 | 208 | class Example(wx.Frame): 209 | 210 | def __init__(self, *args, **kwargs): 211 | super(Example, self).__init__(*args, **kwargs) 212 | 213 | self.InitUI() 214 | 215 | def InitUI(self): 216 | 217 | menubar = wx.MenuBar() 218 | 219 | fileMenu = wx.Menu() 220 | fileMenu.Append(wx.ID_NEW, '&New') 221 | fileMenu.Append(wx.ID_OPEN, '&Open') 222 | fileMenu.Append(wx.ID_SAVE, '&Save') 223 | fileMenu.AppendSeparator() 224 | 225 | imp = wx.Menu() 226 | imp.Append(wx.ID_ANY, 'Import newsfeed list...') 227 | imp.Append(wx.ID_ANY, 'Import bookmarks...') 228 | imp.Append(wx.ID_ANY, 'Import mail...') 229 | 230 | fileMenu.Append(wx.ID_ANY, 'I&mport', imp) 231 | 232 | qmi = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit\tCtrl+W') 233 | fileMenu.Append(qmi) 234 | 235 | self.Bind(wx.EVT_MENU, self.OnQuit, qmi) 236 | 237 | menubar.Append(fileMenu, '&File') 238 | self.SetMenuBar(menubar) 239 | 240 | self.SetSize((350, 250)) 241 | self.SetTitle('Submenu') 242 | self.Centre() 243 | 244 | def OnQuit(self, e): 245 | self.Close() 246 | 247 | 248 | def main(): 249 | 250 | app = wx.App() 251 | ex = Example(None) 252 | ex.Show() 253 | app.MainLoop() 254 | 255 | 256 | if __name__ == '__main__': 257 | main() 258 | ``` 259 | 260 | 在上面的例子中,我们创建了新建,打开和保存标准菜单项。 具有水平分隔符的子菜单。 子菜单还有三个菜单项。 261 | 262 | ```python 263 | fileMenu.Append(wx.ID_NEW, '&New') 264 | fileMenu.Append(wx.ID_OPEN, '&Open') 265 | fileMenu.Append(wx.ID_SAVE, '&Save') 266 | ``` 267 | 268 | 这里我们有三个常用菜单项:New、Open、Save。 269 | 270 | ```python 271 | fileMenu.AppendSeparator() 272 | ``` 273 | 274 | 菜单分隔符 `AppendSeparator()` 方法添加。 275 | 276 | ```python 277 | imp = wx.Menu() 278 | imp.Append(wx.ID_ANY, 'Import newsfeed list...') 279 | imp.Append(wx.ID_ANY, 'Import bookmarks...') 280 | imp.Append(wx.ID_ANY, 'Import mail...') 281 | 282 | fileMenu.Append(wx.ID_ANY, 'I&mport', imp) 283 | ``` 284 | 285 | 一个子菜单也是一个 `wx.Menu` 。菜单上附有三个菜单项。子菜单通过 `Append()` 方法附加到 File 菜单。 286 | 287 | ![A submenu example](assets/submenu.png) 288 | 289 | ## 勾选菜单项 Check menu item 290 | 291 | 有下面几种菜单项 292 | 293 | - normal item 294 | - check item 295 | - radio item 296 | 297 | 在下面的例子中,我们将演示检查菜单项。 检查菜单项在菜单中由勾号可视地表示。 298 | 299 | **checkmenu_item.py** 300 | 301 | ```python 302 | #!/usr/bin/env python3 303 | # -*- coding: utf-8 -*- 304 | 305 | """ 306 | ZetCode wxPython tutorial 307 | 308 | This example creates a checked 309 | menu item. 310 | 311 | author: Jan Bodnar 312 | website: www.zetcode.com 313 | last modified: April 2018 314 | """ 315 | 316 | import wx 317 | 318 | 319 | class Example(wx.Frame): 320 | 321 | def __init__(self, *args, **kwargs): 322 | super(Example, self).__init__(*args, **kwargs) 323 | 324 | self.InitUI() 325 | 326 | def InitUI(self): 327 | 328 | menubar = wx.MenuBar() 329 | viewMenu = wx.Menu() 330 | 331 | self.shst = viewMenu.Append(wx.ID_ANY, 'Show statusbar', 332 | 'Show Statusbar', kind=wx.ITEM_CHECK) 333 | self.shtl = viewMenu.Append(wx.ID_ANY, 'Show toolbar', 334 | 'Show Toolbar', kind=wx.ITEM_CHECK) 335 | 336 | viewMenu.Check(self.shst.GetId(), True) 337 | viewMenu.Check(self.shtl.GetId(), True) 338 | 339 | self.Bind(wx.EVT_MENU, self.ToggleStatusBar, self.shst) 340 | self.Bind(wx.EVT_MENU, self.ToggleToolBar, self.shtl) 341 | 342 | menubar.Append(viewMenu, '&View') 343 | self.SetMenuBar(menubar) 344 | 345 | self.toolbar = self.CreateToolBar() 346 | self.toolbar.AddTool(1, '', wx.Bitmap('exit.png')) 347 | self.toolbar.Realize() 348 | 349 | self.statusbar = self.CreateStatusBar() 350 | self.statusbar.SetStatusText('Ready') 351 | 352 | self.SetSize((450, 350)) 353 | self.SetTitle('Check menu item') 354 | self.Centre() 355 | 356 | 357 | def ToggleStatusBar(self, e): 358 | 359 | if self.shst.IsChecked(): 360 | self.statusbar.Show() 361 | else: 362 | self.statusbar.Hide() 363 | 364 | def ToggleToolBar(self, e): 365 | 366 | if self.shtl.IsChecked(): 367 | self.toolbar.Show() 368 | else: 369 | self.toolbar.Hide() 370 | 371 | 372 | def main(): 373 | 374 | app = wx.App() 375 | ex = Example(None) 376 | ex.Show() 377 | app.MainLoop() 378 | 379 | 380 | if __name__ == '__main__': 381 | main() 382 | ``` 383 | 384 | 我们有一个 viewMenu,上面有两个勾选的菜单项。两个菜单项将会显示或隐藏状态栏和工具栏。 385 | 386 | ```python 387 | self.shst = viewMenu.Append(wx.ID_ANY, 'Show statusbar', 388 | 'Show Statusbar', kind=wx.ITEM_CHECK) 389 | self.shtl = viewMenu.Append(wx.ID_ANY, 'Show toolbar', 390 | 'Show Toolbar', kind=wx.ITEM_CHECK) 391 | ``` 392 | 393 | 如果我们想添加一个勾选菜单项,我们为 `wx.ITEM_CHECK` 设置一个 `kind` 参数。默认参数是 `wx.ITEM_NORMAL`。 `Append()` 方法返回一个 `wx.MenuItem` 。 394 | 395 | ```python 396 | viewMenu.Check(self.shst.GetId(), True) 397 | viewMenu.Check(self.shtl.GetId(), True) 398 | ``` 399 | 400 | 当程序启动,状态栏和工具栏都可见。 所以我们用 `Check()` 方法检查两个项目。 401 | 402 | ```python 403 | def ToggleStatusBar(self, e): 404 | 405 | if self.shst.IsChecked(): 406 | self.statusbar.Show() 407 | else: 408 | self.statusbar.Hide() 409 | ``` 410 | 411 | 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. 412 | 413 | 我们根据勾选菜单项的状态显示或隐藏状态栏。 通过 `IsChecked()` 方法找到的菜单项的状态。工具栏同理。 414 | 415 | ![Check menu item](assets/checkmenuitem.png) 416 | 417 | ## 上下文菜单 Context menu 418 | 419 | 上下文菜单是在某种上下文条件下出现的命令列表。 例如,在 Firefox 网页浏览器中,当我们右击一个网页时,我们会得到一个上下文菜单。在上下文菜单处,我们可以重新加载页面,返回或查看页面源代码。如果我们点击一个工具栏,我们会得到另一个用于管理工具栏的上下文菜单。上下文菜单有时称为弹出式菜单。 420 | 421 | **context_menu.py** 422 | 423 | ```python 424 | #!/usr/bin/env python3 425 | # -*- coding: utf-8 -*- 426 | 427 | """ 428 | ZetCode wxPython tutorial 429 | 430 | In this example, we create a context menu. 431 | 432 | author: Jan Bodnar 433 | website: www.zetcode.com 434 | last modified: April 2018 435 | """ 436 | 437 | import wx 438 | 439 | class MyPopupMenu(wx.Menu): 440 | 441 | def __init__(self, parent): 442 | super(MyPopupMenu, self).__init__() 443 | 444 | self.parent = parent 445 | 446 | mmi = wx.MenuItem(self, wx.NewId(), 'Minimize') 447 | self.Append(mmi) 448 | self.Bind(wx.EVT_MENU, self.OnMinimize, mmi) 449 | 450 | cmi = wx.MenuItem(self, wx.NewId(), 'Close') 451 | self.Append(cmi) 452 | self.Bind(wx.EVT_MENU, self.OnClose, cmi) 453 | 454 | 455 | def OnMinimize(self, e): 456 | self.parent.Iconize() 457 | 458 | def OnClose(self, e): 459 | self.parent.Close() 460 | 461 | 462 | class Example(wx.Frame): 463 | 464 | def __init__(self, *args, **kwargs): 465 | super(Example, self).__init__(*args, **kwargs) 466 | 467 | self.InitUI() 468 | 469 | def InitUI(self): 470 | 471 | self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) 472 | 473 | self.SetSize((350, 250)) 474 | self.SetTitle('Context menu') 475 | self.Centre() 476 | 477 | def OnRightDown(self, e): 478 | self.PopupMenu(MyPopupMenu(self), e.GetPosition()) 479 | 480 | 481 | def main(): 482 | 483 | app = wx.App() 484 | ex = Example(None) 485 | ex.Show() 486 | app.MainLoop() 487 | 488 | 489 | if __name__ == '__main__': 490 | main() 491 | ``` 492 | 493 | 在这个例子中,我们为主窗口创建了一个上下文菜单。它有两个项目。 一个将最小化应用程序,另一个将终止它。 494 | 495 | ```python 496 | class MyPopupMenu(wx.Menu): 497 | 498 | def __init__(self, parent): 499 | super(MyPopupMenu, self).__init__() 500 | ``` 501 | 502 | 我们创建一个单独的 `wx.Menu` 类。 503 | 504 | ```python 505 | mmi = wx.MenuItem(self, wx.NewId(), 'Minimize') 506 | self.Append(mmi) 507 | self.Bind(wx.EVT_MENU, self.OnMinimize, mmi) 508 | ``` 509 | 510 | 一个菜单项被创建并附加到上下文菜单。一个事件处理程序 event handler 绑定到此菜单项。 511 | 512 | ```python 513 | self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) 514 | ``` 515 | 516 | 如果我们右键点击框架 frame,我们调用 `OnRightDown()` 方法。 为此,我们使用 `wx.EVT_RIGHT_DOWN` 事件绑定程序 event binder。 517 | 518 | ```python 519 | def OnRightDown(self, e): 520 | self.PopupMenu(MyPopupMenu(self), e.GetPosition()) 521 | ``` 522 | 523 | 在 `OnRightDown()` 方法中,我们调用 `PopupMenu()` 方法,该方法显示上下文菜单。 第一个参数是要显示的菜单,第二个参数是上下文菜单出现的位置。上下文菜单出现在鼠标光标的位置。 为了获得实际的鼠标位置,我们调用提供的事件对象的 `GetPosition()` 方法。 524 | 525 | ![Context menu](assets/contextmenu.png) 526 | 527 | ## 工具栏 Toolbars 528 | 529 | 菜单将我们可以在应用程序中使用的所有命令分组。工具栏提供了对最常用命令的快速访问。 530 | 531 | 要创建一个工具栏,我们调用框架组件 frame widget 的 `CreateToolBar()` 方法。 532 | 533 | **toolbar.py** 534 | 535 | ```python 536 | #!/usr/bin/env python3 537 | # -*- coding: utf-8 -*- 538 | 539 | """ 540 | ZetCode wxPython tutorial 541 | 542 | This example creates a simple toolbar. 543 | 544 | author: Jan Bodnar 545 | website: www.zetcode.com 546 | last modified: April 2018 547 | """ 548 | 549 | import wx 550 | 551 | 552 | class Example(wx.Frame): 553 | 554 | def __init__(self, *args, **kwargs): 555 | super(Example, self).__init__(*args, **kwargs) 556 | 557 | self.InitUI() 558 | 559 | def InitUI(self): 560 | 561 | toolbar = self.CreateToolBar() 562 | qtool = toolbar.AddTool(wx.ID_ANY, 'Quit', wx.Bitmap('exit.png')) 563 | toolbar.Realize() 564 | 565 | self.Bind(wx.EVT_TOOL, self.OnQuit, qtool) 566 | 567 | self.SetSize((350, 250)) 568 | self.SetTitle('Simple toolbar') 569 | self.Centre() 570 | 571 | def OnQuit(self, e): 572 | self.Close() 573 | 574 | 575 | def main(): 576 | 577 | app = wx.App() 578 | ex = Example(None) 579 | ex.Show() 580 | app.MainLoop() 581 | 582 | 583 | if __name__ == '__main__': 584 | main() 585 | ``` 586 | 587 | 在我们的例子中,我们有一个带有一个工具的工具栏。 当我们点击它时,该工具将关闭应用程序。 588 | 589 | ```python 590 | toolbar = self.CreateToolBar() 591 | ``` 592 | 593 | 我们创建一个工具栏。 默认情况下,工具栏是水平的,没有边框和显示。 594 | 595 | ```python 596 | qtool = toolbar.AddTool(wx.ID_ANY, 'Quit', wx.Bitmap('texit.png')) 597 | ``` 598 | 599 | 要创建一个工具栏工具,我们调用 `AddTool()` 方法。第二个参数是工具的标签,第三个参数是工具的图像。请注意,标签 label 不可见,因为默认样式仅显示图标。 600 | 601 | ```python 602 | toolbar.Realize() 603 | ``` 604 | 605 | 将我们的项目放到工具栏后,我们调用 `Realize()` 方法。 在 Linux 上调用此方法不是强制性的。 在 windows 上是必须的(不然不会添加工具)。 606 | 607 | ```python 608 | self.Bind(wx.EVT_TOOL, self.OnQuit, qtool) 609 | ``` 610 | 611 | 要处理工具栏的事件,我们使用 `wx.EVT_TOOL` 事件绑定器。 612 | 613 | ![Simple toolbar](assets/simpletoolbar.png) 614 | 615 | 如果我们想创建多个工具栏,我们必须以不同的方式处理。 616 | 617 | **toolbars.py** 618 | 619 | ```python 620 | #!/usr/bin/env python3 621 | # -*- coding: utf-8 -*- 622 | 623 | ''' 624 | ZetCode wxPython tutorial 625 | 626 | In this example, we create two horizontal 627 | toolbars. 628 | 629 | author: Jan Bodnar 630 | website: www.zetcode.com 631 | last modified: April 2018 632 | ''' 633 | 634 | import wx 635 | 636 | 637 | class Example(wx.Frame): 638 | 639 | def __init__(self, *args, **kwargs): 640 | super(Example, self).__init__(*args, **kwargs) 641 | 642 | self.InitUI() 643 | 644 | def InitUI(self): 645 | 646 | vbox = wx.BoxSizer(wx.VERTICAL) 647 | 648 | toolbar1 = wx.ToolBar(self) 649 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('tnew.png')) 650 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('topen.png')) 651 | toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('tsave.png')) 652 | toolbar1.Realize() 653 | 654 | toolbar2 = wx.ToolBar(self) 655 | qtool = toolbar2.AddTool(wx.ID_EXIT, '', wx.Bitmap('texit.png')) 656 | toolbar2.Realize() 657 | 658 | vbox.Add(toolbar1, 0, wx.EXPAND) 659 | vbox.Add(toolbar2, 0, wx.EXPAND) 660 | 661 | self.Bind(wx.EVT_TOOL, self.OnQuit, qtool) 662 | 663 | self.SetSizer(vbox) 664 | 665 | self.SetSize((350, 250)) 666 | self.SetTitle('Toolbars') 667 | self.Centre() 668 | 669 | def OnQuit(self, e): 670 | self.Close() 671 | 672 | 673 | def main(): 674 | 675 | app = wx.App() 676 | ex = Example(None) 677 | ex.Show() 678 | app.MainLoop() 679 | 680 | 681 | if __name__ == '__main__': 682 | main() 683 | ``` 684 | 685 | 在上面的例子中,我们创建了两个水平工具栏。 686 | 687 | ```python 688 | toolbar1 = wx.ToolBar(self) 689 | ... 690 | toolbar2 = wx.ToolBar(self) 691 | ``` 692 | 693 | 我们创建了两个工具栏对象,并把它们放入一个垂直的盒子里。 694 | 695 | ![Toolbars](assets/toolbars.png) 696 | 697 | ## 启用和禁用 Enable & disable 698 | 699 | 下面的例子中,我们展示了如何启用和禁用工具栏按钮。 我们还添加到分隔线。 700 | 701 | **undo_redo.py** 702 | 703 | ```python 704 | #!/usr/bin/env python3 705 | # -*- coding: utf-8 -*- 706 | 707 | """ 708 | ZetCode wxPython tutorial 709 | 710 | In this example, we create two horizontal 711 | toolbars. 712 | 713 | author: Jan Bodnar 714 | website: www.zetcode.com 715 | last modified: April 2018 716 | """ 717 | 718 | import wx 719 | 720 | class Example(wx.Frame): 721 | 722 | def __init__(self, *args, **kwargs): 723 | super(Example, self).__init__(*args, **kwargs) 724 | 725 | self.InitUI() 726 | 727 | def InitUI(self): 728 | 729 | self.count = 5 730 | 731 | self.toolbar = self.CreateToolBar() 732 | tundo = self.toolbar.AddTool(wx.ID_UNDO, '', wx.Bitmap('tundo.png')) 733 | tredo = self.toolbar.AddTool(wx.ID_REDO, '', wx.Bitmap('tredo.png')) 734 | self.toolbar.EnableTool(wx.ID_REDO, False) 735 | self.toolbar.AddSeparator() 736 | texit = self.toolbar.AddTool(wx.ID_EXIT, '', wx.Bitmap('texit.png')) 737 | self.toolbar.Realize() 738 | 739 | self.Bind(wx.EVT_TOOL, self.OnQuit, texit) 740 | self.Bind(wx.EVT_TOOL, self.OnUndo, tundo) 741 | self.Bind(wx.EVT_TOOL, self.OnRedo, tredo) 742 | 743 | self.SetSize((350, 250)) 744 | self.SetTitle('Undo redo') 745 | self.Centre() 746 | 747 | def OnUndo(self, e): 748 | if self.count > 1 and self.count <= 5: 749 | self.count = self.count - 1 750 | 751 | if self.count == 1: 752 | self.toolbar.EnableTool(wx.ID_UNDO, False) 753 | 754 | if self.count == 4: 755 | self.toolbar.EnableTool(wx.ID_REDO, True) 756 | 757 | def OnRedo(self, e): 758 | if self.count < 5 and self.count >= 1: 759 | self.count = self.count + 1 760 | 761 | if self.count == 5: 762 | self.toolbar.EnableTool(wx.ID_REDO, False) 763 | 764 | if self.count == 2: 765 | self.toolbar.EnableTool(wx.ID_UNDO, True) 766 | 767 | 768 | def OnQuit(self, e): 769 | self.Close() 770 | 771 | 772 | def main(): 773 | 774 | app = wx.App() 775 | ex = Example(None) 776 | ex.Show() 777 | app.MainLoop() 778 | 779 | 780 | if __name__ == '__main__': 781 | main() 782 | ``` 783 | 784 | 在我们的例子中,我们有三个工具栏按钮。一个按钮用于退出应用程序。 另外两个用于撤消和重做按钮。它们模拟应用程序中的 撤销/重做 功能。(有关实际示例,请参阅提示和技巧)我们有 4 次变化。 撤销/重做 按钮相应地进行启用和禁用。 785 | 786 | ```python 787 | self.toolbar.EnableTool(wx.ID_REDO, False) 788 | self.toolbar.AddSeparator() 789 | ``` 790 | 791 | 在开始时,重做按钮被禁用。我们通过调用 `EnableTool()` 方法来实现。我们可以在工具栏中创建一些逻辑组。 我们可以用分隔符分开各种按钮组。 为此,我们调用 `AddSeparator()` 方法。 792 | 793 | ```python 794 | def OnUndo(self, e): 795 | if self.count > 1 and self.count <= 5: 796 | self.count = self.count - 1 797 | 798 | if self.count == 1: 799 | self.toolbar.EnableTool(wx.ID_UNDO, False) 800 | 801 | if self.count == 4: 802 | self.toolbar.EnableTool(wx.ID_REDO, True) 803 | ``` 804 | 805 | 我们模拟撤销和重做功能,有四次变化。 如果没有任何可撤消的内容,撤消按钮被禁用。 撤消第一次更改后,我们启用重做按钮。同样的逻辑适用于 `OnRedo()` 方法。 806 | 807 | ![Undo redo](assets/undoredo.png) 808 | 809 | 在 wxPython 教程的这一部分,我们使用了菜单和工具栏。 -------------------------------------------------------------------------------- /4.布局.md: -------------------------------------------------------------------------------- 1 | # Layout management in wxPython 2 | 3 | 一个典型的应用程序由各种组件组成。这些组件放置在容器组件中。 程序员必须管理应用程序的布局。这不是一件容易的事。 在 wxPython 中,可以使用绝对定位或使用 sizers 来布局组件。 4 | 5 | ## 绝对定位 6 | 7 | 绝对定位:程序员以像素为单位指定组件的位置和大小。 绝对定位有几个缺点: 8 | 9 | - 如果我们调整窗口的大小,组件的大小和位置不会随之改变。 10 | - 应用在各种平台上看起来不同。 11 | - 更改应用程序中的字体可能会破坏布局。 12 | - 如果我们决定改变布局,我们必须完全重做我们的布局,这是乏味和耗时的。 13 | 14 | 我们可能会出现要使用绝对定位的情况。 例如,小的测试例子中。 但大多数情况下,在真实世界的程序中,程序员使用 sizers。 15 | 16 | 在我们的例子中,我们有一个简单的文本编辑框架。 如果我们调整窗口大小,`wx.TextCtrl` 的大小不会像我们预期的那样改变。 17 | 18 | **absolute.py** 19 | 20 | ```python 21 | #!/usr/bin/env python3 22 | # -*- coding: utf-8 -*- 23 | 24 | """ 25 | ZetCode wxPython tutorial 26 | 27 | In this example, we lay out widgets using 28 | absolute positioning. 29 | 30 | author: Jan Bodnar 31 | website: www.zetcode.com 32 | last modified: April 2018 33 | """ 34 | 35 | import wx 36 | 37 | 38 | class Example(wx.Frame): 39 | 40 | def __init__(self, parent, title): 41 | super(Example, self).__init__(parent, title=title, 42 | size=(350, 300)) 43 | 44 | self.InitUI() 45 | self.Centre() 46 | 47 | def InitUI(self): 48 | 49 | self.panel = wx.Panel(self) 50 | 51 | self.panel.SetBackgroundColour("gray") 52 | 53 | self.LoadImages() 54 | 55 | self.mincol.SetPosition((20, 20)) 56 | self.bardejov.SetPosition((40, 160)) 57 | self.rotunda.SetPosition((170, 50)) 58 | 59 | 60 | def LoadImages(self): 61 | 62 | self.mincol = wx.StaticBitmap(self.panel, wx.ID_ANY, 63 | wx.Bitmap("mincol.jpg", wx.BITMAP_TYPE_ANY)) 64 | 65 | self.bardejov = wx.StaticBitmap(self.panel, wx.ID_ANY, 66 | wx.Bitmap("bardejov.jpg", wx.BITMAP_TYPE_ANY)) 67 | 68 | self.rotunda = wx.StaticBitmap(self.panel, wx.ID_ANY, 69 | wx.Bitmap("rotunda.jpg", wx.BITMAP_TYPE_ANY)) 70 | 71 | 72 | def main(): 73 | 74 | app = wx.App() 75 | ex = Example(None, title='Absolute positioning') 76 | ex.Show() 77 | app.MainLoop() 78 | 79 | 80 | if __name__ == '__main__': 81 | main() 82 | ``` 83 | 84 | 在上面的例子中,我们使用绝对坐标定位三个图像。 85 | 86 | ``` 87 | self.mincol.SetPosition((20, 20)) 88 | ``` 89 | 90 | 使用 `SetPosition()` 方法,我们将图像放置在 x = 20,y = 20 坐标处。 91 | 92 | ![absolute positioning 1](assets/absolute.png) 93 | 94 | ## 使用 sizer 95 | 96 | Sizer 确实解决了我们提到的绝对定位问题。 wxPython 有以下 sizer: 97 | 98 | - wx.BoxSizer 99 | - wx.StaticBoxSizer 100 | - wx.GridSizer 101 | - wx.FlexGridSizer 102 | - wx.GridBagSizer 103 | 104 | ## wx.BoxSizer 105 | 106 | `wx.BoxSizer` 使我们能够将多个组件放入行或列中。 我们可以将另一个 sizer 放入现有的 sizer 中。 这样我们可以创建非常复杂的布局。 107 | 108 | ```python 109 | box = wx.BoxSizer(integer orient) # orient:v. 确定自己的方向 110 | box.Add(wx.Window window, integer proportion=0, integer flag = 0, integer border = 0) 111 | ``` 112 | 113 | orient:方向可以是 `wx.HORIZONTAL` 或 `wx.VERTICAL`。 通过 `Add()` 方法将组件添加到 `wx.BoxSizer` 中。 为了理解它,我们需要看看它的参数。 114 | 115 | proportion:比例参数定义组件在定义的方向上更改的比率。 假设我们有三个比例为 0, 1 和 2 的按钮,它们被添加到一个水平的 `wx.BoxSizer`。比例为 0 的按钮根本不会改变。比例为 2 的按钮在水平方向的变化是比例为 1 的 2 倍。 116 | 117 | flag:使用 flag 参数,您可以进一步配置 `wx.BoxSizer` 中的组件的行为。 118 | 119 | border:我们可以控制组件之间的外边距 border,以像素为单位在组件之间添加一些空间。 120 | 121 | 为了用到 border,我们需要定义 border 的边界 sides。我们可以将它们与 `|` 操作符相结合;例如 `wx.LEFT | wx.BOTTOM`。 我们可以在这些标志 flag 之间选择: 122 | 123 | - wx.LEFT 124 | - wx.RIGHT 125 | - wx.BOTTOM 126 | - wx.TOP 127 | - wx.ALL 128 | 129 | 使用 `setSizer()` 方法将 sizer 设置为面板 panel。 130 | 131 | **border.py** 132 | 133 | ```python 134 | #!/usr/bin/env python3 135 | # -*- coding: utf-8 -*- 136 | 137 | """ 138 | ZetCode wxPython tutorial 139 | 140 | In this example we place a panel inside 141 | another panel. 142 | 143 | author: Jan Bodnar 144 | website: www.zetcode.com 145 | last modified: April 2018 146 | """ 147 | 148 | import wx 149 | 150 | 151 | class Example(wx.Frame): 152 | 153 | def __init__(self, parent, title): 154 | super(Example, self).__init__(parent, title=title) 155 | 156 | self.InitUI() 157 | self.Centre() 158 | 159 | def InitUI(self): 160 | 161 | panel = wx.Panel(self) 162 | 163 | panel.SetBackgroundColour('#4f5049') 164 | vbox = wx.BoxSizer(wx.VERTICAL) 165 | 166 | midPan = wx.Panel(panel) 167 | midPan.SetBackgroundColour('#ededed') 168 | 169 | vbox.Add(midPan, wx.ID_ANY, wx.EXPAND | wx.ALL, 20) 170 | panel.SetSizer(vbox) 171 | 172 | 173 | def main(): 174 | 175 | app = wx.App() 176 | ex = Example(None, title='Border') 177 | ex.Show() 178 | app.MainLoop() 179 | 180 | 181 | if __name__ == '__main__': 182 | main() 183 | ``` 184 | 185 | 在上面的例子中,我们在面板 panel 周围放置了一些空间。 186 | 187 | ```python 188 | vbox.Add(midPan, wx.ID_ANY, wx.EXPAND | wx.ALL, 20) 189 | ``` 190 | 191 | `wx.ID_ANY`:代指任意 id。即不管是安装 installing 事件处理器 event handler,还是创建一个新的窗口,我们都不关心 id 的值。 192 | 193 | 在 `border.py` 我们在 `midPan` 面板上放置了 20px 的 border。 `wx.ALL` 将边框大小应用于所有四条边 sides。 194 | 195 | 如果我们使用 `wx.EXPAND` 标志 flag,我们的组件将使用分配给它的所有空间。最后,我们还可以定义我们的组件的对齐方式。我们用下列标志来做: 196 | 197 | - wx.ALIGN_LEFT 198 | - wx.ALIGN_RIGHT 199 | - wx.ALIGN_TOP 200 | - wx.ALIGN_BOTTOM 201 | - wx.ALIGN_CENTER_VERTICAL 202 | - wx.ALIGN_CENTER_HORIZONTAL 203 | - wx.ALIGN_CENTER 204 | 205 | ![Border around a panel](assets/border.png) 206 | 207 | ## GoToClass example 208 | 209 | 在下面的例子中,我们介绍几个重要的想法。 210 | 211 | **goto_class.py** 212 | 213 | ```python 214 | #!/usr/bin/env python3 215 | # -*- coding: utf-8 -*- 216 | 217 | 218 | """ 219 | ZetCode wxPython tutorial 220 | 221 | In this example we create a Go To class 222 | layout with wx.BoxSizer. 223 | 224 | author: Jan Bodnar 225 | website: www.zetcode.com 226 | last modified: April 2018 227 | """ 228 | 229 | import wx 230 | 231 | class Example(wx.Frame): 232 | 233 | def __init__(self, parent, title): 234 | super(Example, self).__init__(parent, title=title) 235 | 236 | self.InitUI() 237 | self.Centre() 238 | 239 | def InitUI(self): 240 | 241 | panel = wx.Panel(self) 242 | 243 | font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT) 244 | 245 | font.SetPointSize(9) 246 | 247 | vbox = wx.BoxSizer(wx.VERTICAL) 248 | 249 | hbox1 = wx.BoxSizer(wx.HORIZONTAL) 250 | st1 = wx.StaticText(panel, label='Class Name') 251 | st1.SetFont(font) 252 | hbox1.Add(st1, flag=wx.RIGHT, border=8) 253 | tc = wx.TextCtrl(panel) 254 | hbox1.Add(tc, proportion=1) 255 | vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10) 256 | 257 | vbox.Add((-1, 10)) 258 | 259 | hbox2 = wx.BoxSizer(wx.HORIZONTAL) 260 | st2 = wx.StaticText(panel, label='Matching Classes') 261 | st2.SetFont(font) 262 | hbox2.Add(st2) 263 | vbox.Add(hbox2, flag=wx.LEFT | wx.TOP, border=10) 264 | 265 | vbox.Add((-1, 10)) 266 | 267 | hbox3 = wx.BoxSizer(wx.HORIZONTAL) 268 | tc2 = wx.TextCtrl(panel, style=wx.TE_MULTILINE) 269 | hbox3.Add(tc2, proportion=1, flag=wx.EXPAND) 270 | vbox.Add(hbox3, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, 271 | border=10) 272 | 273 | vbox.Add((-1, 25)) 274 | 275 | hbox4 = wx.BoxSizer(wx.HORIZONTAL) 276 | cb1 = wx.CheckBox(panel, label='Case Sensitive') 277 | cb1.SetFont(font) 278 | hbox4.Add(cb1) 279 | cb2 = wx.CheckBox(panel, label='Nested Classes') 280 | cb2.SetFont(font) 281 | hbox4.Add(cb2, flag=wx.LEFT, border=10) 282 | cb3 = wx.CheckBox(panel, label='Non-Project classes') 283 | cb3.SetFont(font) 284 | hbox4.Add(cb3, flag=wx.LEFT, border=10) 285 | vbox.Add(hbox4, flag=wx.LEFT, border=10) 286 | 287 | vbox.Add((-1, 25)) 288 | 289 | hbox5 = wx.BoxSizer(wx.HORIZONTAL) 290 | btn1 = wx.Button(panel, label='Ok', size=(70, 30)) 291 | hbox5.Add(btn1) 292 | btn2 = wx.Button(panel, label='Close', size=(70, 30)) 293 | hbox5.Add(btn2, flag=wx.LEFT|wx.BOTTOM, border=5) 294 | vbox.Add(hbox5, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10) 295 | 296 | panel.SetSizer(vbox) 297 | 298 | 299 | def main(): 300 | 301 | app = wx.App() 302 | ex = Example(None, title='Go To Class') 303 | ex.Show() 304 | app.MainLoop() 305 | 306 | 307 | if __name__ == '__main__': 308 | main() 309 | ``` 310 | 311 | 布局很明了,我们创建一个 垂直 sizer,再把五个水平 sizers 放进去。 312 | 313 | ```python 314 | font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT) 315 | 316 | font.SetPointSize(9) 317 | ``` 318 | 319 | 我们将字体大小更改为 9 像素。 320 | 321 | ```python 322 | vbox.Add(hbox3, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, 323 | border=10) 324 | 325 | vbox.Add((-1, 25)) 326 | ``` 327 | 328 | 我们已经知道我们可以控制结合了 flag 参数和 border 参数的 widgets 之间的距离。但是有一个真正的约束。 在 `Add()` 方法中,我们只能为所有给定的边 sides 指定一个边框。 例子中,我们给右边和左边 10 个像素。我们不能给 25 px 的底部。我们可以做的是给底部 10 像素,或者 0 像素,即省略 `wx.BOTTOM`。 所以如果我们需要不同的值,我们可以添加一些额外的空间。 使用 `Add()` 方法,我们也可以插入组件和空间。 329 | 330 | ```python 331 | vbox.Add(hbox5, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10) 332 | ``` 333 | 334 | 我们将两个按钮放在窗口的右侧。要实现这需要做三件事:proportion、align flag 和 `wx.EXPAND` flag。 比例 proportion 必须为 0。当我们调整窗口大小时,我们不应该改变它们的大小。我们不能指定 `wx.EXPAND` 标志。这些按钮仅覆盖已经分配给它们的区域。最后,我们必须指定 `wx.ALIGN_RIGHT` 标志。水平 sizer 从窗口左侧扩展到右侧。所以如果我们指定了 `wx.ALIGN_RIGHT` 标志,那么这些按钮就会放在右侧。 335 | 336 | ![GoToClass window](assets/gotoclass.png) 337 | 338 | ## wx.GridSizer 339 | 340 | `wx.GridSizer` 在二维表格中展示组件。 表格中的每个单元具有相同的大小。 341 | 342 | ```python 343 | wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0) 344 | ``` 345 | 346 | 在构造函数中,我们指定表中的行数和列数,纵向和横向的单元格之间的间隔(单位是像素)。 347 | 348 | 在我们的例子中,我们创建了一个计算器的骨架。 349 | 350 | **calculator.py** 351 | 352 | ```python 353 | #!/usr/bin/env python3 354 | # -*- coding: utf-8 -*- 355 | 356 | 357 | """ 358 | ZetCode wxPython tutorial 359 | 360 | In this example we create a layout 361 | of a calculator with wx.GridSizer. 362 | 363 | author: Jan Bodnar 364 | website: www.zetcode.com 365 | last modified: April 2018 366 | """ 367 | 368 | import wx 369 | 370 | 371 | class Example(wx.Frame): 372 | 373 | def __init__(self, parent, title): 374 | super(Example, self).__init__(parent, title=title) 375 | 376 | self.InitUI() 377 | self.Centre() 378 | 379 | 380 | def InitUI(self): 381 | 382 | menubar = wx.MenuBar() 383 | fileMenu = wx.Menu() 384 | menubar.Append(fileMenu, '&File') 385 | self.SetMenuBar(menubar) 386 | 387 | vbox = wx.BoxSizer(wx.VERTICAL) 388 | self.display = wx.TextCtrl(self, style=wx.TE_RIGHT) 389 | vbox.Add(self.display, flag=wx.EXPAND|wx.TOP|wx.BOTTOM, border=4) 390 | gs = wx.GridSizer(5, 4, 5, 5) 391 | 392 | gs.AddMany( [(wx.Button(self, label='Cls'), 0, wx.EXPAND), 393 | (wx.Button(self, label='Bck'), 0, wx.EXPAND), 394 | (wx.StaticText(self), wx.EXPAND), 395 | (wx.Button(self, label='Close'), 0, wx.EXPAND), 396 | (wx.Button(self, label='7'), 0, wx.EXPAND), 397 | (wx.Button(self, label='8'), 0, wx.EXPAND), 398 | (wx.Button(self, label='9'), 0, wx.EXPAND), 399 | (wx.Button(self, label='/'), 0, wx.EXPAND), 400 | (wx.Button(self, label='4'), 0, wx.EXPAND), 401 | (wx.Button(self, label='5'), 0, wx.EXPAND), 402 | (wx.Button(self, label='6'), 0, wx.EXPAND), 403 | (wx.Button(self, label='*'), 0, wx.EXPAND), 404 | (wx.Button(self, label='1'), 0, wx.EXPAND), 405 | (wx.Button(self, label='2'), 0, wx.EXPAND), 406 | (wx.Button(self, label='3'), 0, wx.EXPAND), 407 | (wx.Button(self, label='-'), 0, wx.EXPAND), 408 | (wx.Button(self, label='0'), 0, wx.EXPAND), 409 | (wx.Button(self, label='.'), 0, wx.EXPAND), 410 | (wx.Button(self, label='='), 0, wx.EXPAND), 411 | (wx.Button(self, label='+'), 0, wx.EXPAND) ]) 412 | 413 | vbox.Add(gs, proportion=1, flag=wx.EXPAND) 414 | self.SetSizer(vbox) 415 | 416 | 417 | def main(): 418 | 419 | app = wx.App() 420 | ex = Example(None, title='Calculator') 421 | ex.Show() 422 | app.MainLoop() 423 | 424 | 425 | if __name__ == '__main__': 426 | main() 427 | ``` 428 | 429 | 注意我们是如何设置在 Bck 和 Close 按钮之间的间隔。 我们只需在其中放置一个空的 `wx.StaticText` 。 430 | 431 | 在我们的例子中,我们使用了 `AddMany()` 方法。 这是一次添加多个组件的便捷方法。 432 | 433 | ```python 434 | gs.AddMany( [(wx.Button(self, label='Cls'), 0, wx.EXPAND), 435 | ... 436 | ``` 437 | 438 | 组件 Widgets 按顺序被放置在表格内。第一行先填充,然后是第二行等 439 | 440 | ![Calculator](assets/calculator.png)Figure: Calculator 441 | 442 | ## wx.FlexGridSizer 443 | 444 | 这个 sizer 与 `wx.GridSizer` 类似。它也在一个二维表中的放置组件。它增加了一些灵活性。`wx.GridSizer` 单元格大小相同。`wx.FlexGridSizer` 中的所有单元格在同一行中具有相同的高度。所有单元在一列中具有相同的宽度。但是所有的行和列不需要是同样的高度或宽度。 445 | 446 | ```python 447 | wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0) 448 | ``` 449 | 450 | `rows` 和 `cols` 指定了 sizer 中的行数和列数。 `vgap` 和 `hgap` 在两个方向上的组件之间添加一些空间。 451 | 452 | 很多时候开发人员必须开发用于数据输入和修改的对话框。 我发现 `wx.FlexGridSizer` 适合这样的任务。 开发人员可以使用此 sizer 轻松设置对话窗口 dialog window。 使用 `wx.GridSizer` 也可以实现,但由于每个单元必须具有相同的大小,所以它看起来不太好。 453 | 454 | **review.py** 455 | 456 | ```python 457 | #!/usr/bin/env python3 458 | # -*- coding: utf-8 -*- 459 | 460 | """ 461 | ZetCode wxPython tutorial 462 | 463 | In this example we create review 464 | layout with wx.FlexGridSizer. 465 | 466 | author: Jan Bodnar 467 | website: www.zetcode.com 468 | last modified: April 2018 469 | """ 470 | 471 | import wx 472 | 473 | class Example(wx.Frame): 474 | 475 | def __init__(self, parent, title): 476 | super(Example, self).__init__(parent, title=title) 477 | 478 | self.InitUI() 479 | self.Centre() 480 | self.Show() 481 | 482 | def InitUI(self): 483 | 484 | panel = wx.Panel(self) 485 | 486 | hbox = wx.BoxSizer(wx.HORIZONTAL) 487 | 488 | fgs = wx.FlexGridSizer(3, 2, 9, 25) 489 | 490 | title = wx.StaticText(panel, label="Title") 491 | author = wx.StaticText(panel, label="Author") 492 | review = wx.StaticText(panel, label="Review") 493 | 494 | tc1 = wx.TextCtrl(panel) 495 | tc2 = wx.TextCtrl(panel) 496 | tc3 = wx.TextCtrl(panel, style=wx.TE_MULTILINE) 497 | 498 | fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), 499 | (tc2, 1, wx.EXPAND), (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)]) 500 | 501 | fgs.AddGrowableRow(2, 1) 502 | fgs.AddGrowableCol(1, 1) 503 | 504 | hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) 505 | panel.SetSizer(hbox) 506 | 507 | 508 | def main(): 509 | 510 | app = wx.App() 511 | ex = Example(None, title='Review') 512 | ex.Show() 513 | app.MainLoop() 514 | 515 | 516 | if __name__ == '__main__': 517 | main() 518 | ``` 519 | 520 | 在上面的代码示例中,我们使用 `FlexGridSizer` 创建了一个 Review 窗口。 521 | 522 | ```python 523 | hbox = wx.BoxSizer(wx.HORIZONTAL) 524 | ... 525 | hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) 526 | ``` 527 | 528 | 我们创建一个水平 box sizer,为了在组件的表格周围放一些间隔空间(15px)。 529 | 530 | ```python 531 | fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), 532 | (tc2, 1, wx.EXPAND), (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)]) 533 | ``` 534 | 535 | 我们使用 `AddMany()` 方法将组件添加到 sizer 中。 `wx.FlexGridSizer` 和 `wx.GridSizer` 共用此方法。 536 | 537 | ```python 538 | fgs.AddGrowableRow(2, 1) 539 | fgs.AddGrowableCol(1, 1) 540 | ``` 541 | 542 | 我们使第三行和第二列可以成长。通过这种方式,我们可以在窗口大小调整时让文本框增长。第一个和第二个文本框在水平方向增长,第三个双向增长。我们必须谨记使用 `wx.EXPAND` 使组件可扩展。 543 | 544 | ![Review](assets/review.png) 545 | 546 | ## wx.GridBagSizer 547 | 548 | `wx.GridBagSizer` 是 wxPython 中最灵活的 sizer。这种 sizer 不仅适用于 wxPython,我们也可以在其他工具包中找到它。 549 | 550 | 此 sizer 可以显式定位项目。项目也可以选择跨越多个行或列。`wx.GridBagSizer` 有一个简单的构造函数。 551 | 552 | ```python 553 | wx.GridBagSizer(integer vgap, integer hgap) 554 | ``` 555 | 556 | 垂直和水平 gap:定义所有孩子的间隔(单位为像素)。 我们用 `Add()` 方法将项目添加到网格中。 557 | 558 | ```python 559 | Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, 560 | integer border=0, userData=None) 561 | ``` 562 | 563 | item:一个插入到网格中的组件。 564 | 565 | pos:指定虚拟网格中的位置。左上角的单元格的位置为 (0, 0)。  566 | 567 | span:是一个可选的 spanning 组件的;例如,一个 (3, 2) 的 span,跨越 3 行 2 列的组件。  568 | 569 | flag 和 border:在前面的 `wx.BoxSizer` 中讨论过。 570 | 571 | 调整窗口大小时,网格中的项目可以更改其大小或保持默认大小。 如果我们想要增长和缩小,我们可以使用以下两种方法: 572 | 573 | ```python 574 | AddGrowableRow(integer row) 575 | AddGrowableCol(integer col) 576 | ``` 577 | 578 | ## Rename window example 579 | 580 | 在我们的第一个例子中,我们创建了一个 Rename 窗口。它将有一个 `wx.StaticText`,一个 `wx.TextCtrl` 和两个 `wx.Button` 组件。 581 | 582 | **rename.py** 583 | 584 | ```python 585 | #!/usr/bin/env python3 586 | # -*- coding: utf-8 -*- 587 | 588 | """ 589 | ZetCode wxPython tutorial 590 | 591 | In this example we create a rename layout 592 | with wx.GridBagSizer. 593 | 594 | author: Jan Bodnar 595 | website: www.zetcode.com 596 | last modified: April 2018 597 | """ 598 | 599 | import wx 600 | 601 | 602 | class Example(wx.Frame): 603 | 604 | def __init__(self, parent, title): 605 | super(Example, self).__init__(parent, title=title) 606 | 607 | self.InitUI() 608 | self.Centre() 609 | 610 | def InitUI(self): 611 | 612 | panel = wx.Panel(self) 613 | sizer = wx.GridBagSizer(4, 4) 614 | 615 | text = wx.StaticText(panel, label="Rename To") 616 | sizer.Add(text, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=5) 617 | 618 | tc = wx.TextCtrl(panel) 619 | sizer.Add(tc, pos=(1, 0), span=(1, 5), 620 | flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) 621 | 622 | buttonOk = wx.Button(panel, label="Ok", size=(90, 28)) 623 | buttonClose = wx.Button(panel, label="Close", size=(90, 28)) 624 | sizer.Add(buttonOk, pos=(3, 3)) 625 | sizer.Add(buttonClose, pos=(3, 4), flag=wx.RIGHT|wx.BOTTOM, border=10) 626 | 627 | sizer.AddGrowableCol(1) 628 | sizer.AddGrowableRow(2) 629 | panel.SetSizer(sizer) 630 | 631 | 632 | def main(): 633 | 634 | app = wx.App() 635 | ex = Example(None, title='Rename') 636 | ex.Show() 637 | app.MainLoop() 638 | 639 | 640 | if __name__ == '__main__': 641 | main() 642 | ``` 643 | 644 | 我们必须将窗口看做一个大的网格。 645 | 646 | ```python 647 | text = wx.StaticText(panel, label="Rename To") 648 | sizer.Add(text, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=10) 649 | ``` 650 | 651 | The text "Rename to" goes to the left upper corner. So we specify the (0, 0) position. And we add some space to the bottom, left, and bottom. 652 | 653 | 文本“重命名”放在左上角,所以我们指定 (0, 0) 的位置。我们在底部,左侧和底部添加一些空间。 654 | 655 | ```python 656 | tc = wx.TextCtrl(panel) 657 | sizer.Add(tc, pos=(1, 0), span=(1, 5), 658 | flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) 659 | ``` 660 | 661 | `wx.TextCtrl` 进入第二行的开始处 (1, 0)。 请记住,我们从零开始计数。它扩展了 1 行 和 5 列 (1, 5)。我们把5个像素的空间放在左边。 662 | 663 | ```python 664 | sizer.Add(buttonOk, pos=(3, 3)) 665 | sizer.Add(buttonClose, pos=(3, 4), flag=wx.RIGHT|wx.BOTTOM, border=10) 666 | ``` 667 | 668 | 我们把两个按钮放到第四行,第三行留空,所以在 `wx.TextCtrl` 和按钮之间有一些空间。我们把 OK 按钮放到第四列,Close 按钮放在第五列。注意我们将间隔空间应用到一个组件,它会应用到整行。这就是为什么我们不给OK 按钮指定间隔。细心的读者可能注意到我们没有在两个按钮之间指定任何空间。 也就是说,我们没有在右侧 OK 按钮和右侧是 Close 按钮上放置任何空间。 在 `wx.GridBagSizer` 的构造函数中,我们在所有组件之间放置了一些空间。 所以已经有一些空间了。 669 | 670 | ```python 671 | sizer.AddGrowableCol(1) 672 | sizer.AddGrowableRow(2) 673 | ``` 674 | 675 | 我们必须做的最后一件事是让我们的对话框可调整大小。我们让第二列和第三列可增长。现在我们可以扩大或缩小我们的窗口。尝试评论这两行,看看会发生什么。 676 | 677 | ![Rename](assets/rename.png) 678 | 679 | ## New class example 680 | 681 | 在下一个示例中,我们创建一个窗口,该窗口可以在 JDeveloper 中找到。 这是用于给 Java 创建一个新类的窗口。 682 | 683 | **new_class.py** 684 | 685 | ```python 686 | #!/usr/bin/env python3 687 | # -*- coding: utf-8 -*- 688 | 689 | """ 690 | ZetCode wxPython tutorial 691 | 692 | In this example we create a new class layout 693 | with wx.GridBagSizer. 694 | 695 | author: Jan Bodnar 696 | website: www.zetcode.com 697 | last modified: April 2018 698 | """ 699 | 700 | import wx 701 | 702 | class Example(wx.Frame): 703 | 704 | def __init__(self, parent, title): 705 | super(Example, self).__init__(parent, title=title) 706 | 707 | self.InitUI() 708 | self.Centre() 709 | 710 | def InitUI(self): 711 | 712 | panel = wx.Panel(self) 713 | 714 | sizer = wx.GridBagSizer(5, 5) 715 | 716 | text1 = wx.StaticText(panel, label="Java Class") 717 | sizer.Add(text1, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM, 718 | border=15) 719 | 720 | icon = wx.StaticBitmap(panel, bitmap=wx.Bitmap('exec.png')) 721 | sizer.Add(icon, pos=(0, 4), flag=wx.TOP|wx.RIGHT|wx.ALIGN_RIGHT, 722 | border=5) 723 | 724 | line = wx.StaticLine(panel) 725 | sizer.Add(line, pos=(1, 0), span=(1, 5), 726 | flag=wx.EXPAND|wx.BOTTOM, border=10) 727 | 728 | text2 = wx.StaticText(panel, label="Name") 729 | sizer.Add(text2, pos=(2, 0), flag=wx.LEFT, border=10) 730 | 731 | tc1 = wx.TextCtrl(panel) 732 | sizer.Add(tc1, pos=(2, 1), span=(1, 3), flag=wx.TOP|wx.EXPAND) 733 | 734 | text3 = wx.StaticText(panel, label="Package") 735 | sizer.Add(text3, pos=(3, 0), flag=wx.LEFT|wx.TOP, border=10) 736 | 737 | tc2 = wx.TextCtrl(panel) 738 | sizer.Add(tc2, pos=(3, 1), span=(1, 3), flag=wx.TOP|wx.EXPAND, 739 | border=5) 740 | 741 | button1 = wx.Button(panel, label="Browse...") 742 | sizer.Add(button1, pos=(3, 4), flag=wx.TOP|wx.RIGHT, border=5) 743 | 744 | text4 = wx.StaticText(panel, label="Extends") 745 | sizer.Add(text4, pos=(4, 0), flag=wx.TOP|wx.LEFT, border=10) 746 | 747 | combo = wx.ComboBox(panel) 748 | sizer.Add(combo, pos=(4, 1), span=(1, 3), 749 | flag=wx.TOP|wx.EXPAND, border=5) 750 | 751 | button2 = wx.Button(panel, label="Browse...") 752 | sizer.Add(button2, pos=(4, 4), flag=wx.TOP|wx.RIGHT, border=5) 753 | 754 | sb = wx.StaticBox(panel, label="Optional Attributes") 755 | 756 | boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) 757 | boxsizer.Add(wx.CheckBox(panel, label="Public"), 758 | flag=wx.LEFT|wx.TOP, border=5) 759 | boxsizer.Add(wx.CheckBox(panel, label="Generate Default Constructor"), 760 | flag=wx.LEFT, border=5) 761 | boxsizer.Add(wx.CheckBox(panel, label="Generate Main Method"), 762 | flag=wx.LEFT|wx.BOTTOM, border=5) 763 | sizer.Add(boxsizer, pos=(5, 0), span=(1, 5), 764 | flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT , border=10) 765 | 766 | button3 = wx.Button(panel, label='Help') 767 | sizer.Add(button3, pos=(7, 0), flag=wx.LEFT, border=10) 768 | 769 | button4 = wx.Button(panel, label="Ok") 770 | sizer.Add(button4, pos=(7, 3)) 771 | 772 | button5 = wx.Button(panel, label="Cancel") 773 | sizer.Add(button5, pos=(7, 4), span=(1, 1), 774 | flag=wx.BOTTOM|wx.RIGHT, border=10) 775 | 776 | sizer.AddGrowableCol(2) 777 | 778 | panel.SetSizer(sizer) 779 | sizer.Fit(self) 780 | 781 | 782 | def main(): 783 | 784 | app = wx.App() 785 | ex = Example(None, title="Create Java Class") 786 | ex.Show() 787 | app.MainLoop() 788 | 789 | 790 | if __name__ == '__main__': 791 | main() 792 | ``` 793 | 794 | 这是一个更复杂的布局,我们使用 `wx.GridBagSizer` 和 `wx.StaticBoxsizer`。 795 | 796 | ```python 797 | line = wx.StaticLine(panel) 798 | sizer.Add(line, pos=(1, 0), span=(1, 5), 799 | flag=wx.EXPAND|wx.BOTTOM, border=10) 800 | ``` 801 | 802 | 这是用于在布局中分隔小组件组的一行。 803 | 804 | ```python 805 | icon = wx.StaticBitmap(panel, bitmap=wx.Bitmap('exec.png')) 806 | sizer.Add(icon, pos=(0, 4), flag=wx.TOP|wx.RIGHT|wx.ALIGN_RIGHT, 807 | border=5) 808 | ``` 809 | 810 | 我们把一个 `wx.StaticBitmap` 放进网格的第一行,我们把它放在行的右侧。 811 | 812 | ```python 813 | sb = wx.StaticBox(panel, label="Optional Attributes") 814 | boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) 815 | ``` 816 | 817 | `wxStaticBoxSizer` 就像一个普通的 `wx.BoxSizer`,但它在 sizer 周围添加 static box。我们将复选框放入 static box sizer 中。 818 | 819 | ![New class window](assets/newclass.png) 820 | 821 | 这部分 wxPython 教程专门用于布局管理。 822 | -------------------------------------------------------------------------------- /5.事件.md: -------------------------------------------------------------------------------- 1 | # Events in wxPython 2 | 3 | 事件是每个 GUI 应用程序的必需组成部分。所有 GUI 应用程序都是事件驱动的。应用程序会对其生命期间生成的不同事件类型作出反应。事件主要由应用程序的用户生成。但它们也可以通过其他方式产生;例如互联网连接,窗口管理器或计时器。所以当我们调用 `MainLoop()` 方法时,我们的应用程序会等待事件产生。当我们退出应用程序时,`MainLoop()` 方法结束。 4 | 5 | ## 定义 6 | 7 | 从底层框架(一般是 GUI 工具包)看,事件是一段应用层级的信息。事件循环是一种编程结构,用于等待分发 dispatch 程序中的事件或消息。事件循环重复查找要处理的事件。调度员 dispatcher 是将事件 event 映射到事件处理器 event handlers 的过程 。事件处理器是对事件做出反应的方法 methods。 8 | 9 | Event object:事件对象是与事件关联的对象,通常是一个窗口。 10 | 11 | Event type:事件类型是一种已生成的特殊事件。 12 | 13 | Event binder:事件绑定器是一个将事件类型与事件处理器绑定的对象。 14 | 15 | ## wxPython wx.EVT_MOVE example 16 | 17 | 我们对 `wx.MoveEvent` 事件做出反应。当我们将窗口移动到新的位置时,会产生事件。这个事件的 event binder 是 `wx.EVT_MOVE` 。 18 | 19 | **simple_event.py** 20 | 21 | ```python 22 | #!/usr/bin/env python3 23 | # -*- coding: utf-8 -*- 24 | 25 | """ 26 | ZetCode wxPython tutorial 27 | 28 | This is a wx.MoveEvent event demostration. 29 | 30 | author: Jan Bodnar 31 | website: www.zetcode.com 32 | last modified: April 2018 33 | """ 34 | 35 | import wx 36 | 37 | class Example(wx.Frame): 38 | 39 | def __init__(self, *args, **kw): 40 | super(Example, self).__init__(*args, **kw) 41 | 42 | self.InitUI() 43 | 44 | 45 | def InitUI(self): 46 | 47 | wx.StaticText(self, label='x:', pos=(10,10)) 48 | wx.StaticText(self, label='y:', pos=(10,30)) 49 | 50 | self.st1 = wx.StaticText(self, label='', pos=(30, 10)) 51 | self.st2 = wx.StaticText(self, label='', pos=(30, 30)) 52 | 53 | self.Bind(wx.EVT_MOVE, self.OnMove) 54 | 55 | self.SetSize((350, 250)) 56 | self.SetTitle('Move event') 57 | self.Centre() 58 | 59 | def OnMove(self, e): 60 | 61 | x, y = e.GetPosition() 62 | self.st1.SetLabel(str(x)) 63 | self.st2.SetLabel(str(y)) 64 | 65 | 66 | def main(): 67 | 68 | app = wx.App() 69 | ex = Example(None) 70 | ex.Show() 71 | app.MainLoop() 72 | 73 | 74 | if __name__ == '__main__': 75 | main() 76 | ``` 77 | 78 | 该示例显示窗口的当前位置。 79 | 80 | ```python 81 | self.Bind(wx.EVT_MOVE, self.OnMove) 82 | ``` 83 | 84 | 在这里,我们将 `wx.EVT_MOVE` 事件绑定器绑定到 `OnMove()` 方法。 85 | 86 | ```python 87 | def OnMove(self, e): 88 | 89 | x, y = e.GetPosition() 90 | self.st1.SetLabel(str(x)) 91 | self.st2.SetLabel(str(y)) 92 | ``` 93 | 94 | `OnMove()` 方法中的 event 参数是针对于特定事件类型的对象。在我们的例子中,它是一个 `wx.MoveEvent` 类的实例。该对象包含有关该事件的信息。例如,事件对象或窗口的位置。在我们的例子中,事件对象是 `wx.Frame` 组件。 我们可以通过调用事件的 `GetPosition()` 方法来找出当前位置。 95 | 96 | ![Move event](assets/moveevent.png) 97 | 98 | ## wxPython event binding 99 | 100 | 在 WxPython 中事件的三个步骤: 101 | 102 | - 识别事件绑定器 event binder 的名称: `wx.EVT_SIZE`,`wx.EVT_CLOSE` 等 103 | - 创建一个事件处理器 event handler。这个方法在事件产生时被调用。 104 | - 将事件绑定到事件处理器 event handler。 105 | 106 | 在 wxPython 中我们说要绑定一个方法到一个事件。有时会使用 word hook。你通过调用 `Bind()` 方法来绑定一个事件。 该方法具有以下参数: 107 | 108 | ```python 109 | Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY) 110 | ``` 111 | 112 | `event` : 是 EVT_ * 对象之一。它指定事件的类型。 113 | 114 | `handler` : 是要调用的对象。换句话说,这是一种程序员绑定到事件的方法。 115 | 116 | `source` : 当我们想要区分拥有相同的事件类型的不同组件时使用 `source`。 117 | 118 | `id` : 当我们有多个按钮,菜单项等时使用 `id` 参数。 `id` 用于区分它们。 119 | 120 | `id2` : 用于当需要将 handler 绑定到一系列 id 时,例如使用 `EVT_MENU_RANGE`。 121 | 122 | 请注意,方法 `Bind()` 在类 `EvtHandler` 定义。 它是 `wx.Window` 继承的类。 `wx.Window` 是 wxPython 中大多数组件的基类。还有一个相反的过程。如果我们想要从事件中解除一个方法,我们称之为 `Unbind()` 方法。 它具有与上面相同的参数。 123 | 124 | ## Vetoing events 125 | 126 | 有时我们需要停止处理事件。 要做到这一点,我们调用方法 `Veto()` 。 127 | 128 | **event_veto.py** 129 | 130 | ```python 131 | #!/usr/bin/env python3 132 | # -*- coding: utf-8 -*- 133 | 134 | import wx 135 | 136 | """ 137 | ZetCode wxPython tutorial 138 | 139 | In this example we veto an event. 140 | 141 | author: Jan Bodnar 142 | website: www.zetcode.com 143 | last modified: April 2018 144 | """ 145 | 146 | class Example(wx.Frame): 147 | 148 | def __init__(self, *args, **kw): 149 | super(Example, self).__init__(*args, **kw) 150 | 151 | self.InitUI() 152 | 153 | def InitUI(self): 154 | 155 | self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 156 | 157 | self.SetTitle('Event veto') 158 | self.Centre() 159 | 160 | def OnCloseWindow(self, e): 161 | 162 | dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', 163 | wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 164 | 165 | ret = dial.ShowModal() 166 | 167 | if ret == wx.ID_YES: 168 | self.Destroy() 169 | else: 170 | e.Veto() 171 | 172 | 173 | def main(): 174 | 175 | app = wx.App() 176 | ex = Example(None) 177 | ex.Show() 178 | app.MainLoop() 179 | 180 | 181 | if __name__ == '__main__': 182 | main() 183 | ``` 184 | 185 | 在我们的例子中,我们处理了一个 `wx.CloseEvent`。当我们点击标题栏上的 X 按钮时,按下 Alt + F4 或从系统菜单中选择关闭,这个事件被调用。在许多应用程序中,我们希望在我们进行了一些更改时防止意外关闭窗口。 为此,我们必须绑定 `wx.EVT_CLOSE` 事件绑定器。 186 | 187 | ```python 188 | dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', 189 | wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 190 | 191 | ret = dial.ShowModal() 192 | ``` 193 | 194 | 在处理关闭事件的过程中,我们显示一个消息对话框。 195 | 196 | ```python 197 | if ret == wx.ID_YES: 198 | self.Destroy() 199 | else: 200 | event.Veto() 201 | ``` 202 | 203 | 根据对话框的返回值,我们销毁窗口,或否决事件。请注意,要关闭窗口,我们必须调用 `Destroy()` 方法。 如果调用 `Close()` 方法,我们最终会陷入无限循环。 204 | 205 | ## wxPython event propagation 206 | 207 | 有两种类型的事件:基本事件和命令事件。 它们在传播 propagation 上不同。 事件传播 Event propagation 遍历从 child widgets 到 parent widgets 和 grand parent widgets 的事件。 208 | 209 | 基本事件不会传播。 命令事件会传播。 例如 `wx.CloseEvent` 是一个基本事件,这个事件传播到 parent widgets 是没有意义的。 210 | 211 | 默认情况下,事件处理器中捕获的事件停止传播。为了继续传播,我们调用 `Skip()` 方法。 212 | 213 | **event_propagation.py** 214 | 215 | ```python 216 | #!/usr/bin/env python3 217 | # -*- coding: utf-8 -*- 218 | 219 | """ 220 | ZetCode wxPython tutorial 221 | 222 | This example demonstrates event propagation. 223 | 224 | author: Jan Bodnar 225 | website: www.zetcode.com 226 | last modified: April 2018 227 | """ 228 | 229 | import wx 230 | 231 | class MyPanel(wx.Panel): 232 | 233 | def __init__(self, *args, **kw): 234 | super(MyPanel, self).__init__(*args, **kw) 235 | 236 | self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) 237 | 238 | def OnButtonClicked(self, e): 239 | 240 | print('event reached panel class') 241 | e.Skip() 242 | 243 | 244 | class MyButton(wx.Button): 245 | 246 | def __init__(self, *args, **kw): 247 | super(MyButton, self).__init__(*args, **kw) 248 | 249 | self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) 250 | 251 | def OnButtonClicked(self, e): 252 | 253 | print('event reached button class') 254 | e.Skip() 255 | 256 | 257 | class Example(wx.Frame): 258 | 259 | def __init__(self, *args, **kw): 260 | super(Example, self).__init__(*args, **kw) 261 | 262 | self.InitUI() 263 | 264 | 265 | def InitUI(self): 266 | 267 | mpnl = MyPanel(self) 268 | 269 | MyButton(mpnl, label='Ok', pos=(15, 15)) 270 | 271 | self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) 272 | 273 | self.SetTitle('Propagate event') 274 | self.Centre() 275 | 276 | def OnButtonClicked(self, e): 277 | 278 | print('event reached frame class') 279 | e.Skip() 280 | 281 | 282 | def main(): 283 | 284 | app = wx.App() 285 | ex = Example(None) 286 | ex.Show() 287 | app.MainLoop() 288 | 289 | 290 | if __name__ == '__main__': 291 | main() 292 | ``` 293 | 294 | 在我们的例子中,我们在面板 panel 上有一个按钮。 该面板放置在一个框架组件 frame widget 中。 我们为所有组件定义一个处理器。 295 | 296 | ```python 297 | def OnButtonClicked(self, e): 298 | 299 | print('event reached button class') 300 | e.Skip() 301 | ``` 302 | 303 | 我们在自定义按钮类中处理按钮单击事件。 `Skip()` 方法将事件传播到面板类。 304 | 305 | 尝试省略一些 `Skip()` 方法,看看会发生什么。 306 | 307 | ## Window identifiers 308 | 309 | - let the system automatically create an id 310 | - use standard identifiers 311 | - create your own id 312 | 313 | 窗口标识符 Window identifiers 是唯一确定事件系统中窗口标识 window identity 的整数。有三种方法可以创建窗口 id。 314 | 315 | - 让系统自动创建一个id 316 | - 使用标准标识符 standard identifiers 317 | - 创建你自己的 id 318 | 319 | ```python 320 | wx.Button(parent, -1) 321 | wx.Button(parent, wx.ID_ANY) 322 | ``` 323 | 324 | 如果我们为参数 id 提供 -1 或 `wx.ID_ANY`,我们让 wxPython 为我们自动创建一个id。自动创建的 id 始终为负值,而用户指定的 id 必须始终为正值。 我们通常在不需要更改组件状态时使用此选项。例如,在应用程序生命周期中静态文本 static text 永远不会更改。如果我们想要,我们仍然可以得到这个 id。 方法 `GetId()` 可以确定 id。 325 | 326 | **default_ids.py** 327 | 328 | ```python 329 | #!/usr/bin/env python3 330 | # -*- coding: utf-8 -*- 331 | 332 | """ 333 | ZetCode wxPython tutorial 334 | 335 | In this example we use automatic ids 336 | with wx.ID_ANY. 337 | 338 | author: Jan Bodnar 339 | website: www.zetcode.com 340 | last modified: April 2018 341 | """ 342 | 343 | import wx 344 | 345 | 346 | class Example(wx.Frame): 347 | 348 | def __init__(self, *args, **kw): 349 | super(Example, self).__init__(*args, **kw) 350 | 351 | self.InitUI() 352 | 353 | def InitUI(self): 354 | 355 | pnl = wx.Panel(self) 356 | exitButton = wx.Button(pnl, wx.ID_ANY, 'Exit', (10, 10)) 357 | 358 | self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId()) 359 | 360 | self.SetTitle("Automatic ids") 361 | self.Centre() 362 | 363 | def OnExit(self, event): 364 | 365 | self.Close() 366 | 367 | 368 | def main(): 369 | 370 | app = wx.App() 371 | ex = Example(None) 372 | ex.Show() 373 | app.MainLoop() 374 | 375 | 376 | if __name__ == '__main__': 377 | main() 378 | ``` 379 | 380 | 在这个例子中,我们不关心实际的 id 值。 381 | 382 | ```python 383 | self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId()) 384 | ``` 385 | 386 | 我们通过调用 `GetId()` 方法来获得自动生成的ID。 387 | 388 | 建议使用标准标识符 standard identifiers。 标识符可以在某些平台上提供一些标准的图形或行为。 389 | 390 | ## wxPython standard ids 391 | 392 | wxPython 包含一些标准的id,如 `wx.ID_SAVE` 或 `wx.ID_NEW`。 393 | 394 | **standard_ids.py** 395 | 396 | ```python 397 | #!/usr/bin/env python3 398 | # -*- coding: utf-8 -*- 399 | 400 | """ 401 | ZetCode wxPython tutorial 402 | 403 | In this example we create buttons with standard ids. 404 | 405 | author: Jan Bodnar 406 | website: www.zetcode.com 407 | last modified: April 2018 408 | """ 409 | 410 | import wx 411 | 412 | class Example(wx.Frame): 413 | 414 | def __init__(self, *args, **kw): 415 | super(Example, self).__init__(*args, **kw) 416 | 417 | self.InitUI() 418 | 419 | def InitUI(self): 420 | 421 | pnl = wx.Panel(self) 422 | grid = wx.GridSizer(3, 2, 0, 0) 423 | 424 | grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9), 425 | (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9), 426 | (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9), 427 | (wx.Button(pnl, wx.ID_EXIT)), 428 | (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9), 429 | (wx.Button(pnl, wx.ID_NEW))]) 430 | 431 | self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT) 432 | 433 | pnl.SetSizer(grid) 434 | 435 | self.SetTitle("Standard ids") 436 | self.Centre() 437 | 438 | def OnQuitApp(self, event): 439 | 440 | self.Close() 441 | 442 | 443 | def main(): 444 | 445 | app = wx.App() 446 | ex = Example(None) 447 | ex.Show() 448 | app.MainLoop() 449 | 450 | 451 | if __name__ == '__main__': 452 | main() 453 | ``` 454 | 455 | 在我们的例子中,我们在按钮上使用标准标识。在Linux上,按钮有图标。 456 | 457 | ```python 458 | grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9), 459 | (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9), 460 | (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9), 461 | (wx.Button(pnl, wx.ID_EXIT)), 462 | (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9), 463 | (wx.Button(pnl, wx.ID_NEW))]) 464 | ``` 465 | 466 | 我们将六个按钮添加到 grid sizer 中。`wx.ID_CANCEL`,`wx.ID_DELETE`, `wx.ID_SAVE`,`wx.ID_EXIT`,`wx.ID_STOP` 和 `wx.ID_NEW` 是标准标识符。 467 | 468 | ```python 469 | self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT) 470 | ``` 471 | 472 | 我们将按钮点击事件绑定到 `OnQuitApp()` 事件处理器。id 参数用于在按钮之间进行区分,这是我们唯一确定事件的来源。 473 | 474 | ![Standard identifiers](assets/standardidentifiers.png) 475 | 476 | ## Custom event ids 477 | 478 | 最后一个选项就是使用自己的标识符。 我们定义了我们自己的 全局 id。 479 | 480 | **custom_ids.py** 481 | 482 | ```python 483 | #!/usr/bin/env python3 484 | # -*- coding: utf-8 -*- 485 | 486 | """ 487 | ZetCode wxPython tutorial 488 | 489 | In this example we use custom event ids. 490 | 491 | author: Jan Bodnar 492 | website: www.zetcode.com 493 | last modified: April 2018 494 | """ 495 | 496 | import wx 497 | 498 | ID_MENU_NEW = wx.NewId() 499 | ID_MENU_OPEN = wx.NewId() 500 | ID_MENU_SAVE = wx.NewId() 501 | 502 | 503 | class Example(wx.Frame): 504 | 505 | def __init__(self, *args, **kw): 506 | super(Example, self).__init__(*args, **kw) 507 | 508 | self.InitUI() 509 | 510 | def InitUI(self): 511 | 512 | self.CreateMenuBar() 513 | self.CreateStatusBar() 514 | 515 | self.SetSize((350, 250)) 516 | self.SetTitle('Custom ids') 517 | self.Centre() 518 | 519 | def CreateMenuBar(self): 520 | 521 | mb = wx.MenuBar() 522 | 523 | fMenu = wx.Menu() 524 | fMenu.Append(ID_MENU_NEW, 'New') 525 | fMenu.Append(ID_MENU_OPEN, 'Open') 526 | fMenu.Append(ID_MENU_SAVE, 'Save') 527 | 528 | mb.Append(fMenu, '&File') 529 | self.SetMenuBar(mb) 530 | 531 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW) 532 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN) 533 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 534 | 535 | def DisplayMessage(self, e): 536 | 537 | sb = self.GetStatusBar() 538 | 539 | eid = e.GetId() 540 | 541 | if eid == ID_MENU_NEW: 542 | msg = 'New menu item selected' 543 | elif eid == ID_MENU_OPEN: 544 | msg = 'Open menu item selected' 545 | elif eid == ID_MENU_SAVE: 546 | msg = 'Save menu item selected' 547 | 548 | sb.SetStatusText(msg) 549 | 550 | 551 | def main(): 552 | 553 | app = wx.App() 554 | ex = Example(None) 555 | ex.Show() 556 | app.MainLoop() 557 | 558 | 559 | if __name__ == '__main__': 560 | main() 561 | ``` 562 | 563 | 在代码示例中,我们创建了一个包含三个菜单项的菜单。 菜单项的 id 全局创建。 564 | 565 | ```python 566 | ID_MENU_NEW = wx.NewId() 567 | ID_MENU_OPEN = wx.NewId() 568 | ID_MENU_SAVE = wx.NewId() 569 | ``` 570 | 571 | `wx.NewId()` 方法创建一个新的唯一标识。 572 | 573 | ```python 574 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW) 575 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN) 576 | self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 577 | ``` 578 | 579 | 所有三个菜单项都由其唯一 id 标识。 580 | 581 | ```python 582 | eid = e.GetId() 583 | 584 | if eid == ID_MENU_NEW: 585 | msg = 'New menu item selected' 586 | elif eid == ID_MENU_OPEN: 587 | msg = 'Open menu item selected' 588 | elif eid == ID_MENU_SAVE: 589 | msg = 'Save menu item selected' 590 | ``` 591 | 592 | 从事件对象中取回 id。根据值的大小,我们准备消息,该消息显示在应用程序的状态栏中。 593 | 594 | ## wx.PaintEvent 595 | 596 | 重绘窗口时会生成绘图事件 paint event。 当我们调整窗口大小或者最大化窗口时会发生这种情况。绘图事件也可以通过编程方式生成。例如,当我们调用 `SetLabel()` 方法更改 `wx.StaticText` 组件时。请注意,当我们最小化窗口时,不会生成绘图事件。 597 | 598 | **paint_event.py** 599 | 600 | ```python 601 | #!/usr/bin/env python3 602 | # -*- coding: utf-8 -*- 603 | 604 | """ 605 | ZetCode wxPython tutorial 606 | 607 | In this example we count paint events. 608 | 609 | author: Jan Bodnar 610 | website: www.zetcode.com 611 | last modified: April 2018 612 | """ 613 | 614 | import wx 615 | 616 | 617 | class Example(wx.Frame): 618 | 619 | def __init__(self, *args, **kw): 620 | super(Example, self).__init__(*args, **kw) 621 | 622 | self.InitUI() 623 | 624 | def InitUI(self): 625 | 626 | self.count = 0 627 | self.Bind(wx.EVT_PAINT, self.OnPaint) 628 | 629 | self.SetTitle('Paint events') 630 | self.SetSize((350, 250)) 631 | self.Centre() 632 | 633 | def OnPaint(self, e): 634 | 635 | self.count += 1 636 | dc = wx.PaintDC(self) 637 | text = "Number of paint events: {0}".format(self.count) 638 | dc.DrawText(text, 20, 20) 639 | 640 | 641 | def main(): 642 | 643 | app = wx.App() 644 | ex = Example(None) 645 | ex.Show() 646 | app.MainLoop() 647 | 648 | 649 | if __name__ == '__main__': 650 | main() 651 | ``` 652 | 653 | 在我们的例子中,我们计算绘图事件的数量并在窗口上绘制当前生成的事件的数量。 654 | 655 | ```python 656 | self.Bind(wx.EVT_PAINT, self.OnPaint) 657 | ``` 658 | 659 | 我们将 `EVT_PAINT` 事件绑定到 `OnPaint()` 方法。 660 | 661 | ```python 662 | def OnPaint(self, e): 663 | 664 | self.count += 1 665 | dc = wx.PaintDC(self) 666 | text = "Number of paint events: {0}".format(self.count) 667 | dc.DrawText(text, 20, 20) 668 | ``` 669 | 670 | 在 `OnPaint()` 事件中,我们增加计数器,并用 `DrawText()` 方法绘出窗口的 paint events 数。 671 | 672 | ## wx.FocusEvent 673 | 674 | 焦点 focus 指示当前在应用程序中选中的组件。从键盘输入或从剪贴板粘贴文本,传送到的组件,并拥有焦点。 675 | 676 | 有两种焦点事件类型 677 | 678 | `wx.EVT_SET_FOCUS` :它在组件获得焦点时生成。  679 | 680 | `wx.EVT_KILL_FOCUS` :当组件失去焦点时生成。 681 | 682 | 通过点击或者通过键盘按键来改变焦点,通常是 Tab 或者 Shift + Tab。 683 | 684 | **focus_event.py** 685 | 686 | ```python 687 | #!/usr/bin/env python3 688 | # -*- coding: utf-8 -*- 689 | 690 | """ 691 | ZetCode wxPython tutorial 692 | 693 | In this example we work with wx.FocusEvent. 694 | 695 | author: Jan Bodnar 696 | website: www.zetcode.com 697 | last modified: April 2018 698 | """ 699 | 700 | import wx 701 | 702 | class MyWindow(wx.Panel): 703 | 704 | def __init__(self, parent): 705 | super(MyWindow, self).__init__(parent) 706 | 707 | self.color = '#b3b3b3' 708 | 709 | self.Bind(wx.EVT_PAINT, self.OnPaint) 710 | self.Bind(wx.EVT_SIZE, self.OnSize) 711 | self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) 712 | self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) 713 | 714 | def OnPaint(self, e): 715 | 716 | dc = wx.PaintDC(self) 717 | 718 | dc.SetPen(wx.Pen(self.color)) 719 | x, y = self.GetSize() 720 | dc.DrawRectangle(0, 0, x, y) 721 | 722 | def OnSize(self, e): 723 | 724 | self.Refresh() 725 | 726 | def OnSetFocus(self, e): 727 | 728 | self.color = '#ff0000' 729 | self.Refresh() 730 | 731 | def OnKillFocus(self, e): 732 | 733 | self.color = '#b3b3b3' 734 | self.Refresh() 735 | 736 | 737 | class Example(wx.Frame): 738 | 739 | def __init__(self, *args, **kw): 740 | super(Example, self).__init__(*args, **kw) 741 | 742 | self.InitUI() 743 | 744 | 745 | def InitUI(self): 746 | 747 | grid = wx.GridSizer(2, 2, 10, 10) 748 | grid.AddMany([(MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.LEFT, 9), 749 | (MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.RIGHT, 9), 750 | (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9), 751 | (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)]) 752 | 753 | 754 | self.SetSizer(grid) 755 | 756 | self.SetSize((350, 250)) 757 | self.SetTitle('Focus event') 758 | self.Centre() 759 | 760 | 761 | def OnMove(self, e): 762 | 763 | print(e.GetEventObject()) 764 | x, y = e.GetPosition() 765 | self.st1.SetLabel(str(x)) 766 | self.st2.SetLabel(str(y)) 767 | 768 | 769 | def main(): 770 | 771 | app = wx.App() 772 | ex = Example(None) 773 | ex.Show() 774 | app.MainLoop() 775 | 776 | 777 | if __name__ == '__main__': 778 | main() 779 | ``` 780 | 781 | 在我们的例子中,我们有四个面板 panels。 突出显示焦点的面板。 782 | 783 | ```python 784 | self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) 785 | self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) 786 | ``` 787 | 788 | 我们将两个焦点事件绑定到事件处理器。 789 | 790 | ```python 791 | def OnPaint(self, e): 792 | 793 | dc = wx.PaintDC(self) 794 | 795 | dc.SetPen(wx.Pen(self.color)) 796 | x, y = self.GetSize() 797 | dc.DrawRectangle(0, 0, x, y) 798 | ``` 799 | 800 | 在 `OnPaint()` 方法中,我们在窗口上绘图。 轮廓的颜色取决于窗口是否有焦点。 聚焦窗口的轮廓用红色绘制。 801 | 802 | ```python 803 | def OnSetFocus(self, e): 804 | 805 | self.color = '#ff0000' 806 | self.Refresh() 807 | ``` 808 | 809 | 在 `OnSetFocus()` 方法中,我们将 `self.color` 变量设置为红色。我们刷新框架窗口 frame window,它将为其所有子窗口组件生成一个绘图事件。窗户重新绘制,有焦点的组件有一个新颜色的外轮廓。 810 | 811 | ```python 812 | def OnKillFocus(self, e): 813 | 814 | self.color = '#b3b3b3' 815 | self.Refresh() 816 | ``` 817 | 818 | 当窗口失去焦点时, `OnKillFocus()` 方法被调用。我们改变颜色值并刷新。 819 | 820 | ![Focus event](assets/focusevent.png) 821 | 822 | ## wx.KeyEvent 823 | 824 | 当我们按下键盘上的某个键时,会生成一个 `wx.KeyEvent` 。这个事件被发送到有当前焦点的组件。有三种不同的按键处理器: 825 | 826 | - wx.EVT_KEY_DOWN 827 | - wx.EVT_KEY_UP 828 | - wx.EVT_CHAR 829 | 830 | 常见的需求是当 ESC 键按下时关闭程序。 831 | 832 | **key_event.py** 833 | 834 | ```python 835 | #!/usr/bin/env python3 836 | # -*- coding: utf-8 -*- 837 | 838 | """ 839 | ZetCode wxPython tutorial 840 | 841 | In this example we work with wx.KeyEvent. 842 | 843 | author: Jan Bodnar 844 | website: www.zetcode.com 845 | last modified: April 2018 846 | """ 847 | 848 | import wx 849 | 850 | class Example(wx.Frame): 851 | 852 | def __init__(self, *args, **kw): 853 | super(Example, self).__init__(*args, **kw) 854 | 855 | self.InitUI() 856 | 857 | def InitUI(self): 858 | 859 | pnl = wx.Panel(self) 860 | pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 861 | pnl.SetFocus() 862 | 863 | self.SetSize((350, 250)) 864 | self.SetTitle('Key event') 865 | self.Centre() 866 | 867 | def OnKeyDown(self, e): 868 | 869 | key = e.GetKeyCode() 870 | 871 | if key == wx.WXK_ESCAPE: 872 | 873 | ret = wx.MessageBox('Are you sure to quit?', 'Question', 874 | wx.YES_NO | wx.NO_DEFAULT, self) 875 | 876 | if ret == wx.YES: 877 | self.Close() 878 | 879 | 880 | def main(): 881 | 882 | app = wx.App() 883 | ex = Example(None) 884 | ex.Show() 885 | app.MainLoop() 886 | 887 | 888 | if __name__ == '__main__': 889 | main() 890 | ``` 891 | 892 | 在这个例子中,我们处理 Esc 按键。 显示一个消息框以确认终止应用程序。 893 | 894 | ```python 895 | pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 896 | ``` 897 | 898 | 我们将事件处理器绑定到 `wx.EVT_KEY_DOWN` 事件。 899 | 900 | ```python 901 | key = e.GetKeyCode() 902 | ``` 903 | 904 | 这里我们得到按键的键码 905 | 906 | ```python 907 | if key == wx.WXK_ESCAPE: 908 | ``` 909 | 910 | 我们核对键码。 Esc 键有 `wx.WXK_ESCAPE` 码。 911 | 912 | 在本章中,我们讨论了 wxPython中 的事件。 -------------------------------------------------------------------------------- /6.对话框.md: -------------------------------------------------------------------------------- 1 | # wxPython dialogs 2 | 3 | 对话框窗口或对话框是大多数现代 GUI 应用程序不可缺少的部分。对话被定义为两个或更多人之间的对话。在计算机应用程序中,对话框是一个用于与应用程序“交谈”的窗口。 对话框用于输入数据,修改数据,更改应用程序设置等。对话是用户和计算机程序之间交流的重要手段。 4 | 5 | ## A Simple message box 6 | 7 | 消息框 message box 向用户提供简短的信息。一个很好的例子是 CD 刻录应用程序。 当 CD 完成刻录时,会弹出一个消息框。 8 | 9 | **message_box.py** 10 | 11 | ```python 12 | #!/usr/bin/env python3 13 | # -*- coding: utf-8 -*- 14 | 15 | """ 16 | ZetCode wxPython tutorial 17 | 18 | This example shows a simple 19 | message box. 20 | 21 | author: Jan Bodnar 22 | website: www.zetcode.com 23 | last modified: April 2018 24 | """ 25 | 26 | import wx 27 | 28 | 29 | class Example(wx.Frame): 30 | 31 | def __init__(self, *args, **kwargs): 32 | super(Example, self).__init__(*args, **kwargs) 33 | 34 | self.InitUI() 35 | 36 | def InitUI(self): 37 | 38 | wx.CallLater(3000, self.ShowMessage) 39 | 40 | self.SetSize((300, 200)) 41 | self.SetTitle('Message box') 42 | self.Centre() 43 | 44 | def ShowMessage(self): 45 | wx.MessageBox('Download completed', 'Info', 46 | wx.OK | wx.ICON_INFORMATION) 47 | 48 | 49 | def main(): 50 | 51 | app = wx.App() 52 | ex = Example(None) 53 | ex.Show() 54 | app.MainLoop() 55 | 56 | 57 | if __name__ == '__main__': 58 | main() 59 | ``` 60 | 61 | 此示例三秒钟后显示一个消息框。 62 | 63 | ```python 64 | wx.CallLater(3000, self.ShowMessage) 65 | ``` 66 | 67 | `wx.CallLater` 在三秒钟后调用一个方法。第一个参数是一个时间值,之后调用给定的方法。该参数以毫秒为单位。 第二个参数是一个要调用的方法。 68 | 69 | ```python 70 | def ShowMessage(self): 71 | wx.MessageBox('Download completed', 'Info', 72 | wx.OK | wx.ICON_INFORMATION) 73 | ``` 74 | 75 | `wx.MessageBox` 显示一个小对话窗口。我们提供三个参数:文本消息 text message,标题消息 title message和标志 flags。标志用于显示不同的按钮和图标。在我们的例子中,我们显示一个确定按钮和信息图标。 76 | 77 | ![Message box](assets/messagebox.png) 78 | 79 | ## Predefined dialogs 80 | 81 | wxPython 有几个预定义的对话框。这些是常见编程任务的对话框,例如文本、接收输入、加载和保存文件。 82 | 83 | ## Message dialogs 84 | 85 | Message dialogs are used to show messages to the user. They are more flexible than simple message boxes that we saw in the previous example. They are customisable. We can change icons and buttons that will be shown in a dialog. 86 | 87 | 消息对话框用于向用户显示消息,比前面例子中看到的简单消息框更加灵活。它们是可定制的。我们可以更改在对话框中的图标和按钮。 88 | 89 | | flag | meaning | 90 | | ------------------- | ---------------------- | 91 | | wx.OK | 显示 OK 按钮 | 92 | | wx.CANCEL | 显示 Cancel 按钮 | 93 | | wx.YES_NO | 显示 Yes,没有按钮 | 94 | | wx.YES_DEFAULT | 让 Yes 按钮成为默认值 | 95 | | wx.NO_DEFAULT | 让 No 按钮成为默认值 | 96 | | wx.ICON_EXCLAMATION | 显示警告 alert 图标 | 97 | | wx.ICON_ERROR | 显示错误 error 图标 | 98 | | wx.ICON_HAND | 同 wx.ICON_ERROR | 99 | | wx.ICON_INFORMATION | 显示信息 info 图标 | 100 | | wx.ICON_QUESTION | 显示疑问 question 图标 | 101 | 102 | 这些都是可以与 `wx.MessageDialog` 类一起使用的标志 flag。 103 | 104 | **message_dialogs.py** 105 | 106 | ```python 107 | #!/usr/bin/env python3 108 | # -*- coding: utf-8 -*- 109 | 110 | """ 111 | ZetCode wxPython tutorial 112 | 113 | This example shows four types of 114 | message dialogs. 115 | 116 | author: Jan Bodnar 117 | website: www.zetcode.com 118 | last modified: April 2018 119 | """ 120 | 121 | import wx 122 | 123 | 124 | class Example(wx.Frame): 125 | 126 | def __init__(self, *args, **kwargs): 127 | super(Example, self).__init__(*args, **kwargs) 128 | 129 | self.InitUI() 130 | 131 | def InitUI(self): 132 | 133 | panel = wx.Panel(self) 134 | 135 | hbox = wx.BoxSizer() 136 | sizer = wx.GridSizer(2, 2, 2, 2) 137 | 138 | btn1 = wx.Button(panel, label='Info') 139 | btn2 = wx.Button(panel, label='Error') 140 | btn3 = wx.Button(panel, label='Question') 141 | btn4 = wx.Button(panel, label='Alert') 142 | 143 | sizer.AddMany([btn1, btn2, btn3, btn4]) 144 | 145 | hbox.Add(sizer, 0, wx.ALL, 15) 146 | panel.SetSizer(hbox) 147 | 148 | btn1.Bind(wx.EVT_BUTTON, self.ShowMessage1) 149 | btn2.Bind(wx.EVT_BUTTON, self.ShowMessage2) 150 | btn3.Bind(wx.EVT_BUTTON, self.ShowMessage3) 151 | btn4.Bind(wx.EVT_BUTTON, self.ShowMessage4) 152 | 153 | self.SetSize((300, 200)) 154 | self.SetTitle('Messages') 155 | self.Centre() 156 | 157 | def ShowMessage1(self, event): 158 | dial = wx.MessageDialog(None, 'Download completed', 'Info', wx.OK) 159 | dial.ShowModal() 160 | 161 | def ShowMessage2(self, event): 162 | dial = wx.MessageDialog(None, 'Error loading file', 'Error', 163 | wx.OK | wx.ICON_ERROR) 164 | dial.ShowModal() 165 | 166 | def ShowMessage3(self, event): 167 | dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question', 168 | wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 169 | dial.ShowModal() 170 | 171 | def ShowMessage4(self, event): 172 | dial = wx.MessageDialog(None, 'Unallowed operation', 'Exclamation', 173 | wx.OK | wx.ICON_EXCLAMATION) 174 | dial.ShowModal() 175 | 176 | 177 | def main(): 178 | 179 | app = wx.App() 180 | ex = Example(None) 181 | ex.Show() 182 | app.MainLoop() 183 | 184 | 185 | if __name__ == '__main__': 186 | main() 187 | ``` 188 | 189 | 在我们的例子中,我们创建了四个按钮并将它们放入网格 sizer 中。这些按钮将显示四个不同的对话窗口。我们通过指定不同的风格标志来创建它们 190 | 191 | ```python 192 | def ShowMessage2(self, event): 193 | dial = wx.MessageDialog(None, 'Error loading file', 'Error', 194 | wx.OK | wx.ICON_ERROR) 195 | dial.ShowModal() 196 | ``` 197 | 198 | 消息对话框的创建很简单。 通过将 None 设置为父级parent,我们将对话框设置为顶级窗口。两个字符串提供消息文本和对话标题。 我们通过指定 `wx.OK` 和 `wx.ICON_ERROR` 标志来显示一个 OK 按钮和一个错误图标。为了在屏幕上显示对话框,我们调用 `ShowModal()` 方法。 199 | 200 | ## About dialog box 201 | 202 | 几乎每个应用程序都有一个典型的对话框。它通常放置在帮助菜单中,此对话框的目的是为用户提供有关应用程序名称和版本的基本信息。过去这些对话框很简短,现在大多数的框还提供有关作者的更多信息。 他们为别的程序员或文档编写者提供信用。 他们还提供有关应用程序许可证的信息。 这些框可以显示公司的 logo 或应用程序 logo。 203 | 204 | 为了创建关于对话框,我们必须创建两个对象。 `wx.adv.AboutDialogInfo` 和 `wx.adv.AboutBox`。 205 | 206 | wxPython 可以显示两种关于框。这取决于我们使用哪个平台以及我们调用哪些方法。它可以是原生对话框或wxPython 通用对话框。Windows 原生的关于对话框无法显示自定义图标,许可证文本或 URL。如果我们省略这三个字段,wxPython 将显示一个原生对话框。 否则,它将采用通用的。 如果我们想尽可能使用原生对话框,建议在一个单独的菜单项提供许可证信息。GTK+ 可以显示所有这些字段。 207 | 208 | **about_dialog.py** 209 | 210 | ```python 211 | #!/usr/bin/env python3 212 | # -*- coding: utf-8 -*- 213 | 214 | ''' 215 | ZetCode wxPython tutorial 216 | 217 | In this example, we create an 218 | about dialog box. 219 | 220 | author: Jan Bodnar 221 | website: www.zetcode.com 222 | last modified: April 2018 223 | ''' 224 | 225 | import wx 226 | import wx.adv 227 | 228 | 229 | class Example(wx.Frame): 230 | 231 | def __init__(self, *args, **kwargs): 232 | super(Example, self).__init__(*args, **kwargs) 233 | 234 | self.InitUI() 235 | 236 | def InitUI(self): 237 | 238 | menubar = wx.MenuBar() 239 | help = wx.Menu() 240 | help.Append(wx.ID_ANY, '&About') 241 | help.Bind(wx.EVT_MENU, self.OnAboutBox) 242 | 243 | menubar.Append(help, '&Help') 244 | self.SetMenuBar(menubar) 245 | 246 | self.SetSize((350, 250)) 247 | self.SetTitle('About dialog box') 248 | self.Centre() 249 | 250 | def OnAboutBox(self, e): 251 | 252 | description = """File Hunter is an advanced file manager for 253 | the Unix operating system. Features include powerful built-in editor, 254 | advanced search capabilities, powerful batch renaming, file comparison, 255 | extensive archive handling and more. 256 | """ 257 | 258 | licence = """File Hunter is free software; you can redistribute 259 | it and/or modify it under the terms of the GNU General Public License as 260 | published by the Free Software Foundation; either version 2 of the License, 261 | or (at your option) any later version. 262 | 263 | File Hunter is distributed in the hope that it will be useful, 264 | but WITHOUT ANY WARRANTY; without even the implied warranty of 265 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 266 | See the GNU General Public License for more details. You should have 267 | received a copy of the GNU General Public License along with File Hunter; 268 | if not, write to the Free Software Foundation, Inc., 59 Temple Place, 269 | Suite 330, Boston, MA 02111-1307 USA""" 270 | 271 | 272 | info = wx.adv.AboutDialogInfo() 273 | 274 | info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG)) 275 | info.SetName('File Hunter') 276 | info.SetVersion('1.0') 277 | info.SetDescription(description) 278 | info.SetCopyright('(C) 2007 - 2018 Jan Bodnar') 279 | info.SetWebSite('http://www.zetcode.com') 280 | info.SetLicence(licence) 281 | info.AddDeveloper('Jan Bodnar') 282 | info.AddDocWriter('Jan Bodnar') 283 | info.AddArtist('The Tango crew') 284 | info.AddTranslator('Jan Bodnar') 285 | 286 | wx.adv.AboutBox(info) 287 | 288 | 289 | def main(): 290 | 291 | app = wx.App() 292 | ex = Example(None) 293 | ex.Show() 294 | app.MainLoop() 295 | 296 | 297 | if __name__ == '__main__': 298 | main() 299 | ``` 300 | 301 | The example has an about menu item. After selecting the item, the about box is displayed. 302 | 303 | ```python 304 | description = """File Hunter is an advanced file manager for 305 | the Unix operating system. Features include powerful built-in editor, 306 | advanced search capabilities, powerful batch renaming, file comparison, 307 | extensive archive handling and more. 308 | """ 309 | ``` 310 | 311 | 将太多文本放入应用程序的代码并不是最好的办法。我们不想让这个例子太复杂,所以我们把所有的文本放入代码中。 但是在现实世界的程序中,文本应该单独放置在一个文件中。它有助于我们应用程序的维护。 例如,如果我们想将我们的应用程序翻译成其他语言。 312 | 313 | ```python 314 | info = wx.adv.AboutDialogInfo() 315 | ``` 316 | 317 | 第一件要做的事情是创建 `wx.AboutDialogInfo` 对象。构造函数是空的,它不带任何参数。 318 | 319 | ```python 320 | info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG)) 321 | info.SetName('File Hunter') 322 | info.SetVersion('1.0') 323 | info.SetDescription(description) 324 | info.SetCopyright('(C) 2007 - 2014 Jan Bodnar') 325 | info.SetWebSite('http://www.zetcode.com') 326 | info.SetLicence(licence) 327 | info.AddDeveloper('Jan Bodnar') 328 | info.AddDocWriter('Jan Bodnar') 329 | info.AddArtist('The Tango crew') 330 | info.AddTranslator('Jan Bodnar') 331 | ``` 332 | 333 | The next thing to do is to call all necessary methods upon the created `wx.AboutDialogInfo` object. 334 | 335 | 接下来要做的事情是调用 `wx.AboutDialogInfo` 对象中所有必要的方法。 336 | 337 | ```python 338 | wx.adv.AboutBox(info) 339 | ``` 340 | 341 | 最后,我们创建一个 `wx.adv.AboutBox` 组件。 它唯一接收的参数是 `wx.adv.AboutDialogInfo` 对象。 342 | 343 | ![About dialog box](assets/hunter.png) 344 | 345 | ## A custom dialog 346 | 347 | 在下一个例子中,我们创建一个自定义对话框。图像编辑应用程序可以改变图片的颜色深度。为了提供这种功能,我们可以创建一个合适的对话框。 348 | 349 | **custom_dialog.py** 350 | 351 | ```python 352 | #!/usr/bin/env python3 353 | # -*- coding: utf-8 -*- 354 | 355 | ''' 356 | ZetCode wxPython tutorial 357 | 358 | In this code example, we create a 359 | custom dialog. 360 | 361 | author: Jan Bodnar 362 | website: www.zetcode.com 363 | last modified: April 2018 364 | ''' 365 | 366 | import wx 367 | 368 | class ChangeDepthDialog(wx.Dialog): 369 | 370 | def __init__(self, *args, **kw): 371 | super(ChangeDepthDialog, self).__init__(*args, **kw) 372 | 373 | self.InitUI() 374 | self.SetSize((250, 200)) 375 | self.SetTitle("Change Color Depth") 376 | 377 | 378 | def InitUI(self): 379 | 380 | pnl = wx.Panel(self) 381 | vbox = wx.BoxSizer(wx.VERTICAL) 382 | 383 | sb = wx.StaticBox(pnl, label='Colors') 384 | sbs = wx.StaticBoxSizer(sb, orient=wx.VERTICAL) 385 | sbs.Add(wx.RadioButton(pnl, label='256 Colors', 386 | style=wx.RB_GROUP)) 387 | sbs.Add(wx.RadioButton(pnl, label='16 Colors')) 388 | sbs.Add(wx.RadioButton(pnl, label='2 Colors')) 389 | 390 | hbox1 = wx.BoxSizer(wx.HORIZONTAL) 391 | hbox1.Add(wx.RadioButton(pnl, label='Custom')) 392 | hbox1.Add(wx.TextCtrl(pnl), flag=wx.LEFT, border=5) 393 | sbs.Add(hbox1) 394 | 395 | pnl.SetSizer(sbs) 396 | 397 | hbox2 = wx.BoxSizer(wx.HORIZONTAL) 398 | okButton = wx.Button(self, label='Ok') 399 | closeButton = wx.Button(self, label='Close') 400 | hbox2.Add(okButton) 401 | hbox2.Add(closeButton, flag=wx.LEFT, border=5) 402 | 403 | vbox.Add(pnl, proportion=1, 404 | flag=wx.ALL|wx.EXPAND, border=5) 405 | vbox.Add(hbox2, flag=wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, border=10) 406 | 407 | self.SetSizer(vbox) 408 | 409 | okButton.Bind(wx.EVT_BUTTON, self.OnClose) 410 | closeButton.Bind(wx.EVT_BUTTON, self.OnClose) 411 | 412 | 413 | def OnClose(self, e): 414 | 415 | self.Destroy() 416 | 417 | 418 | class Example(wx.Frame): 419 | 420 | def __init__(self, *args, **kw): 421 | super(Example, self).__init__(*args, **kw) 422 | 423 | self.InitUI() 424 | 425 | 426 | def InitUI(self): 427 | 428 | tb = self.CreateToolBar() 429 | tb.AddTool(toolId=wx.ID_ANY, label='', bitmap=wx.Bitmap('color.png')) 430 | 431 | tb.Realize() 432 | 433 | tb.Bind(wx.EVT_TOOL, self.OnChangeDepth) 434 | 435 | self.SetSize((350, 250)) 436 | self.SetTitle('Custom dialog') 437 | self.Centre() 438 | 439 | def OnChangeDepth(self, e): 440 | 441 | cdDialog = ChangeDepthDialog(None, 442 | title='Change Color Depth') 443 | cdDialog.ShowModal() 444 | cdDialog.Destroy() 445 | 446 | 447 | def main(): 448 | 449 | app = wx.App() 450 | ex = Example(None) 451 | ex.Show() 452 | app.MainLoop() 453 | 454 | 455 | if __name__ == '__main__': 456 | main() 457 | ``` 458 | 459 | 在上面的例子中,我们创建了一个自定义对话框。 460 | 461 | ```python 462 | class ChangeDepthDialog(wx.Dialog): 463 | 464 | def __init__(self, *args, **kw): 465 | super(ChangeDepthDialog, self).__init__(*args, **kw) 466 | ``` 467 | 468 | 在我们的代码示例中,我们创建了一个自定义的 ChangeDepthDialog 对话框,从 `wx.Dialog` 小部件继承。 469 | 470 | ```python 471 | def OnChangeDepth(self, e): 472 | 473 | cdDialog = ChangeDepthDialog(None, 474 | title='Change Color Depth') 475 | cdDialog.ShowModal() 476 | cdDialog.Destroy() 477 | ``` 478 | 479 | We instantiate a `ChangeDepthDialog class`. Then we call the `ShowModal()` method. Later, we must destroy our dialog with `Destroy()` 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. 480 | 481 | 我们实例化了一个 `ChangeDepthDialog` 类,然后我们调用 `ShowModal()` 方法。之后,我们必须用 `Destroy()` 销毁我们的对话框。注意下对话框和顶层窗口 toplevel window 的区别,下图中的对话框已被激活,在对话框销毁之前,我们无法使用顶层窗口,同时它们的窗口标题栏有明显不同。 482 | 483 | ![Custom dialog](assets/customdialog.png) 484 | 485 | 在本章中,我们讨论了对话框。 -------------------------------------------------------------------------------- /8.高级组件.md: -------------------------------------------------------------------------------- 1 | # Advanced widgets in wxPython 2 | 3 | 在本章中,我们将讨论以下高级组件: `wx.ListBox` , `wx.html.HtmlWindow` , `wx.ListCtrl` 。 4 | 5 | wxPython 有几个众所周知的高级组件。 例如:tree widget、HTML window、grid widget、listbox widget、list widget 或具有高级样式功能的编辑器。 6 | 7 | ## wx.ListBox widget 8 | 9 | `wx.ListBox` 用于显示和处理项目列表。一个 `wx.ListBox` 可以创建两种不同的状态:单一选择状态或多选状态。 单选是默认状态。 10 | 11 | 在 `wx.ListBox` 中有两个重要的事件。第一个是 `wx.EVT_COMMAND_LISTBOX_SELECTED` 事件,当我们在 `wx.ListBox` 选中一个项目时会产生这个事件。第二个是 `wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED` 事件。 当我们在 `wx.ListBox` 中双击一个项目时会生成。元素从零开始编号。如果需要,滚动条会自动显示。 12 | 13 | **listbox.py** 14 | 15 | ```python 16 | #!/usr/bin/env python3 17 | # -*- coding: utf-8 -*- 18 | 19 | """ 20 | ZetCode wxPython tutorial 21 | 22 | In this example, we create a wx.ListBox widget. 23 | 24 | author: Jan Bodnar 25 | website: www.zetcode.com 26 | last modified: May 2018 27 | """ 28 | 29 | import wx 30 | 31 | class Example(wx.Frame): 32 | 33 | def __init__(self, *args, **kw): 34 | super(Example, self).__init__(*args, **kw) 35 | 36 | self.InitUI() 37 | 38 | def InitUI(self): 39 | 40 | panel = wx.Panel(self) 41 | hbox = wx.BoxSizer(wx.HORIZONTAL) 42 | 43 | self.listbox = wx.ListBox(panel) 44 | hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20) 45 | 46 | btnPanel = wx.Panel(panel) 47 | vbox = wx.BoxSizer(wx.VERTICAL) 48 | newBtn = wx.Button(btnPanel, wx.ID_ANY, 'New', size=(90, 30)) 49 | renBtn = wx.Button(btnPanel, wx.ID_ANY, 'Rename', size=(90, 30)) 50 | delBtn = wx.Button(btnPanel, wx.ID_ANY, 'Delete', size=(90, 30)) 51 | clrBtn = wx.Button(btnPanel, wx.ID_ANY, 'Clear', size=(90, 30)) 52 | 53 | self.Bind(wx.EVT_BUTTON, self.NewItem, id=newBtn.GetId()) 54 | self.Bind(wx.EVT_BUTTON, self.OnRename, id=renBtn.GetId()) 55 | self.Bind(wx.EVT_BUTTON, self.OnDelete, id=delBtn.GetId()) 56 | self.Bind(wx.EVT_BUTTON, self.OnClear, id=clrBtn.GetId()) 57 | self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename) 58 | 59 | vbox.Add((-1, 20)) 60 | vbox.Add(newBtn) 61 | vbox.Add(renBtn, 0, wx.TOP, 5) 62 | vbox.Add(delBtn, 0, wx.TOP, 5) 63 | vbox.Add(clrBtn, 0, wx.TOP, 5) 64 | 65 | btnPanel.SetSizer(vbox) 66 | hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20) 67 | panel.SetSizer(hbox) 68 | 69 | self.SetTitle('wx.ListBox') 70 | self.Centre() 71 | 72 | def NewItem(self, event): 73 | 74 | text = wx.GetTextFromUser('Enter a new item', 'Insert dialog') 75 | if text != '': 76 | self.listbox.Append(text) 77 | 78 | def OnRename(self, event): 79 | 80 | sel = self.listbox.GetSelection() 81 | text = self.listbox.GetString(sel) 82 | renamed = wx.GetTextFromUser('Rename item', 'Rename dialog', text) 83 | 84 | if renamed != '': 85 | self.listbox.Delete(sel) 86 | item_id = self.listbox.Insert(renamed, sel) 87 | self.listbox.SetSelection(item_id) 88 | 89 | def OnDelete(self, event): 90 | 91 | sel = self.listbox.GetSelection() 92 | if sel != -1: 93 | self.listbox.Delete(sel) 94 | 95 | def OnClear(self, event): 96 | self.listbox.Clear() 97 | 98 | 99 | def main(): 100 | 101 | app = wx.App() 102 | ex = Example(None) 103 | ex.Show() 104 | app.MainLoop() 105 | 106 | 107 | if __name__ == '__main__': 108 | main() 109 | ``` 110 | 111 | 该示例显示如何从 `wx.ListBox` 中添加、修改和删除项目。 112 | 113 | ```python 114 | self.listbox = wx.ListBox(panel) 115 | hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20) 116 | ``` 117 | 118 | 我们创建一个空的 `wx.ListBox`,我们在列表框周围放置了一个 20px 的 border。 119 | 120 | ```python 121 | self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename) 122 | ``` 123 | 124 | 我们使用 `OnRename()`方法,用 `wx.EVT_LISTBOX_DCLICK` 绑定器 event binder,绑定了一个 `wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED` 事件类型 event type。这样,如果我们双击列表框中的特定元素,将显示一个重命名对话框。 125 | 126 | ```python 127 | def NewItem(self, event): 128 | 129 | text = wx.GetTextFromUser('Enter a new item', 'Insert dialog') 130 | if text != '': 131 | self.listbox.Append(text) 132 | ``` 133 | 134 | 我们通过点击 New 按钮来调用 `NewItem()` 方法。此方法使用包装器 `wx.GetTextFromUser()` 方法显示`wx.TextEntryDialog`。我们输入的文本返回到文本变量。如果文本不是空的,我们使用 `Append()` 方法将它附加到列表框中。 135 | 136 | ```python 137 | if renamed != '': 138 | self.listbox.Delete(sel) 139 | item_id = self.listbox.Insert(renamed, sel) 140 | self.listbox.SetSelection(item_id) 141 | ``` 142 | 143 | 我们通过删除它并在同一位置插入一个新项目来重命名一个项目。 我们还将选中的项目设置为要修改的项目。 144 | 145 | ```python 146 | def OnDelete(self, event): 147 | 148 | sel = self.listbox.GetSelection() 149 | if sel != -1: 150 | self.listbox.Delete(sel) 151 | ``` 152 | 153 | 要删除一个项目,我们调用 `GetSelection()` 方法找到选中的项目的索引。然后我们用 `Delete()` 方法删除该项目。 该方法的参数是选中项目的索引。 154 | 155 | ```python 156 | def OnClear(self, event): 157 | self.listbox.Clear() 158 | ``` 159 | 160 | 最简单的事情是清除整个列表框。 我们只需调用 `Clear()` 方法。 161 | 162 | ![wx.ListBox widget](assets/listbox2.png) 163 | 164 | ## wx.html.HtmlWindow widget 165 | 166 | `wx.html.HtmlWindow` 组件显示HTML页面。 它不是一个完整的浏览器。 我们可以用 `wx.html.HtmlWindow` 组件做一些有趣的事情。 167 | 168 | 例如在下面的程序中,我们创建一个显示基本统计数据的窗口。 169 | 170 | **page.html** 171 | 172 | ```python 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
  Maximum  9000
  Mean  6076
  Minimum  3800
  Median  6000
  Standard Deviation  6076
198 | 199 | 200 | ``` 201 | 202 | 这是要显示的HTML页面。 203 | 204 | **htmlwin.py** 205 | 206 | ```python 207 | #!/usr/bin/env python3 208 | # -*- coding: utf-8 -*- 209 | 210 | """ 211 | ZetCode wxPython tutorial 212 | 213 | In this example, we create a wx.html.HtmlWindow widget. 214 | 215 | author: Jan Bodnar 216 | website: www.zetcode.com 217 | last modified: May 2018 218 | """ 219 | 220 | import wx 221 | import wx.html 222 | 223 | class Example(wx.Frame): 224 | 225 | def __init__(self, *args, **kw): 226 | super(Example, self).__init__(*args, **kw) 227 | 228 | self.InitUI() 229 | 230 | def InitUI(self): 231 | 232 | panel = wx.Panel(self) 233 | 234 | vbox = wx.BoxSizer(wx.VERTICAL) 235 | hbox = wx.BoxSizer(wx.HORIZONTAL) 236 | 237 | htmlwin = wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER) 238 | htmlwin.SetStandardFonts() 239 | htmlwin.LoadPage("page.html") 240 | 241 | vbox.Add((-1, 10), 0) 242 | vbox.Add(htmlwin, 1, wx.EXPAND | wx.ALL, 9) 243 | 244 | bitmap = wx.StaticBitmap(panel, wx.ID_ANY, wx.Bitmap('newt.png')) 245 | hbox.Add(bitmap, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 10) 246 | btnOk = wx.Button(panel, wx.ID_ANY, 'Ok') 247 | 248 | self.Bind(wx.EVT_BUTTON, self.OnClose, id=btnOk.GetId()) 249 | 250 | hbox.Add((100, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT) 251 | hbox.Add(btnOk, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10) 252 | vbox.Add(hbox, 0, wx.EXPAND) 253 | 254 | panel.SetSizer(vbox) 255 | 256 | self.SetTitle('Basic statistics') 257 | self.Centre() 258 | 259 | def OnClose(self, event): 260 | self.Close() 261 | 262 | 263 | def main(): 264 | 265 | app = wx.App() 266 | ex = Example(None) 267 | ex.Show() 268 | app.MainLoop() 269 | 270 | 271 | if __name__ == '__main__': 272 | main() 273 | ``` 274 | 275 | 该示例显示了 `wx.html.HtmlWindow` 组件中的 HTML 文件。 276 | 277 | ```python 278 | htmlwin = wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER) 279 | htmlwin.SetStandardFonts() 280 | htmlwin.LoadPage("page.html") 281 | ``` 282 | 283 | `wx.html.HtmlWindow` 被创建。HTML文件使用 `LoadPage()` 方法加载。 284 | 285 | ![img](assets/htmlwin.png) 286 | 287 | ## Help window 288 | 289 | 我们可以使用 `wx.html.HtmlWindow` 在我们的应用程序中提供帮助。我们可以创建一个独立的窗口,或者我们可以创建一个将成为应用程序一部分的窗口。以下脚本将使用后一种方法创建一个帮助窗口。 290 | 291 | **helpwindow.py** 292 | 293 | ```python 294 | #!/usr/bin/env python3 295 | # -*- coding: utf-8 -*- 296 | 297 | """ 298 | ZetCode wxPython tutorial 299 | 300 | In this example, we create a help window window 301 | with wx.html.HtmlWindow. 302 | 303 | author: Jan Bodnar 304 | website: www.zetcode.com 305 | last modified: May 2018 306 | """ 307 | 308 | import wx 309 | import wx.html as html 310 | 311 | class Example(wx.Frame): 312 | 313 | def __init__(self, *args, **kw): 314 | super(Example, self).__init__(*args, **kw) 315 | 316 | self.InitUI() 317 | 318 | def InitUI(self): 319 | 320 | toolbar = self.CreateToolBar() 321 | toolbar.AddTool(1, 'Exit', wx.Bitmap('exit.png')) 322 | toolbar.AddTool(2, 'Help', wx.Bitmap('help.png')) 323 | toolbar.Realize() 324 | 325 | self.splitter = wx.SplitterWindow(self) 326 | self.panelLeft = wx.Panel(self.splitter, wx.ID_ANY, style=wx.BORDER_SUNKEN) 327 | 328 | self.panelRight = wx.Panel(self.splitter) 329 | vbox2 = wx.BoxSizer(wx.VERTICAL) 330 | header = wx.Panel(self.panelRight, wx.ID_ANY) 331 | 332 | header.SetBackgroundColour('#6f6a59') 333 | header.SetForegroundColour('white') 334 | 335 | hbox = wx.BoxSizer(wx.HORIZONTAL) 336 | 337 | st = wx.StaticText(header, wx.ID_ANY, 'Help') 338 | font = st.GetFont() 339 | font.SetFamily(wx.FONTFAMILY_ROMAN) 340 | font.SetPointSize(11) 341 | st.SetFont(font) 342 | 343 | hbox.Add(st, 1, wx.TOP | wx.BOTTOM | wx.LEFT, 8) 344 | 345 | closeBtn = wx.BitmapButton(header, wx.ID_ANY, wx.Bitmap('closebutton.png', 346 | wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER) 347 | closeBtn.SetBackgroundColour('#6f6a59') 348 | 349 | hbox.Add(closeBtn, 0, wx.TOP|wx.BOTTOM, 8) 350 | header.SetSizer(hbox) 351 | 352 | vbox2.Add(header, 0, wx.EXPAND) 353 | 354 | helpWin = html.HtmlWindow(self.panelRight, style=wx.NO_BORDER) 355 | helpWin.LoadPage('help.html') 356 | 357 | vbox2.Add(helpWin, 1, wx.EXPAND) 358 | 359 | self.panelRight.SetSizer(vbox2) 360 | self.panelLeft.SetFocus() 361 | 362 | self.splitter.SplitVertically(self.panelLeft, self.panelRight) 363 | self.splitter.Unsplit() 364 | 365 | self.Bind(wx.EVT_BUTTON, self.CloseHelp, id=closeBtn.GetId()) 366 | self.Bind(wx.EVT_TOOL, self.OnClose, id=1) 367 | self.Bind(wx.EVT_TOOL, self.OnHelp, id=2) 368 | 369 | self.panelLeft.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) 370 | self.panelLeft.SetFocus() 371 | 372 | self.CreateStatusBar() 373 | 374 | self.SetTitle('Help') 375 | self.Centre() 376 | 377 | def OnClose(self, e): 378 | self.Close() 379 | 380 | def OnHelp(self, e): 381 | 382 | self.splitter.SplitVertically(self.panelLeft, self.panelRight) 383 | self.panelLeft.SetFocus() 384 | 385 | def CloseHelp(self, e): 386 | 387 | self.splitter.Unsplit() 388 | self.panelLeft.SetFocus() 389 | 390 | def OnKeyPressed(self, e): 391 | 392 | keycode = e.GetKeyCode() 393 | print(keycode) 394 | 395 | if keycode == wx.WXK_F1: 396 | 397 | self.splitter.SplitVertically(self.panelLeft, self.panelRight) 398 | self.panelLeft.SetFocus() 399 | 400 | 401 | def main(): 402 | 403 | app = wx.App() 404 | ex = Example(None) 405 | ex.Show() 406 | app.MainLoop() 407 | 408 | 409 | if __name__ == '__main__': 410 | main() 411 | ``` 412 | 413 | 帮助窗口在开始时隐藏。 我们可以通过点击工具栏上的帮助按钮或按 F1 来显示它。帮助窗口出现在应用程序的右侧。 要隐藏帮助窗口,请单击关闭按钮。 414 | 415 | ```python 416 | self.splitter.SplitVertically(self.panelLeft, self.panelRight) 417 | self.splitter.Unsplit() 418 | ``` 419 | 420 | 我们创建从左到右的面板并垂直分割它们。 之后,我们调用 `Unsplit()` 方法。 该方法默认隐藏右侧或底部面板。 421 | 422 | 我们将右面板分成两部分:标题 header 和面板的主体 body 。标题是一个可调整的 `wx.Panel`。标题由一个静态文本和一个位图按钮 bitmap button 组成。我们把 `wx.html.Window` 放到面板的主体中。 423 | 424 | ```python 425 | closeBtn = wx.BitmapButton(header, wx.ID_ANY, wx.Bitmap('closebutton.png', 426 | wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER) 427 | closeBtn.SetBackgroundColour('#6f6a59') 428 | ``` 429 | 430 | 位图按钮的 style 被设置为 `wx.NO_BORDER`。背景颜色设置为标题面板的颜色,这么做是为了让按钮显示为标题的一部分。 431 | 432 | ```python 433 | helpWin = html.HtmlWindow(self.panelRight, style=wx.NO_BORDER) 434 | helpWin.LoadPage('help.html') 435 | ``` 436 | 437 | 我们在右侧面板上创建一个 `wx.html.HtmlWindow` 组件。在一个单独的文件中有我们自己的HTML代码。这次我们调用 `LoadPage()` 方法来获取HTML代码。 438 | 439 | ```python 440 | self.panelLeft.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed) 441 | self.panelLeft.SetFocus() 442 | ``` 443 | 444 | 我们将焦点 focus 放在左侧面板上。我们可以用 F1 键启动帮助窗口。为了用键盘控制窗口,它必须有焦点。如果我们没有设定焦点,我们将不得不先点击面板,然后才能通过按F1键启动帮助窗口。 445 | 446 | ```python 447 | def OnHelp(self, e): 448 | 449 | self.splitter.SplitVertically(self.panelLeft, self.panelRight) 450 | self.panelLeft.SetFocus() 451 | ``` 452 | 453 | 为了显示帮助窗口,我们调用 `OnHelp()` 方法。 它垂直分割两个面板。 我们一定不能忘记要再次设定焦点,因为最初的焦点会因分割而失去。 454 | 455 | 以下是我们在应用程序中加载的 HTML 文件。 456 | 457 | **help.html** 458 | 459 | ```python 460 | 461 | 462 | 463 | 464 |

Table of Contents

465 | 466 | 475 | 476 |

477 | 478 |

Basic Statistics
479 | Overview of elementary concepts in statistics. 480 | Variables. Correlation. Measurement scales. Statistical significance. 481 | Distributions. Normality assumption. 482 | 483 |

484 | 485 |

486 | 487 |

Advanced Statistics
488 | Overview of advanced concepts in statistics. Anova. Linear regression. 489 | Estimation and hypothesis testing. 490 | Error terms. 491 | 492 |

493 | 494 |

495 | 496 |

Introducing Newt
497 | Introducing the basic functionality of the Newt application. Creating sheets. 498 | Charts. Menus and Toolbars. Importing data. Saving data in various formats. 499 | Exporting data. Shortcuts. List of methods. 500 | 501 |

502 | 503 |

504 | 505 |

Charts
506 | Working with charts. 2D charts. 3D charts. Bar, line, box, pie, range charts. 507 | Scatterplots. Histograms. 508 | 509 |

510 | 511 |

512 | 513 |

Predicting values
514 | Time series and forecasting. Trend Analysis. Seasonality. Moving averages. 515 | Univariate methods. Multivariate methods. Holt-Winters smoothing. 516 | Exponential smoothing. ARIMA. Fourier analysis. 517 | 518 |

519 | 520 |

521 | 522 |

Neural networks
523 | Overview of neural networks. Biology behind neural networks. 524 | Basic artificial Model. Training. Preprocessing. Postprocessing. 525 | Types of neural networks. 526 | 527 |

528 | 529 |

530 | 531 |

Glossary
532 | Terms and definitions in statistics. 533 | 534 |

535 | 536 | 537 | 538 | ``` 539 | 540 | 该 HTML 文件包含应用程序帮助的目录。 541 | 542 | ![img](assets/helpwindow.png) 543 | 544 | ## wx.ListCtrl widget 545 | 546 | `wx.ListCtrl` 是项目列表的图形表示。 一个 `wx.ListBox` 只能有一列。`wx.ListCtrl` 可以有多个列。`wx.ListCtrl` 是一个非常常用和有用的组件。 例如文件管理器使用 `wx.ListCtrl` 来显示文件系统上的目录和文件。 CD 刻录机应用程序在 `wx.ListCtrl` 显示要刻录的文件。 547 | 548 | `wx.ListCtrl` 可以以三种不同的格式使用:列表视图 list view、报告视图 report view 或图标视图 icon view。 这些格式由 `wx.ListCtrl` 窗口样式控制。 `wx.LC_REPORT`,`wx.LC_LIST` 和 `wx.LC_ICON` 。 549 | 550 | ## wx.ListCtrl styles 551 | 552 | - wx.LC_LIST 553 | - wx.LC_REPORT 554 | - wx.LC_VIRTUAL 555 | - wx.LC_ICON 556 | - wx.LC_SMALL_ICON 557 | - wx.LC_ALIGN_LEFT 558 | - wx.LC_EDIT_LABELS 559 | - wx.LC_NO_HEADER 560 | - wx.LC_SORT_ASCENDING 561 | - wx.LC_SORT_DESCENDING 562 | - wx.LC_HRULES 563 | - wx.LC_VRULES 564 | 565 | ### Simple example 566 | 567 | 第一个例子展示了 `wx.ListCtrl` 一些基本功能。 568 | 569 | **actresses.py** 570 | 571 | ```python 572 | #!/usr/bin/env python3 573 | # -*- coding: utf-8 -*- 574 | 575 | """ 576 | ZetCode wxPython tutorial 577 | 578 | In this example, we create a simple 579 | wx.ListCtrl widget. 580 | 581 | author: Jan Bodnar 582 | website: www.zetcode.com 583 | last modified: May 2018 584 | """ 585 | 586 | import wx 587 | 588 | data = [('Jessica Alba', 'Pomona', '1981'), ('Sigourney Weaver', 'New York', '1949'), 589 | ('Angelina Jolie', 'los angeles', '1975'), ('Natalie Portman', 'Jerusalem', '1981'), 590 | ('Rachel Weiss', 'London', '1971'), ('Scarlett Johansson', 'New York', '1984' )] 591 | 592 | 593 | class Example(wx.Frame): 594 | 595 | def __init__(self, *args, **kw): 596 | super(Example, self).__init__(*args, **kw) 597 | 598 | self.InitUI() 599 | 600 | def InitUI(self): 601 | 602 | hbox = wx.BoxSizer(wx.HORIZONTAL) 603 | panel = wx.Panel(self) 604 | 605 | self.list = wx.ListCtrl(panel, wx.ID_ANY, style=wx.LC_REPORT) 606 | self.list.InsertColumn(0, 'name', width=140) 607 | self.list.InsertColumn(1, 'place', width=130) 608 | self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) 609 | 610 | idx = 0 611 | 612 | for i in data: 613 | 614 | index = self.list.InsertItem(idx, i[0]) 615 | self.list.SetItem(index, 1, i[1]) 616 | self.list.SetItem(index, 2, i[2]) 617 | idx += 1 618 | 619 | hbox.Add(self.list, 1, wx.EXPAND) 620 | panel.SetSizer(hbox) 621 | 622 | self.SetTitle('Actresses') 623 | self.Centre() 624 | 625 | 626 | def main(): 627 | 628 | app = wx.App() 629 | ex = Example(None) 630 | ex.Show() 631 | app.MainLoop() 632 | 633 | 634 | if __name__ == '__main__': 635 | main() 636 | ``` 637 | 638 | 代码示例在 `wx.ListCtrl` 中展示了关于女演员的数据。 639 | 640 | ```python 641 | self.list = wx.ListCtrl(panel, wx.ID_ANY, style=wx.LC_REPORT) 642 | ``` 643 | 644 | 我们用 `wx.LC_REPORT` 风格创建一个`wx.ListCtrl`。 645 | 646 | ```python 647 | self.list.InsertColumn(0, 'name', width=140) 648 | self.list.InsertColumn(1, 'place', width=130) 649 | self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) 650 | ``` 651 | 652 | 我们插入三列。我们可以指定列的 `width` 和列的 `format`。 默认格式是 `wx.LIST_FORMAT_LEFT`。 653 | 654 | ```python 655 | idx = 0 656 | 657 | for i in data: 658 | 659 | index = self.list.InsertItem(idx, i[0]) 660 | self.list.SetItem(index, 1, i[1]) 661 | self.list.SetItem(index, 2, i[2]) 662 | idx += 1 663 | ``` 664 | 665 | 我们使用两种方法将数据插入到 `wx.ListCtrl`。 每行都以 `InsertItem()` 方法开始。该方法的第一个参数指定行号。 该方法返回行索引。`SetItem()` 方法将数据添加到当前行的连续列。 666 | 667 | ### Mixins 668 | 669 | `Mixins` 是进一步增强 `wx.ListCtrl` 功能的类。它们位于 `wx.lib.mixins.listctrl` 模块中。 为了使用它们,我们必须从这些类继承。 670 | 671 | 有六个 mixins: 672 | 673 | - wx.ColumnSorterMixin 674 | - wx.ListCtrlAutoWidthMixin 675 | - wx.ListCtrlSelectionManagerMix 676 | - wx.TextEditMixin 677 | - wx.CheckListCtrlMixin 678 | - wx.ListRowHighlighter 679 | 680 | `wx.ColumnSorterMixin` 可以在报告视图中对列进行排序。 681 | 682 | `wx.ListCtrlAutoWidthMixin` 自动将最后一列的大小调整到 `wx.ListCtrl` 的末尾。默认情况下,最后一列不占用剩余空间。 见前面的例子。 683 | 684 | `wx.ListCtrlSelectionManagerMix` 定义了独立于平台的选择策略。 685 | 686 | `wx.TextEditMixin` enables text to be edited 687 | 688 | `wx.CheckListCtrlMixin` 为每一行添加一个复选框 check box 。这样我们可以控制行。我们可以设置每一行为选中状态或未选中。 689 | 690 | `wx.ListRowHighlighter` 处理 `wx.ListCtrl` 中交替行的自动背景高亮。 691 | 692 | ### wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin 693 | 694 | 以下代码显示了我们如何使用 `wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin` 695 | 696 | **autowidth.py** 697 | 698 | ```python 699 | #!/usr/bin/env python3 700 | # -*- coding: utf-8 -*- 701 | 702 | """ 703 | ZetCode wxPython tutorial 704 | 705 | In this example, we use wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin 706 | with a wx.ListBox. 707 | 708 | author: Jan Bodnar 709 | website: www.zetcode.com 710 | last modified: May 2018 711 | """ 712 | 713 | import wx 714 | import wx.lib.mixins.listctrl 715 | 716 | data = [('Jessica Alba', 'Pomona', '1981'), ('Sigourney Weaver', 'New York', '1949'), 717 | ('Angelina Jolie', 'Los Angeles', '1975'), ('Natalie Portman', 'Jerusalem', '1981'), 718 | ('Rachel Weiss', 'London', '1971'), ('Scarlett Johansson', 'New York', '1984')] 719 | 720 | 721 | class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin): 722 | 723 | def __init__(self, parent, *args, **kw): 724 | wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT) 725 | wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self) 726 | 727 | 728 | class Example(wx.Frame): 729 | 730 | def __init__(self, *args, **kw): 731 | super(Example, self).__init__(*args, **kw) 732 | 733 | self.InitUI() 734 | 735 | def InitUI(self): 736 | 737 | hbox = wx.BoxSizer(wx.HORIZONTAL) 738 | 739 | panel = wx.Panel(self) 740 | 741 | self.list = AutoWidthListCtrl(panel) 742 | self.list.InsertColumn(0, 'name', width=140) 743 | self.list.InsertColumn(1, 'place', width=130) 744 | self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) 745 | 746 | idx = 0 747 | 748 | for i in data: 749 | 750 | index = self.list.InsertItem(idx, i[0]) 751 | self.list.SetItem(index, 1, i[1]) 752 | self.list.SetItem(index, 2, i[2]) 753 | idx += 1 754 | 755 | hbox.Add(self.list, 1, wx.EXPAND) 756 | panel.SetSizer(hbox) 757 | 758 | self.SetTitle('Actresses') 759 | self.Centre() 760 | 761 | 762 | def main(): 763 | 764 | app = wx.App() 765 | ex = Example(None) 766 | ex.Show() 767 | app.MainLoop() 768 | 769 | 770 | if __name__ == '__main__': 771 | main() 772 | ``` 773 | 774 | 我们稍微改变了点前面的例子。 775 | 776 | ```python 777 | import wx.lib.mixins.listctrl 778 | ``` 779 | 780 | 这里我们导入 mixin 模块。 781 | 782 | ```python 783 | class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin): 784 | 785 | def __init__(self, parent, *args, **kw): 786 | wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT) 787 | wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self) 788 | ``` 789 | 790 | 我们创建一个新的 `AutoWidthListCtrl` 类。 这个类继承自 `wx.ListCtrl` 和 `wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin`。 这称为多重继承。最后一列将自动调整占据到 `wx.ListCtrl` 的剩余宽度。 791 | 792 | ### wx.lib.mixins.listctrl.ColumnSorterMixin 793 | 794 | 以下示例创建可排序的列。如果我们单击列标题,则列中的相应行将被排序。 795 | 796 | **sorted.py** 797 | 798 | ```python 799 | #!/usr/bin/env python3 800 | # -*- coding: utf-8 -*- 801 | 802 | """ 803 | ZetCode wxPython tutorial 804 | 805 | In this example, we create sortable columns with 806 | wx.lib.mixins.listctrl.ColumnSorterMixin 807 | 808 | author: Jan Bodnar 809 | website: www.zetcode.com 810 | last modified: May 2018 811 | """ 812 | 813 | import wx 814 | import wx.lib.mixins.listctrl 815 | 816 | actresses = { 817 | 1 : ('Jessica Alba', 'Pomona', '1981'), 818 | 2 : ('Sigourney Weaver', 'New York', '1949'), 819 | 3 : ('Angelina Jolie', 'Los Angeles', '1975'), 820 | 4 : ('Natalie Portman', 'Jerusalem', '1981'), 821 | 5 : ('Rachel Weiss', 'London', '1971'), 822 | 6 : ('Scarlett Johansson', 'New York', '1984') 823 | } 824 | 825 | 826 | class SortedListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ColumnSorterMixin): 827 | 828 | def __init__(self, parent): 829 | 830 | wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT) 831 | wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(actresses)) 832 | self.itemDataMap = actresses 833 | 834 | def GetListCtrl(self): 835 | return self 836 | 837 | 838 | class Example(wx.Frame): 839 | 840 | def __init__(self, *args, **kw): 841 | super(Example, self).__init__(*args, **kw) 842 | 843 | self.InitUI() 844 | 845 | def InitUI(self): 846 | 847 | hbox = wx.BoxSizer(wx.HORIZONTAL) 848 | panel = wx.Panel(self) 849 | 850 | self.list = SortedListCtrl(panel) 851 | self.list.InsertColumn(0, 'name', width=140) 852 | self.list.InsertColumn(1, 'place', width=130) 853 | self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90) 854 | 855 | items = actresses.items() 856 | 857 | idx = 0 858 | 859 | for key, data in items: 860 | 861 | index = self.list.InsertItem(idx, data[0]) 862 | self.list.SetItem(index, 1, data[1]) 863 | self.list.SetItem(index, 2, data[2]) 864 | self.list.SetItemData(index, key) 865 | idx += 1 866 | 867 | hbox.Add(self.list, 1, wx.EXPAND) 868 | panel.SetSizer(hbox) 869 | 870 | self.SetTitle('Actresses') 871 | self.Centre() 872 | 873 | 874 | def main(): 875 | 876 | app = wx.App() 877 | ex = Example(None) 878 | ex.Show() 879 | app.MainLoop() 880 | 881 | 882 | if __name__ == '__main__': 883 | main() 884 | ``` 885 | 886 | 我们将再次使用女演员的例子。 887 | 888 | ```python 889 | wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(actresses)) 890 | ``` 891 | 892 | `wx.lib.mixins.listctrl.ColumnSorterMixin` 接受一个参数:要排序的列数。 893 | 894 | ```python 895 | self.itemDataMap = actresses 896 | ``` 897 | 898 | 我们必须将我们要显示在列表控件中的数据映射到 `itemDataMap` 属性。 数据必须是字典数据类型。 899 | 900 | ```python 901 | def GetListCtrl(self): 902 | return self 903 | ``` 904 | 905 | 我们必须创建一个 `GetListCtrl()` 方法。 此方法返回要排序的 `wx.ListCtrl` 组件。 906 | 907 | ```python 908 | self.list.SetItemData(index, key) 909 | ``` 910 | 911 | 我们必须将每一行与一个特定的索引结合起来。 这是用 `SetItemData` 方法完成的。 912 | 913 | ## wx.lib.mixins.listctrl.CheckListCtrl 914 | 915 | 复选框 check box 可以放在列表控件中。在 wxPython 中,我们可以使用 `wx.lib.mixins.listctrl.CheckListCtrl`。 916 | 917 | **repository.py** 918 | 919 | ```python 920 | #!/usr/bin/env python3 921 | # -*- coding: utf-8 -*- 922 | 923 | """ 924 | ZetCode wxPython tutorial 925 | 926 | In this example, we create a check list control widget. 927 | 928 | author: Jan Bodnar 929 | website: www.zetcode.com 930 | last modified: May 2018 931 | """ 932 | 933 | import wx 934 | from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin 935 | 936 | packages = [('abiword', '5.8M', 'base'), ('adie', '145k', 'base'), 937 | ('airsnort', '71k', 'base'), ('ara', '717k', 'base'), ('arc', '139k', 'base'), 938 | ('asc', '5.8M', 'base'), ('ascii', '74k', 'base'), ('ash', '74k', 'base')] 939 | 940 | class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin): 941 | 942 | def __init__(self, parent): 943 | wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT | 944 | wx.SUNKEN_BORDER) 945 | CheckListCtrlMixin.__init__(self) 946 | ListCtrlAutoWidthMixin.__init__(self) 947 | 948 | 949 | class Example(wx.Frame): 950 | 951 | def __init__(self, *args, **kw): 952 | super(Example, self).__init__(*args, **kw) 953 | 954 | panel = wx.Panel(self) 955 | 956 | vbox = wx.BoxSizer(wx.VERTICAL) 957 | hbox = wx.BoxSizer(wx.HORIZONTAL) 958 | 959 | leftPanel = wx.Panel(panel) 960 | rightPanel = wx.Panel(panel) 961 | 962 | self.log = wx.TextCtrl(rightPanel, style=wx.TE_MULTILINE|wx.TE_READONLY) 963 | self.list = CheckListCtrl(rightPanel) 964 | self.list.InsertColumn(0, 'Package', width=140) 965 | self.list.InsertColumn(1, 'Size') 966 | self.list.InsertColumn(2, 'Repository') 967 | 968 | idx = 0 969 | 970 | for i in packages: 971 | 972 | index = self.list.InsertItem(idx, i[0]) 973 | self.list.SetItem(index, 1, i[1]) 974 | self.list.SetItem(index, 2, i[2]) 975 | idx += 1 976 | 977 | vbox2 = wx.BoxSizer(wx.VERTICAL) 978 | 979 | selBtn = wx.Button(leftPanel, label='Select All') 980 | desBtn = wx.Button(leftPanel, label='Deselect All') 981 | appBtn = wx.Button(leftPanel, label='Apply') 982 | 983 | self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=selBtn.GetId()) 984 | self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=desBtn.GetId()) 985 | self.Bind(wx.EVT_BUTTON, self.OnApply, id=appBtn.GetId()) 986 | 987 | vbox2.Add(selBtn, 0, wx.TOP|wx.BOTTOM, 5) 988 | vbox2.Add(desBtn, 0, wx.BOTTOM, 5) 989 | vbox2.Add(appBtn) 990 | 991 | leftPanel.SetSizer(vbox2) 992 | 993 | vbox.Add(self.list, 4, wx.EXPAND | wx.TOP, 3) 994 | vbox.Add((-1, 10)) 995 | vbox.Add(self.log, 1, wx.EXPAND) 996 | vbox.Add((-1, 10)) 997 | 998 | rightPanel.SetSizer(vbox) 999 | 1000 | hbox.Add(leftPanel, 0, wx.EXPAND | wx.RIGHT, 5) 1001 | hbox.Add(rightPanel, 1, wx.EXPAND) 1002 | hbox.Add((3, -1)) 1003 | 1004 | panel.SetSizer(hbox) 1005 | 1006 | self.SetTitle('Repository') 1007 | self.Centre() 1008 | 1009 | def OnSelectAll(self, event): 1010 | 1011 | num = self.list.GetItemCount() 1012 | for i in range(num): 1013 | self.list.CheckItem(i) 1014 | 1015 | def OnDeselectAll(self, event): 1016 | 1017 | num = self.list.GetItemCount() 1018 | for i in range(num): 1019 | self.list.CheckItem(i, False) 1020 | 1021 | def OnApply(self, event): 1022 | 1023 | num = self.list.GetItemCount() 1024 | 1025 | for i in range(num): 1026 | 1027 | if i == 0: self.log.Clear() 1028 | 1029 | if self.list.IsChecked(i): 1030 | self.log.AppendText(self.list.GetItemText(i) + '\n') 1031 | 1032 | 1033 | def main(): 1034 | 1035 | app = wx.App() 1036 | ex = Example(None) 1037 | ex.Show() 1038 | app.MainLoop() 1039 | 1040 | 1041 | if __name__ == '__main__': 1042 | main() 1043 | ``` 1044 | 1045 | 该示例使用 `wx.lib.mixins.listctrl.CheckListCtrl` 创建一个UI 仓库 repository。 1046 | 1047 | ```python 1048 | class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin): 1049 | 1050 | def __init__(self, parent): 1051 | wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT | 1052 | wx.SUNKEN_BORDER) 1053 | CheckListCtrlMixin.__init__(self) 1054 | ListCtrlAutoWidthMixin.__init__(self) 1055 | ``` 1056 | 1057 | 我们从三个不同的类继承。 1058 | 1059 | ```python 1060 | def OnSelectAll(self, event): 1061 | 1062 | num = self.list.GetItemCount() 1063 | 1064 | for i in range(num): 1065 | self.list.CheckItem(i) 1066 | ``` 1067 | 1068 | `OnSelectAll()` 方法选中所有复选框。 `GetItemCount()` 确定项目的数量,`CheckItem()` 方法勾选当前的复选框。 1069 | 1070 | ![img](assets/repository.png) 1071 | 1072 | 在 wxPython 教程的这一部分,我们介绍了几个高级小部件,包括 `wx.ListBox` , `wx.html.HtmlWindow` 和 `wx.ListCtrl` 。 -------------------------------------------------------------------------------- /9.拖放.md: -------------------------------------------------------------------------------- 1 | # Drag and drop in wxPython 2 | 3 | 在计算机图形用户界面中,拖放是单击虚拟对象并将其拖动到不同位置或其他虚拟对象的动作(或支持动作)。 一般来说,它可以用来执行多种动作,或者在两个抽象对象之间创建各种类型的关联。 4 | 5 | 拖放操作使您能够直观地完成复杂的事情。 6 | 7 | 在拖放操作中,我们将一些数据从数据源拖动到数据目标。 所以我们必须有: 8 | 9 | - Some data 10 | - A data source 11 | - A data target 12 | 13 | 在wxPython中,我们有两个预定义的数据目标: `wx.TextDropTarget` and `wx.FileDropTarget`. 14 | 15 | ## wx.TextDropTarget 16 | 17 | `wx.TextDropTarget` 是处理文本数据的预定义放置目标。 18 | 19 | **dragdrop_text.py** 20 | 21 | ```python 22 | #!/usr/bin/env python3 23 | # -*- coding: utf-8 -*- 24 | 25 | """ 26 | ZetCode wxPython tutorial 27 | 28 | In this example, we drag and drop text data. 29 | 30 | author: Jan Bodnar 31 | website: www.zetcode.com 32 | last modified: May 2018 33 | """ 34 | 35 | from pathlib import Path 36 | import os 37 | import wx 38 | 39 | class MyTextDropTarget(wx.TextDropTarget): 40 | 41 | def __init__(self, object): 42 | 43 | wx.TextDropTarget.__init__(self) 44 | self.object = object 45 | 46 | def OnDropText(self, x, y, data): 47 | 48 | self.object.InsertItem(0, data) 49 | return True 50 | 51 | 52 | class Example(wx.Frame): 53 | 54 | def __init__(self, *args, **kw): 55 | super(Example, self).__init__(*args, **kw) 56 | 57 | self.InitUI() 58 | 59 | def InitUI(self): 60 | 61 | splitter1 = wx.SplitterWindow(self, style=wx.SP_3D) 62 | splitter2 = wx.SplitterWindow(splitter1, style=wx.SP_3D) 63 | 64 | home_dir = str(Path.home()) 65 | 66 | self.dirWid = wx.GenericDirCtrl(splitter1, dir=home_dir, 67 | style=wx.DIRCTRL_DIR_ONLY) 68 | 69 | self.lc1 = wx.ListCtrl(splitter2, style=wx.LC_LIST) 70 | self.lc2 = wx.ListCtrl(splitter2, style=wx.LC_LIST) 71 | 72 | dt = MyTextDropTarget(self.lc2) 73 | self.lc2.SetDropTarget(dt) 74 | 75 | self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId()) 76 | 77 | tree = self.dirWid.GetTreeCtrl() 78 | 79 | splitter2.SplitHorizontally(self.lc1, self.lc2, 150) 80 | splitter1.SplitVertically(self.dirWid, splitter2, 200) 81 | 82 | self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelect, id=tree.GetId()) 83 | 84 | self.OnSelect(0) 85 | 86 | self.SetTitle('Drag and drop text') 87 | self.Centre() 88 | 89 | def OnSelect(self, event): 90 | 91 | list = os.listdir(self.dirWid.GetPath()) 92 | 93 | self.lc1.ClearAll() 94 | self.lc2.ClearAll() 95 | 96 | for i in range(len(list)): 97 | 98 | if list[i][0] != '.': 99 | self.lc1.InsertItem(0, list[i]) 100 | 101 | def OnDragInit(self, event): 102 | 103 | text = self.lc1.GetItemText(event.GetIndex()) 104 | tdo = wx.TextDataObject(text) 105 | tds = wx.DropSource(self.lc1) 106 | 107 | tds.SetData(tdo) 108 | tds.DoDragDrop(True) 109 | 110 | 111 | def main(): 112 | 113 | app = wx.App() 114 | ex = Example(None) 115 | ex.Show() 116 | app.MainLoop() 117 | 118 | 119 | if __name__ == '__main__': 120 | main() 121 | ``` 122 | 123 | 在这个例子中,我们在 `wx.GenericDirCtrl` 中显示一个文件系统。所选目录的内容显示在右上方的列表控件中。 文件名可以拖放到右下角的列表控件中。 124 | 125 | ```python 126 | def OnDropText(self, x, y, data): 127 | 128 | self.object.InsertItem(0, data) 129 | return True 130 | ``` 131 | 132 | 当我们将文本数据拖放到目标上时,将使用 `InsertItem()` 方法将数据插入到列表控件中。 133 | 134 | ```python 135 | dt = MyTextDropTarget(self.lc2) 136 | self.lc2.SetDropTarget(dt) 137 | ``` 138 | 139 | 放置目标被创建。 我们使用 `SetDropTarget()` 方法将放置目标设置为第二个列表控件。 140 | 141 | ```python 142 | self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId()) 143 | ``` 144 | 145 | 当拖动操作开始时,调用 `OnDragInit()` 方法。 146 | 147 | ```python 148 | def OnDragInit(self, event): 149 | 150 | text = self.lc1.GetItemText(event.GetIndex()) 151 | tdo = wx.TextDataObject(text) 152 | tds = wx.DropSource(self.lc1) 153 | ... 154 | ``` 155 | 156 | 在 `OnDragInit()` 方法中,我们创建了一个 `wx.TextDataObject`,它包含我们的文本数据。 从第一个列表控件创建放置源 drop source。 157 | 158 | ```python 159 | tds.SetData(tdo) 160 | tds.DoDragDrop(True) 161 | ``` 162 | 163 | 我们使用 `SetData()` 将数据设置到放置源,并使用 `DoDragDrop()` 启动拖放操作。 164 | 165 | ## wx.FileDropTarget 166 | 167 | `wx.FileDropTarget` 是一个放置目标,它接受从文件管理器拖动的文件。 168 | 169 | **dragdrop_file.py** 170 | 171 | ```python 172 | #!/usr/bin/env python3 173 | # -*- coding: utf-8 -*- 174 | 175 | """ 176 | ZetCode wxPython tutorial 177 | 178 | In this example, we drag and drop files. 179 | 180 | author: Jan Bodnar 181 | website: www.zetcode.com 182 | last modified: May 2018 183 | """ 184 | 185 | import wx 186 | 187 | class FileDrop(wx.FileDropTarget): 188 | 189 | def __init__(self, window): 190 | 191 | wx.FileDropTarget.__init__(self) 192 | self.window = window 193 | 194 | def OnDropFiles(self, x, y, filenames): 195 | 196 | for name in filenames: 197 | 198 | try: 199 | file = open(name, 'r') 200 | text = file.read() 201 | self.window.WriteText(text) 202 | 203 | except IOError as error: 204 | 205 | msg = "Error opening file\n {}".format(str(error)) 206 | dlg = wx.MessageDialog(None, msg) 207 | dlg.ShowModal() 208 | 209 | return False 210 | 211 | except UnicodeDecodeError as error: 212 | 213 | msg = "Cannot open non ascii files\n {}".format(str(error)) 214 | dlg = wx.MessageDialog(None, msg) 215 | dlg.ShowModal() 216 | 217 | return False 218 | 219 | finally: 220 | 221 | file.close() 222 | 223 | return True 224 | 225 | class Example(wx.Frame): 226 | 227 | def __init__(self, *args, **kw): 228 | super(Example, self).__init__(*args, **kw) 229 | 230 | self.InitUI() 231 | 232 | def InitUI(self): 233 | 234 | self.text = wx.TextCtrl(self, style = wx.TE_MULTILINE) 235 | dt = FileDrop(self.text) 236 | 237 | self.text.SetDropTarget(dt) 238 | 239 | self.SetTitle('File drag and drop') 240 | self.Centre() 241 | 242 | 243 | def main(): 244 | 245 | app = wx.App() 246 | ex = Example(None) 247 | ex.Show() 248 | app.MainLoop() 249 | 250 | 251 | if __name__ == '__main__': 252 | main() 253 | ``` 254 | 255 | 该示例创建一个简单的 `wx.TextCtrl`。 我们可以将文本文件从文件管理器拖到控件中。 256 | 257 | ```python 258 | def OnDropFiles(self, x, y, filenames): 259 | 260 | for name in filenames: 261 | ... 262 | ``` 263 | 264 | 我们可以一次拖放多个文件。 265 | 266 | ```python 267 | try: 268 | file = open(name, 'r') 269 | text = file.read() 270 | self.window.WriteText(text) 271 | ``` 272 | 273 | 我们以只读模式打开文件,获取其内容并将内容写入文本控制窗口。 274 | 275 | ```python 276 | except IOError as error: 277 | 278 | msg = "Error opening file\n {}".format(str(error)) 279 | dlg = wx.MessageDialog(None, msg) 280 | dlg.ShowModal() 281 | 282 | return False 283 | ``` 284 | 285 | 如果出现输入/输出错误,我们将显示消息对话框并终止操作。 286 | 287 | ```python 288 | self.text = wx.TextCtrl(self, style = wx.TE_MULTILINE) 289 | dt = FileDrop(self.text) 290 | 291 | self.text.SetDropTarget(dt) 292 | ``` 293 | 294 | `wx.TextCtrl` 是放置目标。 295 | 296 | 在本章中,我们使用 wxPython 进行了拖放操作。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wxPython 中文简明教程 2 | 3 | 翻译自 [http://zetcode.com/wxpython/](http://zetcode.com/wxpython/) 4 | 5 | 代码:[https://github.com/janbodnar/wxPython-examples](https://github.com/janbodnar/wxPython-examples) 6 | 7 | 为了方便学习 Python GUI,本人翻译了网上最简明易懂的 wxPython 教程。当然本人能力有限,如有翻译错误,请及时向我反馈。转载请注明出处! 8 | -------------------------------------------------------------------------------- /assets/1527417412183.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527417412183.png -------------------------------------------------------------------------------- /assets/1527417584346.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527417584346.png -------------------------------------------------------------------------------- /assets/1527417657992.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527417657992.png -------------------------------------------------------------------------------- /assets/1527561443370.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527561443370.png -------------------------------------------------------------------------------- /assets/1527561475646.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527561475646.png -------------------------------------------------------------------------------- /assets/1527561515784.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527561515784.png -------------------------------------------------------------------------------- /assets/1527561810112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/1527561810112.png -------------------------------------------------------------------------------- /assets/absolute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/absolute.png -------------------------------------------------------------------------------- /assets/aline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/aline.png -------------------------------------------------------------------------------- /assets/bars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/bars.jpg -------------------------------------------------------------------------------- /assets/base.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/base.jpg -------------------------------------------------------------------------------- /assets/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/border.png -------------------------------------------------------------------------------- /assets/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/browser.png -------------------------------------------------------------------------------- /assets/brushes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/brushes.png -------------------------------------------------------------------------------- /assets/burning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/burning.png -------------------------------------------------------------------------------- /assets/buttonwid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/buttonwid.png -------------------------------------------------------------------------------- /assets/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/calculator.png -------------------------------------------------------------------------------- /assets/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/checkbox.png -------------------------------------------------------------------------------- /assets/checkmenuitem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/checkmenuitem.png -------------------------------------------------------------------------------- /assets/colours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/colours.png -------------------------------------------------------------------------------- /assets/combobox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/combobox.png -------------------------------------------------------------------------------- /assets/containers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/containers.jpg -------------------------------------------------------------------------------- /assets/contextmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/contextmenu.png -------------------------------------------------------------------------------- /assets/coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/coordinates.png -------------------------------------------------------------------------------- /assets/cpuwidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/cpuwidget.png -------------------------------------------------------------------------------- /assets/customdialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/customdialog.png -------------------------------------------------------------------------------- /assets/custompatterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/custompatterns.png -------------------------------------------------------------------------------- /assets/dynamic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/dynamic.jpg -------------------------------------------------------------------------------- /assets/filehunter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/filehunter.png -------------------------------------------------------------------------------- /assets/focusevent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/focusevent.png -------------------------------------------------------------------------------- /assets/gauge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/gauge.png -------------------------------------------------------------------------------- /assets/gdi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/gdi2.png -------------------------------------------------------------------------------- /assets/gotoclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/gotoclass.png -------------------------------------------------------------------------------- /assets/gradients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/gradients.png -------------------------------------------------------------------------------- /assets/helpwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/helpwindow.png -------------------------------------------------------------------------------- /assets/htmlwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/htmlwin.png -------------------------------------------------------------------------------- /assets/hunter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/hunter.png -------------------------------------------------------------------------------- /assets/hyperlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/hyperlink.png -------------------------------------------------------------------------------- /assets/iconsshortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/iconsshortcuts.png -------------------------------------------------------------------------------- /assets/inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/inheritance.png -------------------------------------------------------------------------------- /assets/joinscaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/joinscaps.png -------------------------------------------------------------------------------- /assets/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/lines.png -------------------------------------------------------------------------------- /assets/listbox2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/listbox2.png -------------------------------------------------------------------------------- /assets/messagebox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/messagebox.png -------------------------------------------------------------------------------- /assets/modules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/modules.jpg -------------------------------------------------------------------------------- /assets/moveevent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/moveevent.png -------------------------------------------------------------------------------- /assets/newclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/newclass.png -------------------------------------------------------------------------------- /assets/pens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/pens.png -------------------------------------------------------------------------------- /assets/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/player.png -------------------------------------------------------------------------------- /assets/points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/points.png -------------------------------------------------------------------------------- /assets/radiobutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/radiobutton.png -------------------------------------------------------------------------------- /assets/region_operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/region_operations.png -------------------------------------------------------------------------------- /assets/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/rename.png -------------------------------------------------------------------------------- /assets/repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/repository.png -------------------------------------------------------------------------------- /assets/review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/review.png -------------------------------------------------------------------------------- /assets/ruler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/ruler.png -------------------------------------------------------------------------------- /assets/shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/shapes.png -------------------------------------------------------------------------------- /assets/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/simple.png -------------------------------------------------------------------------------- /assets/simplemenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/simplemenu.png -------------------------------------------------------------------------------- /assets/simpletoolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/simpletoolbar.png -------------------------------------------------------------------------------- /assets/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/slider.png -------------------------------------------------------------------------------- /assets/spinctrl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/spinctrl.png -------------------------------------------------------------------------------- /assets/spreadsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/spreadsheet.png -------------------------------------------------------------------------------- /assets/standardidentifiers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/standardidentifiers.png -------------------------------------------------------------------------------- /assets/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/star.png -------------------------------------------------------------------------------- /assets/staticbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/staticbox.png -------------------------------------------------------------------------------- /assets/staticline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/staticline.png -------------------------------------------------------------------------------- /assets/statictext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/statictext.png -------------------------------------------------------------------------------- /assets/staticwidgets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/staticwidgets.jpg -------------------------------------------------------------------------------- /assets/statusbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/statusbar.png -------------------------------------------------------------------------------- /assets/submenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/submenu.png -------------------------------------------------------------------------------- /assets/tetris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/tetris.png -------------------------------------------------------------------------------- /assets/tetrominoes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/tetrominoes.png -------------------------------------------------------------------------------- /assets/togglebuttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/togglebuttons.png -------------------------------------------------------------------------------- /assets/toolbars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/toolbars.png -------------------------------------------------------------------------------- /assets/toplevel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/toplevel.jpg -------------------------------------------------------------------------------- /assets/undoredo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necan/wxPython-tutorial/db35d888e05ec9e9cbad68a50f6c5907f20f55aa/assets/undoredo.png --------------------------------------------------------------------------------