我在erlang中使用wxWidget创建了一个小程序,并成功在Windows XP下正常运行。该程序根据内部事件设置位图动画,并使用时基(无用户事件)刷新它。我的问题是代码在Linux Ubuntu(11.04)下无法正常运行。
位图面板初始化如下:
fill_window(W,H,Frame,Z) ->
MainSz = wxBoxSizer:new(?wxVERTICAL),
Board = wxPanel:new(Frame),
wxWindow:setSizer(Board,MainSz),
PanSz = wxStaticBoxSizer:new(?wxVERTICAL, Board,[{label, "Le Monde"}]),
KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Board,[{label, "Controle"}]),
KeyGrSz = wxGridSizer:new(2,3,2,2),
Panel = wxPanel:new(Board, [{size,{W,H}}]),
wxSizer:addSpacer(MainSz,2),
wxSizer:add(PanSz, Panel, [{flag, ?wxALL bor ?wxEXPAND}]),
wxSizer:add(MainSz, PanSz, [{proportion, 0}, {border, 4}, {flag, ?wxALL bor ?wxEXPAND}]),
wxSizer:addSpacer(MainSz,3),
wxPanel:connect(Panel, paint, [callback]),
wxPanel:connect(Panel, left_up, []),
wxPanel:connect(Panel, right_down, []),
wxPanel:connect(Panel, middle_down, []),
wxSizer:addSpacer(MainSz,3),
wxSizer:add(KeySz, KeyGrSz, [{flag, ?wxALL bor ?wxEXPAND}]),
[wxSizer:add(KeyGrSz, wxButton:new(Board,Id,[{label,Txt},{size,{80,20}}]), [{flag, ?wxALL}]) || {Id,Txt} <- ?BLIST],
wxSizer:add(MainSz, KeySz, [{proportion, 0}, {border, 4}, {flag, ?wxALL}]),
wxSizer:addSpacer(MainSz,2),
wxWindow:connect(Board, command_button_clicked),
wxSizer:layout(MainSz),
ClientDC = wxClientDC:new(Panel),
Bitmap = wxBitmap:new(W,H),
PM = wxPen:new(color(dead), [{width, 1}]),
PV = wxPen:new(color(live), [{width, 1}]),
BG = wxBrush:new(color(background)),
BM = wxBrush:new(color(dead)),
BV = wxBrush:new(color(live)),
%% initialisation de l'image de départ
MemoryDC = wxMemoryDC:new(Bitmap),
wxDC:setBackground(MemoryDC,BG),
wxDC:setBrush(MemoryDC,BG),
PenTemp = wxPen:new(color(background), [{width, 1}]),
wxDC:setPen(MemoryDC,PenTemp),
wxDC:drawRectangle(MemoryDC, {0,0}, {W,H}),
wxPen:destroy(PenTemp),
wxDC:setPen(MemoryDC,PM),
wxDC:setBrush(MemoryDC,BM),
[cell(MemoryDC, {X,Y},Z) || X <- lists:seq(0,W-1), Y <- lists:seq(0,H-1)],
redraw(ClientDC,Bitmap,W,H),
wxWindow:refresh(Panel),
wxWindow:show(Frame),
{Frame, Panel, Bitmap, ClientDC, PV, PM, BV, BM}.
color(live) -> {255,255,255};
color(dead) -> {80,80,80};
color(background) -> {0,0,40}.
redraw(DC, Bitmap, W, H) ->
MemoryDC = wxMemoryDC:new(Bitmap),
wxMemoryDC:destroy(MemoryDC).
该进程从其他进程接收一些消息,告诉它绘制一个单元格。该窗口假定根据内部状态的值(refreshtoggle)自动刷新屏幕或将更改存储在列表中。它还会收到一些“刷新”消息,告诉它执行待处理的图像修改列表并刷新屏幕。我已经使用同步事件完成了这个操作,该事件更新了挂起的修改列表,并在需要时通过调用刷新函数来执行它:
handle_call(refresh, _From, #state{cellbuf=Cb,clientDC=ClientDC, bitmap=Bitmap, w=W, h=H,zoom=Z, penlive=PV, pendead=PM, brushlive=BV, brushdead=BM,panel=P} = State) ->
do_refresh(Cb,ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P),
{reply, ok, State#state{cellbuf=[]}};
handle_call({setcell,Etat,Cell}, _From, #state{cellbuf=Cb,clientDC=ClientDC, bitmap=Bitmap, w=W, h=H,zoom=Z, penlive=PV, pendead=PM, brushlive=BV, brushdead=BM,panel=P,refreshtoggle=true} = State) ->
do_refresh([{Etat,Cell}|Cb],ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P),
{reply, ok, State#state{cellbuf=[]}};
handle_call({setcell,Etat,Cell}, _From, #state{cellbuf=Cb} = State) ->
{reply, ok, State#state{cellbuf=[{Etat,Cell}|Cb]}};
handle_call(What, _From, State) ->
{stop, {call, What}, State}.
do_refresh(Cb,ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P) ->
wx:batch(fun () ->
Cb1 = lists:reverse(Cb),
MemoryDC = wxMemoryDC:new(Bitmap),
[cell(MemoryDC,PV,BV,PM,BM,State,Cell,Z) || {State,Cell} <- Cb1],
wxDC:blit(ClientDC, {0,0},{W,H},MemoryDC, {0,0}),
wxMemoryDC:destroy(MemoryDC)
end).
在Windows中,这不会刷新屏幕,我不得不添加同步绘制事件来更新屏幕:
handle_sync_event(#wx{event = #wxPaint{}}, _wxObj, #state{panel=Panel, bitmap=Bitmap, w=W, h=H}) ->
DC = wxPaintDC:new(Panel),
wxPaintDC:destroy(DC),
ok.
此功能除了从Panel创建PaintDC并立即销毁它之外什么都不做。它看起来有点神奇,但它是我从wxWidget文档中理解的(用户窗口可以隐藏或移动,但从未调整大小,所以我没有什么可以重绘的)
正如我所说,这在Windows XP上工作得很好,动画很流畅,我没有闪烁,但是对于linux,屏幕永远不会更新。我可以看到位图已更新,因为如果我暂停动画,缩小窗口并重新打开它,新图像就在那里。
以避免闪烁并在不同平台上工作的方式实现此类动画的正确方法是什么?
[编辑1]
这里是PaintDC文档的摘录,据说即使我不使用它,我也必须在onPaint事件期间构建和销毁PaintDC。我需要这个事件,因为用户可能隐藏,最小化或移动窗口(我知道这些事件将创建一个onPaint事件,我将在今晚检查)。
Detailed Description
A wxPaintDC must be constructed if an application wishes to paint on the client area of a window from within an EVT_PAINT() event handler.
This should normally be constructed as a temporary stack object; don't store a wxPaintDC object. If you have an EVT_PAINT() handler, you must create a wxPaintDC object within it even if you don't actually use it.
Using wxPaintDC within your EVT_PAINT() handler is important because it automatically sets the clipping area to the damaged area of the window. Attempts to draw outside this area do not appear.
To draw on a window from outside your EVT_PAINT() handler, construct a wxClientDC object.
To draw on the whole window including decorations, construct a wxWindowDC object (Windows only).
A wxPaintDC object is initialized to use the same font and colours as the window it is associated with.
尽管如此,我会尝试在我的代码中重新插入一个refresh(),如果我没记错的话我删除了它,因为它没用。