wxPython繪圖

      在〈wxPython繪圖〉中尚無留言

簡易繪制方式

先看下面的代碼, 產生一個Panel, 然後要在Panel上畫一條線.
首先, 由Panel取得一個wxClientDC物件, 然後再對這個dc物件進行繪制. 這就好比dc就是Panel上的畫布(Canvas).

繪制的時機, 必需在整個Frame形成ready後才可以開始繪制. 所以如果在 __init__()建構子就開始繪制的話, 是看不到線條的. 因為必需使用wx.CallLater(), 一秒鐘後才由UI主執行緒繪制

請注意, wx.ClientDC不可長時間保存, 必需在繪圖前才產生. 也就是說, dc不能為物件變數. 

import wx
import random
class MainFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.panel=wx.Panel(self)
        self.panel.SetBackgroundColour("#ffff00")
        box=wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(box)
        box.Add(self.panel, 1, wx.EXPAND,1)
        wx.CallLater(1000, self.DrawLine)
    def DrawLine(self):
        x1=random.randint(0,800)
        y1 = random.randint(0, 800)
        x2 = random.randint(0, 800)
        y2 = random.randint(0, 800)
        dc=wx.ClientDC(self.panel) #ClientDC必需在繪圖前才產生, 不可事先產生
        self.dc.DrawLine(x1, y1, x2, y2)
app=wx.App()
frame=MainFrame(None)
frame.Show()
app.MainLoop()

上述使用CallLater事實上並不太正確. 正確使用方式是在Frame ready後, 才開始畫. 當Frame Ready時, 會傳送Paint事件, 所以可以在接收到Paint event後, 開始繪圖, 如下

import wx
import random
class MainFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.panel=wx.Panel(self)
        self.panel.SetBackgroundColour("#ffff00")
        box=wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(box)
        box.Add(self.panel, 1, wx.EXPAND,1)
        self.Bind(wx.EVT_PAINT, self.OnFormLoaded)
    def OnFormLoaded(self, event):
        x1=random.randint(0,800)
        y1 = random.randint(0, 800)
        x2 = random.randint(0, 800)
        y2 = random.randint(0, 800)
        dc=wx.ClientDC(self.panel) 
        dc.SetBrush(wx.Brush(wx.RED))
        dc.DrawLine(x1, y1, x2, y2)
        dc.DrawCircle(100,100,50)
app=wx.App()
frame=MainFrame(None)
frame.Show()
app.MainLoop()

閃爍問題

上面的代碼中, 如果把DrawLine()換成DrawCircle()畫出圓形, 則圓形會有鋸齒的現像, 非常的醜.  另外, 如果換成DrawImage畫圖片時, 則會出現閃爍的情形.

首先由panel產生wxClientDC canvas, 然後開啟一個self.buffer =wx.Bitmap(size) , 此為空白的圖檔緩衝區. 再使用wx.BufferedDC()將canvas與buffer連結. 這樣就可以直接對buffer畫圖了. 但為什麼直接對buffer畫圖就不會閃爍呢? 原因是此時直接針對buffer繪制的圖形, 等到繪制完成後, 把整個buffer覆蓋在canvas上. 因為覆蓋的動作非常快速, 所以就會讓我們感覺不到閃爍的現像.

如同ClientDC, BufferedDC亦不可長時間保存, 必需在需要繪圖時, 才產生. 畫完覆蓋後, 它就會自動被刪除了。

反鋸齒

接下來, 紅色的圓形周圍都有鋸齒的狀況. 這顯的相當醜陃. 還好, 可以使用wx.GCDC()來消除艍齒. 不過請注意, wx.GCDC並不支援直接對wxClientDC 控制, 而是必需對wxBufferedDC控制. 所以就可以使用如下方式產生dc. 再對dc繪制圖形
dc=wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer))

wx.GCDC在清除背景前, 必需先設定Background顏色, 這樣Clear才會有效的運作

import wx
import random
import time, threading
class MainFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent,style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE | wx.TAB_TRAVERSAL)
        self.panel=wx.Panel(self)
        self.panel.SetBackgroundColour("#ffff00")
        box=wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(box)
        box.Add(self.panel, 1, wx.EXPAND,1)
        t=threading.Thread(target=self.DrawThread)
        self.Layout()
        t.start()
        self.Bind(wx.EVT_SIZE, self.Resize)
    def Resize(self, event):
        self.w, self.h=self.GetSize()
        self.buffer = wx.Bitmap(self.w, self.h)
    def DrawThread(self):
        while True:
            wx.CallAfter(self.DrawLine);
            time.sleep(2)
    def DrawLine(self):
        dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer))
        dc.SetBackground(wx.Brush("#ffff00"))
        dc.Clear()
        x1=random.randint(0,self.w)
        y1 = random.randint(0, self.h)
        radius=random.randint(20,100)
        pen=wx.Pen("#ff0000")
        brush=wx.Brush("#ff0000")
        dc.SetPen(pen)
        dc.SetBrush((brush))
        dc.DrawCircle(x1, y1, radius)
app=wx.App()
frame=MainFrame(None)
frame.Show()
app.MainLoop()

python_draw_2

彈跳球

import wx
import random
import time, threading
class MainFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent,style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE | wx.TAB_TRAVERSAL)
        self.panel=wx.Panel(self)
        self.panel.SetBackgroundColour("#ffff00")
        box=wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(box)
        box.Add(self.panel, 1, wx.EXPAND,1)
        self.runFlag=True;
        self.panel.Bind(wx.EVT_SIZE, self.Resize)
        self.Bind(wx.EVT_CLOSE, self.Close)
        self.x=50
        self.y=50;
        self.xspeed=25;
        self.yspeed=17;
        self.xdir=1;
        self.ydir=1;
        self.radius=20;
        t=threading.Thread(target=self.DrawThread)
        self.Layout()
        t.start()
    def Resize(self, event):
        self.w, self.h=self.panel.GetSize()
        self.buffer = wx.Bitmap(self.w, self.h)
    def DrawThread(self):
        while self.runFlag:
            wx.CallAfter(self.DrawLine);
            time.sleep(0.02)
    def DrawLine(self):
        dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self.panel), self.buffer))
        dc.SetBackground(wx.Brush(wx.Colour(0, 0, 0)))
        dc.Clear()

        #以下的寫法, 就不需clear了, 因為重建buffer
        #buffer=wx.Bitmap(self.GetClientSize())
        #dc = wx.GCDC(wx.BufferedDC(wx.ClientDC(self), buffer))

        dc.SetPen(wx.Pen(wx.Colour(255,255,0), 5))
        r = random.randint(0,255)
        g = random.randint(0, 255)
        b = random.randint(0, 255)
        dc.SetBrush(wx.Brush(wx.Colour(r, g, b)))
        self.x+=self.xspeed*self.xdir
        self.y+=self.yspeed*self.ydir
        if self.x>=self.w-self.radius:
            self.x=self.w-self.radius
            self.xdir*=-1
        elif self.x<self.radius:
            self.x=self.radius
            self.xdir*=-1
        if self.y>=self.h-self.radius:
            self.y=self.h-self.radius
            self.ydir*=-1
        elif self.y<self.radius:
            self.y=self.radius
            self.ydir*=-1
        dc.DrawCircle(self.x, self.y, self.radius)
    def Close(self, event):
        self.runFlag=False
        app.ExitMainLoop()
app=wx.App()
frame=MainFrame(None)
frame.Show()
app.MainLoop()

python_ball

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *