打造自己的天气预报之(三)——给程序加个图形用户界面(GUI)

经过前期的设计,我们已经可以从网上获取天气信息,并且可以使用邮件发送。今天我们要给程序增加一个界面,毕竟并不是所有人都习惯命令行操作。先给大家看一下最终效果

打造自己的天气预报之(三)——给程序加个图形用户界面(GUI)
这是在Win7下的运行效果。我使用wxPython来实现GUI。wxPython是wxWidgets的Python实现,而wxWidgets是一个开源的跨平台的C++构架库(framework),它可以提供GUI(图形用户界面)和其它工具。

开发任一wxPython程序必须包括五个基本步骤:

1、导入必须的wxPython包

2、子类化wxPyhon应用程序类

3、定义一个应用程序的初始化方法

4、创建一个应用程序类的实例

5、进入这个应用程序的主事件循环

下面我们一步一步地来实现这个界面。

首先,我们需要导入必须的wxPython包。

import wx
 一旦这个包被导入,你就可以引用wxPython的类、函数和常量(它们以wx为前缀,如wx.Frame)。当然也可以使用from wx import *来导入具体用到的类、函数或者变量,这样使用时就不必加上wx前缀,但是这样可能导致命名空间冲突,所以不建议这样做。

通常情况下,Python中的模块导入顺序无关紧要,但是wxPython中的不同,它是一个复杂的模块,当你第一次导入wx模块时,wxPython需要对别的wxPython模块执行一些初始化工作。所以我们必须将import wx作为第一条导入名句,然后再导入其他的Python模块,其他的Python模块导入顺序可以随意。

接下来,我们要使用应用程序和框架工作。每个wxPython程序必须有一个application对象和至少一个frame对象。Application对象必须是wx.App或其子类的一个实例。

要子类化wxPython的应用程序类,也就是wx.App,代码如下:

class MyApp(wx.App):
    """应用程序类wx.App的子类"""
    def __init__(self, title, sText):    #重构构造方法,其中title、sText分别是传递给主窗口的标题参数和状态栏参数
        wx.App.__init__(self)   #调用wx.App的构造方法
        frame = MyFrame(title, sText)   #创建主窗口对象
        frame.Show(True)    #显示主窗口
 

上面的代码定义了一个名为MyApp的子类,__init__方法定义了应用程序的初始化方法,在该方法中我们调用了其父方法wx.App.__init__,然后调用MyFrame方法(后面会实现)创建了一个窗口,再调用Show方法使窗口可见。

为了使应用程序运行,我们需要创建一个应用程序实例并进入它的主事件循环,代码如下:

app = MyApp(wTitle, wTime)
app.MainLoop()
至此,上述五个基本步骤我们都已经实现了,接下来我们简单说明一下如何实现文章开头的图形用户界面,也就是实现MyFrame类。

MyFrame类是wx.Frame类的子类,创建MyFrame类时需要调用其父类的构造器wx.Frame.__init__(),wx.Frame的构造器所要求的参数如下:

wx.Frame(parent, id=-1, title=””, pos=wx.DefaultPosition,
        size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
        name=”frame”)

我们在别的窗口部件的构造器中将会看到类似的参数。参数的说明如下:

parent:框架的父窗口。对于顶级窗口,这个值是None。框架随其父窗口的销毁而销毁。取决于平台,框架可被限制只出现在父窗口的顶部。在多文档界面的情况下,子窗口被限制为只能在父窗口中移动和缩放。
id:关于新窗口的wxPython ID号。你可以明确地传递一个。或传递-1,这将导致wxPython自动生成一个新的ID。
title:窗口的标题。
pos:一个wx.Point对象,它指定这个新窗口的左上角在屏幕中的位置。在图形用户界面程序中,通常(0,0)是显示器的左上角。这个默认的(-1,-1)将让系统决定窗口的位置。
size:一个wx.Size对象,它指定这个窗口的初始尺寸。这个默认的(-1,-1)将让系统决定窗口的初始尺寸。
style:指定窗口的类型的常量。你可以使用或运算来组合它们。
name:框架的内在的名字。以后你可以使用它来寻找这个窗口。

除于parent参数外,其他参数都是可选的。

def __init__(self, title, sText):
        wx.Frame.__init__(self, None, -1, title=title,
                                      style=wx.DEFAULT_FRAME_STYLE ^
                                      (wx.MAXIMIZE_BOX | wx.RESIZE_BORDER))
我们这里所用的style是默认类型但是没有最大化按钮和可缩放的边框。

这个窗口创建后内容是空的,我们需要在框架中插入各种对象和控件。首先增加一个wx.Panel的实例,它是其他控件的容器。

self.panel = wx.Panel(self)

我们还需要一个状态栏来显示天气发布的时间

stBar = self.CreateStatusBar()

接下来创建窗口中的各种控件。我们的程序一共需要三种控件:wx.StaticBitmap、wx.StaticText和wx.Button分别表示静态图片控件、静态文本控件以及按钮控件。这三种控件的构造方法都比较类似:

wx.StaticBitmap(parent, id=-1, bitmap=wxNullBitmap, 
                          pos=DefaultPosition,size=DefaultSize, 
                          style=0, name=StaticBitmapNameStr)
wx.StaticText(parent, id, label, pos=wx.DefaultPosition,
                          size=wx.DefaultSize, style=0, name=”staticText”)
wx.Button(parent, id, label, pos, size=wxDefaultSize, style=0,
    			  validator=DefaultValidator, name=”button”)

其中,wx.StaticBitmap构造器中的bitmap参数是一个Bitmap对象,我们可以先从本地图片文件创建图像对象,然后再转换成Bitmap对象,再生成wx.StaticBitmap控件。

创建各种控件比较简单,但是我们不能直接把创建好的控件放置在窗口中,这样的话界面比较丑陋,因此我们需要使用布局管理器——sizer来管理我们的布局。sizer是用于自动布局一组窗口部件的算法。sizer被附加到一个容器,通常是一个框架或面板。在父容器中创建的子窗口部件必须被分别地添加到sizer。当sizer被附加到容器时,它随后就管理它所包含的孩子的布局。

使用sizer的好处是很多的。当子窗口部件的容器的尺寸改变时,sizer将自动计算它的孩子的布局并作出相应的调整。同样,如果其中的一个孩子改变了尺寸,那么sizer能够自动地刷新布局。此外,当你想要改变布局时,sizer管理起来很容易。最大的弊端是sizer的布局有一些局限性。但是,最灵活sizer——grid bag和box,能够做你要它们做的几乎任何事。

Sizer之间也可以互相嵌套,本例一共使用了3种Sizer:GridbagSizer、BoxSizer、StaticBoxSizer。整个窗口使用BoxSizer将内容分为上下两部分;上半部分使用BoxSizer再分为左右两部分,左半部分使用GridBagSizer来布局天气信息,再嵌入到一个StaticBoxSizer中,右半部分使用StaticBoxSizer放置了三个按钮;下半部分使用GridBagSizer来布局每一天的天气信息,再使用StaticBoxSizer来布局未来4天的天气信息。

完整程序如下所示,关键地方我都有注释。

#! /usr/bin/env python
#coding=utf-8
import wx
from GetWeather import WeatherInfo

#todayInfo=u"12月8日星期四 小雨转多云7℃/-1℃ 北风5-6级 img/07.gif img/01.gif".split()
#daysInfo=[todayInfo,todayInfo,todayInfo,todayInfo]
class MyApp(wx.App):
    """应用程序类wx.App的子类"""
    def __init__(self, title, sText):    #重构构造方法,其中title、sText分别是传递给主窗口的标题参数和状态栏参数
        wx.App.__init__(self)   #调用wx.App的构造方法
        frame = MyFrame(title, sText)   #创建主窗口对象
        frame.Show(True)    #显示主窗口
        
class MyFrame(wx.Frame):
    def __init__(self, title, sText):
        wx.Frame.__init__(self, None, -1, title=title, style=wx.DEFAULT_FRAME_STYLE ^ (wx.MAXIMIZE_BOX | wx.RESIZE_BORDER))
        self.panel = wx.Panel(self)
        stBar = self.CreateStatusBar()  #创建状态栏
        stBar.SetStatusText(sText)  #状态栏显示发布时间
        
        hdLeft = self.LayoutHeadLeft(todayInfo)
        hdRight = self.LayoutHeadRight()
        head = self.LayoutHead(hdLeft, hdRight)
        days = self.LayoutDaysWeather(daysInfo)
        sizer = self.LayoutPanel(head, days)
        
        self.panel.SetSizer(sizer)
        sizer.Fit(self)
        sizer.SetSizeHints(self)
        
    def LayoutHeadLeft(self, todayInfo):
        """窗口上半部分左侧布局,主要是今日天气信息。
        todayInfo=[日期,图片1,图片2,天气,风力]"""
        
        #一个静态文本控件
        tLbl = wx.StaticText(self.panel, -1, label=todayInfo[3])
        
        #从文件载入图像
        img1 = wx.Image("img/a_" + todayInfo[1], wx.BITMAP_TYPE_GIF)
        img2 = wx.Image("img/a_" + todayInfo[2], wx.BITMAP_TYPE_GIF)
        
        #转换为静态图像控件
        sb1 = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(img1))
        sb2 = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(img2))
        
        #使用GridBagSizer来放置两个图片和两个文本控件
        GBSizer = wx.GridBagSizer(vgap=5, hgap=5)   #Grid之间水平、竖直方向间距都是5个像素
        GBSizer.Add(sb1, pos=(0, 0), span=(1, 1), flag=0)   #天气图片1,放置在第1行第1列,占一行一列
        GBSizer.Add(sb2, pos=(0, 1), span=(1, 1), flag=0)   #天气图片2,放置在第1行第2列,占一行一列
        GBSizer.Add(tLbl, pos=(1, 0), span=(1, 2), flag=0)  #天气文字信息,放置在第2行第1列,占一行两列
                
        #再使用Static Box Sizer来放置上面的GridBagSizer
        sbox = wx.StaticBox(self.panel, -1, todayInfo[0])        
        sbsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
        sbsizer.Add(GBSizer, 0, wx.ALL, 2)
        
        return sbsizer
    
    def LayoutHeadRight(self):
        """放置3个按钮"""
        
        #三个按钮控件
        updateBtn = wx.Button(self.panel, -1, label=u"更新")
        setupBtn = wx.Button(self.panel, -1, label=u"设置")
        sendBtn = wx.Button(self.panel, -1, label=u"发送")
        
        #使用Static Box Sizer来放置上面的GridBagSizer
        sbox = wx.StaticBox(self.panel, -1, u"个性化")        
        sbsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
        sbsizer.Add(updateBtn, 0, wx.ALL, 2)
        sbsizer.Add(setupBtn, 0, wx.ALL, 2)
        sbsizer.Add(sendBtn, 0, wx.ALL, 2)
                
        return sbsizer
    
    def LayoutHead(self, headLeft, headRight):
        
        box = wx.BoxSizer(wx.HORIZONTAL)    #使用水平BoxSizer放置今日天气信息和3个按钮
        box.Add(headLeft, 1, flag=wx.EXPAND)
        box.Add(headRight, flag=wx.EXPAND)
        
        return box
    
    def LayoutDayWeather(self, dayInfo):
        """放置未来4天每天的天气信息"""
        #两个静态文本控件
        dLbl = wx.StaticText(self.panel, -1, label=dayInfo[0])
        tLbl = wx.StaticText(self.panel, -1, label=dayInfo[3])
        #wLbl=wx.StaticText(self.panel,-1,label=todayInfo[2])
                
        #从文件载入图像
        img1 = wx.Image("img/" + dayInfo[1], wx.BITMAP_TYPE_GIF)
        img2 = wx.Image("img/" + dayInfo[2], wx.BITMAP_TYPE_GIF)
                
        #转换为静态图像控件
        sb1 = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(img1))
        sb2 = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(img2))
                
        #使用GridBagSizer来放置两个图片和两个文本控件
        GBSizer = wx.GridBagSizer(vgap=5, hgap=5)
        GBSizer.Add(dLbl, pos=(0, 0), span=(1, 3), flag=0)
        GBSizer.Add(sb1, pos=(1, 0), flag=0)
        GBSizer.Add(sb2, pos=(1, 1), flag=0)
        GBSizer.Add(tLbl, pos=(1, 2), flag=0)
        
        return GBSizer
    
    def LayoutDaysWeather(self, daysInfo):
        """使用竖直方向的StaticBoxSizer放置未来4天的天气信息"""
        sbox = wx.StaticBox(self.panel, -1, u"未来4日天气")
        sbsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
        for dayInfo in daysInfo:
            sz = self.LayoutDayWeather(dayInfo)
            sbsizer.Add(sz, flag=wx.EXPAND)
        return sbsizer
    
    def LayoutPanel(self, head, days):
        """使用竖直方向的BoxSizer放置窗口上半部分内容和下半部分内容"""
        bsizer = wx.BoxSizer(wx.VERTICAL)
        bsizer.Add(head, flag=wx.EXPAND)
        bsizer.Add(days, flag=wx.EXPAND)
        return bsizer
        
if __name__ == "__main__":
    
    #南京天气
    wInfo = WeatherInfo('http://wap.weather.com.cn/wap/weather/101190101.shtml').getWeather()
    wTitle = wInfo[0]   #南京天气预报
    wTime = wInfo[1]    #发布时间
    todayInfo = wInfo[2]    #今日天气信息
    daysInfo = wInfo[3:7]   #未来4天天气信息
    app = MyApp(wTitle, wTime)  #应用程序类的一个实例
    app.MainLoop()  #进入主循环
上述程序实现了图形用户界面,当然目前还没实现3个按钮的功能,这将在下一篇中来实现。为了将天气预报信息传递给程序界面框架,我把 第一篇中抓取天气信息的程序做了一些修改,主要是增加了天气图片信息,以及将抓取到的天气信息存在一个列表里方便调用,大家可以下载附件自行查看。程序中用到的天气图标文件也在附件中。

你可能感兴趣的:(编程,python,天气预报,GUI,wxPython)