Search code examples
python-2.7eventsdialogwxpython

Dialog's ShowModal() won't send EVT_PAINT


wx version: 2.8.12.1

I'm trying to build a decored Dialog (Background, Buttons, Bitmap Border, etc) but on ShowModal() the Paint event is not issued.

It works with Show() super seeding wx.PopupTransientWindow, but not with Show() or ShowModal() on wx.Dialog

If you run the example, open the Dialog and click either of the two buttons, you'll get on the terminal:

send refresh
Clicked OK/CANCEL

"Paint Event" or "Draw Function" won't be printed. "send refresh" indicates that a manual Refresh() has been issued; and that should issue an wx.EVT_PAINT.

If OnPaint is binded to wx.EVT_SHOW the functions will be called, the wx.BufferedPaintDC will be correctly set, but it won't change anything visible.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

def GetThemeTools(borderWidth, backgrounColour):
    return {
        'Pens': {
            'DarkRectSolidBorder': wx.Pen( wx.Colour(67, 67, 67), borderWidth),
        },
        'Brushes': {
            'Background': wx.Brush(backgrounColour),
            'RectBoxFilling': wx.Brush( wx.Colour(119,120,119) ),
        },
        'ForegroundColor': wx.Colour(241,241,241),
        'BackgroundColor': 'WHITE',
        'Fonts': {
            'Brief': wx.Font(  pointSize=12, 
                                family=wx.FONTFAMILY_SWISS,
                                style=wx.FONTSTYLE_NORMAL,
                                weight=wx.FONTWEIGHT_NORMAL,
                                encoding=wx.FONTENCODING_UTF8
                                ),
        },
    }


class ConfirmDialog(wx.Dialog):

    def __init__(self, parent, text="", 
                 margin=10, borderRadio=10):
        wx.Dialog.__init__(self, parent, style=wx.STAY_ON_TOP)
        # Get data to show
        self.parent = parent
        self._margin = margin
        self._borderRadio = borderRadio
        self._text = text
        self._font = None

        self.initializeTools()

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SHOW, self._sendRefresh)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

        self._setWidgets()
        self._layout()
        wx.CallAfter(self.Refresh)

    def _sendRefresh(self, e):
        if self.IsShown():
            print("send refresh")
            self.Refresh()

    def _setWidgets(self):
        self.panel = wx.Panel(self)
        self.message = wx.StaticText(self.panel, label=self.GetText())
        self.message.SetForegroundColour(self.tools['ForegroundColor'])
        self.message.SetBackgroundColour(self.tools['BackgroundColor'])

        self.cancelBtn  = wx.Button(self.panel, id=wx.ID_CANCEL, label="Cancel")
        self.okBtn      = wx.Button(self.panel, id=wx.ID_OK, label="Ok")

    def _layout(self):
        self.vSizer = wx.BoxSizer(wx.VERTICAL)
        self.buttonsSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.buttonsSizer.Add(self.okBtn)
        self.buttonsSizer.AddSpacer(20)
        self.buttonsSizer.Add(self.cancelBtn)
        self.vSizer.Add(self.message,      0, wx.CENTER|wx.BOTTOM, 5)
        self.vSizer.Add(self.buttonsSizer, 0, wx.CENTER|wx.TOP, 5)

        self.panel.SetSizer(self.vSizer)
        self.vSizer.Fit(self.panel)

    def SetMargin(self, margin):
        self._margin = margin
        self.Refresh()

    def GetMargin(self):
        return self._margin

    def SetBorderRadio(self, borderRadio):
        self._borderRadio = borderRadio
        self.Refresh()

    def GetBorderRadio(self):
        return self._borderRadio

    def SetText(self, text):
        self._text = text
        self.Refresh()

    def GetText(self):
        return self._text

    def GetFont(self):
        if not self._font:
            self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        return self._font

    def initializeTools(self):

        self.borderWidth = 6

        backColour = self.GetBackgroundColour()
        self.tools = GetThemeTools(self.borderWidth, backColour)

        self._font = self.tools['Fonts']['Brief']

    def OnPaint(self, event):
        print("Paint Event")
        dc = wx.BufferedPaintDC(self)
        self.Draw(dc)
        event.Skip()

    def Draw(self, dc):

        print("Draw Function")
        margin = self.GetMargin()
        borderRadio = self.GetBorderRadio()
        text = self.GetText()

        self.Layout()

        dc.SetBackground(self.tools['Brushes']['Background'])
        dc.SetFont(self.GetFont())
        dc.SetTextBackground(self.tools['BackgroundColor'])
        dc.Clear()

        (H, W) = self.GetSize()

        # Draw Border
        dc.SetPen(self.tools['Pens']['DarkRectSolidBorder'])
        dc.SetBrush(self.tools['Brushes']['RectBoxFilling'])
        dc.DrawRoundedRectangle( 0, 0, H, W, borderRadio)

    def OnEraseBackground(self, event):
        pass


class AppFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Custom Dialog Test")
        panel = wx.Panel(self)

        frameSizer = wx.BoxSizer(wx.VERTICAL)
        panelSizer = wx.BoxSizer(wx.VERTICAL)
        btn = wx.Button(panel, label="Show Dialog")
        btn.Bind(wx.EVT_BUTTON, self.ShowDlg)

        panelSizer.Add(btn, 0, wx.ALL, 20)

        panel.SetSizer(panelSizer)
        self.SetSizer(frameSizer)

        panelSizer.Fit(panel)
        self.Layout()

        self.Show()

    def ShowDlg(self, event):
        dia = ConfirmDialog(self, "Custom Dialog\nTest\nThird Line")
        if dia.ShowModal() == wx.ID_OK:
            print("Clicked OK")
        else:
            print("Clicked CANCEL")



app = wx.App(False)
frame = AppFrame()
app.MainLoop()

Solution

  • Following the hint on Drawing To Panel Inside of Frame:

    • I got wrong the bind of the Paint Event

      self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
      self.panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
      

      For some reason I still don't fully get (examples at self.Bind vs self.button.Bind) why the panel won't Skip() the Paint Event; leading to self never getting aware of the Paint Event.

    • And the parenthood of the canvas:

      dc = wx.BufferedPaintDC(self.panel)
      

      After playing a little bit subclassing from wx.PopupWindow (which resulted in a badly sized Window that I couldn't properly center in the frame) I realized that the DC was being rendered below the panel. It makes sence since self.panel is a child of self, so only self was being painted.


    Not an Error but on the Paint Event, it seems the panel or dialog gets back to their default size. If the panel is fit, the dialog is resized, and the window is centered again in the Paint Event, then new Paint Events will be raised and a flicker will appear. Since this is an parameterized static dialog, the dimensions can be fixed with this at the end of self._layout():

    self.panel.Fit()
    minSize = self.panel.GetSize()
    self.SetMinSize(minSize)
    self.SetMaxSize(minSize)