Highlighting a region of the frame (wx.PaintDC; events triggering other events)

Hello everyone,

I am writing a user interface for a behavioural experiment. I need
that when the players mouse over a certain area of the screen, another
area of the screen gets highlighted.

I have solved the problem in the past by creating a
wx.EVT_ENTER_WINDOW, binding it to the correct widget; this event
calls a method setting to bold and bigger font all the elements of the
area of the screen I need to highlight. This works, but it is not so
nice to see and not so intuitive for the subjects.

It would be more intuitive that, when the user mouses over the widget,
a red rectangle gets drawn upon the needed area, to be deleted when
the user mouses away from the trigger widget.

I tried to use wx.PaintDC, following online tutorials. But I have
problems: in all examples, the EVT_PAINT has been bounded to the
panel. I need a sort of 'second order bounding': I need in a way to
bound a wx.EVT_ENTER_WINDOW to the trigger widget, and I need this
event to trigger a EVT_PAINT. How do I do this?

Do I need nested events, i.e. events bounded to events? In other
words: how can one event (mousing over a widget) generate a chain of
events, first a evt_enter_window, and then this in turn generating a
evt_paint?

Here follows a test app I have written. It DOES NOT work on windows
(because all PaintDC outside of a EVT_PAINT does not work) and it
works in a peculiar way in Linux: when mousing over, the rectangle is
drawn; but when mousing away, all the frame becomes white.

Any suggestion on how to make a mouseover event trigger a paint event?

Thanks!

Paolo

···

-----------------------------------------

import wx

class MyFrame(wx.Frame):
    def __init__(self, parent=None, id=-1, title=None):
        wx.Frame.__init__(self, parent, id, title)
        self.panel = wx.Panel(self, size=(550, 300))

        self.button1 = wx.Button(self.panel, -1, 'Mouse Here',
size=(80,30))
        self.button1.Bind(wx.EVT_ENTER_WINDOW, self.on_paint)
        self.button1.Bind(wx.EVT_LEAVE_WINDOW, self.on_clearpaint)
        self.button1.SetPosition((10,10))
        self.Fit()

    def on_paint(self, event):
        dc = wx.PaintDC(self.panel)
        dc.SetPen(wx.Pen('red', 4))
        rect = wx.Rect(100, 10, 200, 200)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.DrawRoundedRectangleRect(rect, 8)

    def on_clearpaint(self, event):
        dc = wx.PaintDC(self.panel)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.Clear()

app = wx.PySimpleApp()
frame1 = MyFrame(title='rounded-rectangle & circle')
frame1.Center()
frame1.Show()
app.MainLoop()

Hi,
   I am not sure what is the exact effect you need to do.
   But I think you can just bind a method to wx.EVT_ENTER_WINDOW, and
in that method, Refresh() the window which will trigger the
wx.EV_PAINT event.

···

On Jan 26, 11:02 am, Paolo Crosetto <paolo.crose...@gmail.com> wrote:

Hello everyone,

I am writing a user interface for a behavioural experiment. I need
that when the players mouse over a certain area of the screen, another
area of the screen gets highlighted.

I have solved the problem in the past by creating a
wx.EVT_ENTER_WINDOW, binding it to the correct widget; this event
calls a method setting to bold and bigger font all the elements of the
area of the screen I need to highlight. This works, but it is not so
nice to see and not so intuitive for the subjects.

It would be more intuitive that, when the user mouses over the widget,
a red rectangle gets drawn upon the needed area, to be deleted when
the user mouses away from the trigger widget.

I tried to use wx.PaintDC, following online tutorials. But I have
problems: in all examples, the EVT_PAINT has been bounded to the
panel. I need a sort of 'second order bounding': I need in a way to
bound a wx.EVT_ENTER_WINDOW to the trigger widget, and I need this
event to trigger a EVT_PAINT. How do I do this?

Do I need nested events, i.e. events bounded to events? In other
words: how can one event (mousing over a widget) generate a chain of
events, first a evt_enter_window, and then this in turn generating a
evt_paint?

Here follows a test app I have written. It DOES NOT work on windows
(because all PaintDC outside of a EVT_PAINT does not work) and it
works in a peculiar way in Linux: when mousing over, the rectangle is
drawn; but when mousing away, all the frame becomes white.

Any suggestion on how to make a mouseover event trigger a paint event?

Thanks!

Paolo

-----------------------------------------

import wx

class MyFrame(wx.Frame):
def __init__(self, parent=None, id=-1, title=None):
wx.Frame.__init__(self, parent, id, title)
self.panel = wx.Panel(self, size=(550, 300))

    self\.button1 = wx\.Button\(self\.panel, \-1, &#39;Mouse Here&#39;,

size=(80,30))
self.button1.Bind(wx.EVT_ENTER_WINDOW, self.on_paint)
self.button1.Bind(wx.EVT_LEAVE_WINDOW, self.on_clearpaint)
self.button1.SetPosition((10,10))
self.Fit()

def on\_paint\(self, event\):
    dc = wx\.PaintDC\(self\.panel\)
    dc\.SetPen\(wx\.Pen\(&#39;red&#39;, 4\)\)
    rect = wx\.Rect\(100, 10, 200, 200\)
    dc\.SetBrush\(wx\.TRANSPARENT\_BRUSH\)
    dc\.DrawRoundedRectangleRect\(rect, 8\)

def on\_clearpaint\(self, event\):
    dc = wx\.PaintDC\(self\.panel\)
    dc\.SetBrush\(wx\.TRANSPARENT\_BRUSH\)
    dc\.Clear\(\)

app = wx.PySimpleApp()
frame1 = MyFrame(title='rounded-rectangle & circle')
frame1.Center()
frame1.Show()
app.MainLoop()

Hi,

Hello everyone,

I am writing a user interface for a behavioural experiment. I need
that when the players mouse over a certain area of the screen, another
area of the screen gets highlighted.

I have solved the problem in the past by creating a
wx.EVT_ENTER_WINDOW, binding it to the correct widget; this event
calls a method setting to bold and bigger font all the elements of the
area of the screen I need to highlight. This works, but it is not so
nice to see and not so intuitive for the subjects.

It would be more intuitive that, when the user mouses over the widget,
a red rectangle gets drawn upon the needed area, to be deleted when
the user mouses away from the trigger widget.

I tried to use wx.PaintDC, following online tutorials. But I have
problems: in all examples, the EVT_PAINT has been bounded to the
panel. I need a sort of 'second order bounding': I need in a way to
bound a wx.EVT_ENTER_WINDOW to the trigger widget, and I need this
event to trigger a EVT_PAINT. How do I do this?

Do I need nested events, i.e. events bounded to events? In other
words: how can one event (mousing over a widget) generate a chain of
events, first a evt_enter_window, and then this in turn generating a
evt_paint?

Here follows a test app I have written. It DOES NOT work on windows
(because all PaintDC outside of a EVT_PAINT does not work) and it
works in a peculiar way in Linux: when mousing over, the rectangle is
drawn; but when mousing away, all the frame becomes white.

Any suggestion on how to make a mouseover event trigger a paint event?

I would suggest just using the mouse over events to set a flag of
what you need to draw in OnPaint. Then after setting the flag call
Refresh() to trigger a PaintEvent.

Pseudo code:

def onMouseEnter(self, event):
    self._rectangle = getRectOfChildWidget(...)
    self.Refresh()

def OnMouseLeave(self, event):
    self._rectangle = None
    self.Refresh()

def onPaint(self, event):
    dc = wx.PaintDC(self):
    if self._rectangle:
        dc.DrawRectangle(...)
    else:
        dc.Clear()

Cody

···

On Wed, Jan 26, 2011 at 1:02 PM, Paolo Crosetto <paolo.crosetto@gmail.com> wrote:

A different approach would be to change the background color (like to
yellow) on the panel on which the to-be-highlighted widgets are. But
generally I haven't seen this sort of prompting on most apps GUIs, and
I'd guess some usability experts would say that if the widgets are
grouped intuitively, this sort of highlighting is unnecessary and only
adds noise to the look and feel.

Che

···

On Wed, Jan 26, 2011 at 2:02 PM, Paolo Crosetto <paolo.crosetto@gmail.com> wrote:

Hello everyone,

I am writing a user interface for a behavioural experiment. I need
that when the players mouse over a certain area of the screen, another
area of the screen gets highlighted.

I have solved the problem in the past by creating a
wx.EVT_ENTER_WINDOW, binding it to the correct widget; this event
calls a method setting to bold and bigger font all the elements of the
area of the screen I need to highlight. This works, but it is not so
nice to see and not so intuitive for the subjects.

Che, Cody Zheng,

thanks for the useful hints.

Che: I know this is less than beautiful. I tried to change background
color but that was really ugly. It is a behavioural experiment and
what we really need is to be sure that the user singles out the
particular area of the screen where her current decision will have an
impact. Th red-rectangle-over seemed the best solution

Cody: thanks for the pseudocode. I tried it and it works. One remark:
in windows it works as it should; in linux still the strange behaviour
that when calling dc.Clear() it repaints the background white and not
with the default grey background. I tried working around with the
SetBrush method but to no real result. I need to deploy this on
windows hence no problem, but just to signal the fact that on linux it
does not work as expected - Clear() paints white background.

Just for reference, the working code is as follows:

···

-------------
import wx

class MyFrame(wx.Frame):
    def __init__(self, parent=None, id=-1, title=None):
        wx.Frame.__init__(self, parent, id, title)
        self.panel = wx.Panel(self, size=(550, 300))

        self.panel.Bind(wx.EVT_PAINT, self.on_paint)

        self.button1 = wx.Button(self.panel, -1, 'Mouse Here',
size=(80,30))
        self.button1.Bind(wx.EVT_ENTER_WINDOW, self.on_mouseover)
        self.button1.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouseaway)
        self.button1.SetPosition((10,10))
        self._rect = None
        self.Fit()

    def on_mouseover(self, event):
        self._rect = wx.Rect(100, 10, 200, 200)
        self.panel.Refresh()

    def on_mouseaway(self, event):
        self._rect = None
        self.panel.Refresh()

    def on_paint(self, event):
        dc = wx.PaintDC(self.panel)
        if self._rect:
            dc.SetPen(wx.Pen('red', 4))
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            dc.DrawRoundedRectangleRect(self._rect, 8)
        else:
            dc.Clear()

app = wx.PySimpleApp()
frame1 = MyFrame(title='rounded-rectangle & circle')
frame1.Center()
frame1.Show()
app.MainLoop()

The active theme may be using a texture for the background so when the DC tries to clear it to the default solid colour there is a visible mismatch. However in this case you can just leave out the dc.Clear() because by default the window will have already been cleared to the default background before the EVT_PAINT handler is called.

···

On 1/26/11 11:39 AM, Paolo Crosetto wrote:

Che, Cody Zheng,

thanks for the useful hints.

Che: I know this is less than beautiful. I tried to change background
color but that was really ugly. It is a behavioural experiment and
what we really need is to be sure that the user singles out the
particular area of the screen where her current decision will have an
impact. Th red-rectangle-over seemed the best solution

Cody: thanks for the pseudocode. I tried it and it works. One remark:
in windows it works as it should; in linux still the strange behaviour
that when calling dc.Clear() it repaints the background white and not
with the default grey background. I tried working around with the
SetBrush method but to no real result. I need to deploy this on
windows hence no problem, but just to signal the fact that on linux it
does not work as expected - Clear() paints white background.

--
Robin Dunn
Software Craftsman

Thanks Robin,

that solved it.

I have now a follow-up problem. Both in Linux and in Windows, the red
rectangle (not in the small example here, but in the real app) gets
drawn _behind_ the widgets. I have a panel (main) that contains other
panels (child); and the rectangle is drawn behind those nested panels,
so that it disappears and the whole point of highlighting is missed.
In Linux, I can make the wx.dcPaint(main) be a child of the nested
panel [wx.dcPaint(child)], and in this case the rectangle is drawn (1)
with coords relative to that panel and (2) over it and not behind it.
But the same trick in windows does not work: the app hangs, no output
is sent to the python console, and I do not know what is going wrong.

Is there a way to draw on the highest possible level, so that this
dcPaint is 'above' all the rest? I tried to make the wx.dcPaint be the
child of the main frame but that did not work.

Thanks!

Paolo

···

On 26 Gen, 21:02, Robin Dunn <ro...@alldunn.com> wrote:

On 1/26/11 11:39 AM, Paolo Crosetto wrote:

> Che, Cody Zheng,

> thanks for the useful hints.

> Che: I know this is less than beautiful. I tried to change background
> color but that was really ugly. It is a behavioural experiment and
> what we really need is to be sure that the user singles out the
> particular area of the screen where her current decision will have an
> impact. Th red-rectangle-over seemed the best solution

> Cody: thanks for the pseudocode. I tried it and it works. One remark:
> in windows it works as it should; in linux still the strange behaviour
> that when calling dc.Clear() it repaints the background white and not
> with the default grey background. I tried working around with the
> SetBrush method but to no real result. I need to deploy this on
> windows hence no problem, but just to signal the fact that on linux it
> does not work as expected - Clear() paints white background.

The active theme may be using a texture for the background so when the
DC tries to clear it to the default solid colour there is a visible
mismatch. However in this case you can just leave out the dc.Clear()
because by default the window will have already been cleared to the
default background before the EVT_PAINT handler is called.

--
Robin Dunn
Software Craftsmanhttp://wxPython.org

Paolo Crosetto wrote:

I tried to use wx.PaintDC, following online tutorials. But I have
problems: in all examples, the EVT_PAINT has been bounded to the
panel. I need a sort of 'second order bounding': I need in a way to
bound a wx.EVT_ENTER_WINDOW to the trigger widget, and I need this
event to trigger a EVT_PAINT. How do I do this?

A wx.PaintDC can only be used inside an EVT_PAINT handler, but you can
create a wx.ClientDC and do drawing with it any time you want.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

[[ Please do not use top-posting for replies to this group. See Top Posting and Bottom Posting and Why is Bottom-posting better than Top-posting for the reasons why. ]]

Thanks Robin,

that solved it.

I have now a follow-up problem. Both in Linux and in Windows, the red
rectangle (not in the small example here, but in the real app) gets
drawn _behind_ the widgets.

Correct. You are painting the panel, and the panel is behind the panel's child widgets.

I have a panel (main) that contains other
panels (child); and the rectangle is drawn behind those nested panels,
so that it disappears and the whole point of highlighting is missed.
In Linux, I can make the wx.dcPaint(main) be a child of the nested
panel [wx.dcPaint(child)],

Just a terminology issue, but DCs are not "children" of the windows. The DC is just an abstract drawing context and in the case of wx.PaintDC the target of the drawing operations is the window.

and in this case the rectangle is drawn (1)
with coords relative to that panel and (2) over it and not behind it.
But the same trick in windows does not work: the app hangs, no output
is sent to the python console, and I do not know what is going wrong.

Because you are painting a different window than the EVT_PAINT event was sent to. On Windows that is a problem since when control returns from the system it senses that the window is still "dirty" and needs to be refreshed, so it sends another paint event immediately.

Is there a way to draw on the highest possible level, so that this
dcPaint is 'above' all the rest? I tried to make the wx.dcPaint be the
child of the main frame but that did not work.

I think if you learn to visualize things a bit differently here then it will help you understand better how it works. Try inverting your sense of the "highest possible level" such that the frame is at the bottom of a stack of rectangular sheets of paper. The child(ren) of the frame is another sheet of paper that is laid on top of the frame, and the children of those windows are more rectangular pieces of paper laid on top of the stack. The stack is oriented such that the bottom layer (the frame) is on the surface of your computer's monitor, and the higher levels of the stack are advancing from the screen towards your eyes.

Now, with that visualization in mind you should notice that the only portions of the frame that can be seen are those that are not covered by other pieces of paper (the title bar and the borders,) and the same goes for the panels and their children. So taking it one step further, if you draw something on the paper representing the frame, then it can not be seen by your eyes because there are other papers on top of it, or in other cases you may see parts of the drawing because the paper you are drawing on is not totally obscured by the papers further up the stack.

Back to your issue, if you can arrange the panels and child widgets such that the place where you need to draw the rectangle is all on one widget and not obscured by children then you can continue on the path you are on now and it should work well. Or if you have two or more neighboring panels that should each have part of the rectangle then you could simply refresh all of them and have them each draw the rectangle with an appropriate offset such that it looks like a single rectangle.

If things can not be laid out that way for some reason then there are other ways to do this. You can use a wx.ScreenDC to draw on top of everything that is currently visible. The down side to this is that it can be a little glitchy at times. The widgets you are drawing over have no idea that you've drawn something to the screen and so as soon as they redraw themselves then what you drew to that portion of the screen DC will be gone. But if it's just going to be shown for a short time then it should be okay.

The coordinates in the screen DC are relative to the screen (duh) and so you'll need to translate from the position within the window where you want the drawing to show up. the ClientToScreen method will help you there. Also, you will want to get rid of your EVT_PAINT handler since you will not need to handle it any more.

···

On 1/26/11 12:09 PM, Paolo Crosetto wrote:

--
Robin Dunn
Software Craftsman

Actually, as modern GUI systems have advanced that is not the best way to handle things any more, especially on Mac where drawing with a wx.ClientDC can slow down your application to the point where it is a noticeable difference to the user. The better way is as Cody described where you simply change program state in some way and then wait for the next paint event (perhaps triggered by a Refresh() call) to update the display as needed to show that changed state.

···

On 1/26/11 12:47 PM, Tim Roberts wrote:

Paolo Crosetto wrote:

I tried to use wx.PaintDC, following online tutorials. But I have
problems: in all examples, the EVT_PAINT has been bounded to the
panel. I need a sort of 'second order bounding': I need in a way to
bound a wx.EVT_ENTER_WINDOW to the trigger widget, and I need this
event to trigger a EVT_PAINT. How do I do this?

A wx.PaintDC can only be used inside an EVT_PAINT handler, but you can
create a wx.ClientDC and do drawing with it any time you want.

--
Robin Dunn
Software Craftsman

Robin Dunn wrote:

Actually, as modern GUI systems have advanced that is not the best way
to handle things any more, especially on Mac where drawing with a
wx.ClientDC can slow down your application to the point where it is a
noticeable difference to the user.

Really? I've never heard that before, and I've been doing graphics for
a long time.

What's the root cause? With desktop compositing today, you're just
drawing into a texture instead of the desktop surface, but I'm not clear
why it should be a performance issue.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

I'm not sure of the nitty gritty details but on Mac a multi-stage "display pipeline" is used to merge updates from the visible applications into a screen buffer, and that buffer is sent to the display such that it is updated in sync with the hardware's vertical refresh.

A wx.ClientDC (or an Update() call to force a paint event) indicates that the drawing should show up on the screen immediately, and not wait for the next cycle of the display pipeline. So the pipeline must be stopped, the drawing done with the client dc is pushed all the way through to the screen, and then the pipeline is restarted. Or something like that.

Although it is less important for the other platforms I still recommend using the same approach of using wx.ClientDC only for things like measuring text and doing all drawing in a EVT_PAINT handler (or to a memory dc) for MSW and GTK for a few reasons.

* It works as well (if not better) than a client DC drawing approach
* It makes the drawing code be a little better organized IMO.
* It tends to be a little less flickery on Windows when not double-buffering.

···

On 1/26/11 1:05 PM, Tim Roberts wrote:

Robin Dunn wrote:

Actually, as modern GUI systems have advanced that is not the best way
to handle things any more, especially on Mac where drawing with a
wx.ClientDC can slow down your application to the point where it is a
noticeable difference to the user.

Really? I've never heard that before, and I've been doing graphics for
a long time.

What's the root cause? With desktop compositing today, you're just
drawing into a texture instead of the desktop surface, but I'm not clear
why it should be a performance issue.

--
Robin Dunn
Software Craftsman

Robin Dunn wrote:

I'm not sure of the nitty gritty details but on Mac a multi-stage
"display pipeline" is used to merge updates from the visible
applications into a screen buffer, and that buffer is sent to the
display such that it is updated in sync with the hardware's vertical
refresh.

Windows (with Aero) is similar. With Aero, Windows apps don't actually
draw on the frame buffer, although they don't know it. Instead, they
draw into a texture. The Desktop Window Manager is then just a
relatively simple Direct3D application that draws the desktop as a set
of rectangles, each of which is textured from one of the texture windows.

A wx.ClientDC (or an Update() call to force a paint event) indicates
that the drawing should show up on the screen immediately, and not wait
for the next cycle of the display pipeline. So the pipeline must be
stopped, the drawing done with the client dc is pushed all the way
through to the screen, and then the pipeline is restarted. Or something
like that.

Fascinating. I will have to do some reading about that. In Windows,
that wx.ClientDC call just draws asynchronously into the window's
texture. The desktop pipeline doesn't have to stop. After all, it is
utterly silly to render anything faster than the screen refresh rate.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

I keep hearing this, but the last time I tried moving FloatCanvas to a non-ClientDC approach, it was worthless -- on the Mac, it simply didn't draw anything like quick enough. I think that may have been when rendering a rubber band box while the mouse moved.

Maybe it's time to try again -- or use Overlays for that sort of thing.

-Chris

···

On 1/26/11 1:33 PM, Robin Dunn wrote:

* It works as well (if not better) than a client DC drawing approach
* It makes the drawing code be a little better organized IMO.
* It tends to be a little less flickery on Windows when not
double-buffering.

I always forget to mention wx.Overlay... It is probably better for dealing with transient or real-time updates like rubber-banding or drawing lines with the mouse. It's wrapping a native API on the Mac which is designed for doing things like that, and IIRC it uses wxClientDC and a buffer on the other platforms.

···

On 1/26/11 9:51 PM, Chris Barker wrote:

On 1/26/11 1:33 PM, Robin Dunn wrote:

* It works as well (if not better) than a client DC drawing approach
* It makes the drawing code be a little better organized IMO.
* It tends to be a little less flickery on Windows when not
double-buffering.

I keep hearing this, but the last time I tried moving FloatCanvas to a
non-ClientDC approach, it was worthless -- on the Mac, it simply didn't
draw anything like quick enough. I think that may have been when
rendering a rubber band box while the mouse moved.

Maybe it's time to try again -- or use Overlays for that sort of thing.

--
Robin Dunn
Software Craftsman