欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-09 15:26:31
...

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

打造自己的天气预报之(三)——给程序加个图形用户界面(GUI)
            
    
    博客分类: 天气预报wxPythonGUI编程Python Python编程天气预报GUIwxPython 
这是在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个按钮的功能,这将在下一篇中来实现。为了将天气预报信息传递给程序界面框架,我把第一篇中抓取天气信息的程序做了一些修改,主要是增加了天气图片信息,以及将抓取到的天气信息存在一个列表里方便调用,大家可以下载附件自行查看。程序中用到的天气图标文件也在附件中。
  • 打造自己的天气预报之(三)——给程序加个图形用户界面(GUI)
            
    
    博客分类: 天气预报wxPythonGUI编程Python Python编程天气预报GUIwxPython 
  • 大小: 29.3 KB