I made a small program using wxWidget in erlang, and succeed to have it working fine under windows XP. This program animates a bitmap based on internal events and refresh it using a time base (no user event). My problem is that the code does not works properly under Linux Ubuntu (11.04).
the bitmap panel is initialized as follow:
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).
The process receives some messages from other processes telling it to draw a cell. The window is suppose to automatically refresh the screen or store the change in a list depending on the value of an internal state (refreshtoggle). it receives also some "refresh" messages telling it to execute the pending list of image modification and refresh the screen. I have done this using a synchronous event which update a list of pending modification and execute it when it is require by calling a refresh function:
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).
In windows, this does not refresh the screen, I had to add a synchronous paint event to get the screen updated:
handle_sync_event(#wx{event = #wxPaint{}}, _wxObj, #state{panel=Panel, bitmap=Bitmap, w=W, h=H}) ->
DC = wxPaintDC:new(Panel),
wxPaintDC:destroy(DC),
ok.
This function does nothing but create a PaintDC from the Panel and destroy it immediately. It looks a bit magic but it is what I have understood from the wxWidget documentation (The user window can be hidden or moved but never resized, so I have nothing to repaint really)
As I said this works very fine on windows XP, the animation is smooth and I have no flickering, but with linux, the screen is never updated. I can see that the bitmap is updated, because if I pause the animation, reduce the window and reopen it, the new image is there.
What is the right method to achieve this kind of animation in a way that avoids flickering and works on different platform?
[edit 1]
here is an extract of the PaintDC documentation, it is said that even if I don't use it I must build and destroy a PaintDC during the onPaint event. I need this event because the user may hide, minimize or move the window (i understood that these event will create an onPaint event, I will check this tonight).
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.
Nevertheless, I will try to re-insert a refresh() in my code, If i remember correctly I removed it because it was useless.
I don't understand the Erlang code well, but you definitely don't refresh the screen by creating and destroying a wxPaintDC
. Use Refresh() to do it instead.