Drawing in mouse events (trying to draw things when mouse hovers over certain areas)

I just recently created my first custom drawn thing.

Essentially, it is a Gantt Chart. I’ve created a class that inherits from wx.ScrolledWindow. The wx.EVT_PAINT event triggers self.onPaint, which paints the entire chart using a wx.GraphicsContext. I refresh the data the chart stores when user input changes and it is all drawn based on this data. I give it the date range to show and the tasks to show in it, and it uses the tasks’ start and ending date and time to draw the tasks onto the chart.

So, it is all drawn directly onto a scrolled window, and everything works just fine.

However, I’m looking to implement some more functionality, namely a small hovering popup that shows more information about the task when the mouse hovers over the task, as well as being able to move the tasks around directly in the chart.

I can’t figure out how to do either of those, but maybe it’s just the way that I’m drawing everything. I’ve got an event handler for wx.EVT_MOTION that I can get the mouse’s coordinates from, and I was just trying to draw text where the mouse is that says the current position. In my function that gets called for wx.EVT_MOTION, I’m creating the wx.PaintDC and wx.GraphicsContext and trying to draw, however I’m getting an error because I’m not in a native paint event.

Do I have to force a refresh and then pass the data into the drawing function to paint it then? Or how else can I do it?

I’m using wxPython 2.9.3.1 on a Mac.

Thank you.

I’m sorry, that was very silly of me. It turns out I was trying to use the PaintDC which is not the one I should have been using outside of a wx.EVT_PAINT. I will try getting it to work without that, but my other question still stands.

Was that the best way for me to do that? Draw the entire Gantt chart on that panel? Or is there something else that will make it easier for me to have the user interact with the tasks?

···

On Friday, April 6, 2012 12:41:32 PM UTC-4, Cameron Leger wrote:

I just recently created my first custom drawn thing.

Essentially, it is a Gantt Chart. I’ve created a class that inherits from wx.ScrolledWindow. The wx.EVT_PAINT event triggers self.onPaint, which paints the entire chart using a wx.GraphicsContext. I refresh the data the chart stores when user input changes and it is all drawn based on this data. I give it the date range to show and the tasks to show in it, and it uses the tasks’ start and ending date and time to draw the tasks onto the chart.

So, it is all drawn directly onto a scrolled window, and everything works just fine.

However, I’m looking to implement some more functionality, namely a small hovering popup that shows more information about the task when the mouse hovers over the task, as well as being able to move the tasks around directly in the chart.

I can’t figure out how to do either of those, but maybe it’s just the way that I’m drawing everything. I’ve got an event handler for wx.EVT_MOTION that I can get the mouse’s coordinates from, and I was just trying to draw text where the mouse is that says the current position. In my function that gets called for wx.EVT_MOTION, I’m creating the wx.PaintDC and wx.GraphicsContext and trying to draw, however I’m getting an error because I’m not in a native paint event.

Do I have to force a refresh and then pass the data into the drawing function to paint it then? Or how else can I do it?

I’m using wxPython 2.9.3.1 on a Mac.

Thank you.

I'm sorry, that was very silly of me. It turns out I was trying to use the
PaintDC which is not the one I should have been using outside of a
wx.EVT_PAINT. I will try getting it to work without that, but my other
question still stands.

Was that the best way for me to do that? Draw the entire Gantt chart on that
panel? Or is there something else that will make it easier for me to have
the user interact with the tasks?

As I understand drawing, you must do all drawing during the paint
event. So what you should do is get the mouse coordinates and store
them, then force a refresh. During the refresh, you check for drawing
stuff based on the mouse position.

Similarly, if you want to be able to drag things, then you catch the
mouse down/up events, figure out which object was clicked on, and draw
it in a different position during the paint event.

-Chris

···

On Fri, Apr 6, 2012 at 11:28 AM, Cameron Leger <cleger@thedvigroup.com> wrote:

On Friday, April 6, 2012 12:41:32 PM UTC-4, Cameron Leger wrote:

I just recently created my first custom drawn thing.

Essentially, it is a Gantt Chart. I've created a class that inherits from
wx.ScrolledWindow. The wx.EVT_PAINT event triggers self.onPaint, which
paints the entire chart using a wx.GraphicsContext. I refresh the data the
chart stores when user input changes and it is all drawn based on this data.
I give it the date range to show and the tasks to show in it, and it uses
the tasks' start and ending date and time to draw the tasks onto the chart.

So, it is all drawn directly onto a scrolled window, and everything works
just fine.

However, I'm looking to implement some more functionality, namely a small
hovering popup that shows more information about the task when the mouse
hovers over the task, as well as being able to move the tasks around
directly in the chart.

I can't figure out how to do either of those, but maybe it's just the way
that I'm drawing everything. I've got an event handler for wx.EVT_MOTION
that I can get the mouse's coordinates from, and I was just trying to draw
text where the mouse is that says the current position. In my function that
gets called for wx.EVT_MOTION, I'm creating the wx.PaintDC and
wx.GraphicsContext and trying to draw, however I'm getting an error because
I'm not in a native paint event.

Do I have to force a refresh and then pass the data into the drawing
function to paint it then? Or how else can I do it?

I'm using wxPython 2.9.3.1 on a Mac.

Thank you.

--
To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Hi Chris,

Thanks for your reply. I think you might be right. I just tried using a ClientDC, and while it doesn’t error, it doesn’t actually draw anything.

It seems that my drawing function will be getting very big then.

By creating my own custom drawn “thing” (I guess I could call this a widget?) I am missing all of the things that I would normally expect out of a widget, such as being able to tell what is being hovered. So, I have to write all of that stuff myself. Do you have any advice for that?

Currently, I will get the x and y position of the mouse. I have to translate that into what it is on top of, if anything. I’m not storing any useful information about what I’ve drawn and where, but I guess I will have to for this to work. Unless there’s a way to store the results of a drawn object (like “drawnRect = gc.DrawRect(x, y, w, h)”) and then have it return that reference when hovering.

I guess I took for granted all of the other already built widgets. This is fun, but a little overwhelming.

Forgive me for thinking outloud here. I think I can do this hover information by storing each task’s x1,y1,x2,y2 with it’s ID, and then checking if the mouse is inside of that region. That seems like it would be a lot though, looping through each region to check if the mouse is inside of it.

I was hoping for more of a tool-tip kind of feel, where the info would pop up of the mouse was hovered over it for a second. I don’t see a Mouse Event that I could use for that, though.

It would be great if the tooltip was not a part of the PaintDC, because that is limited by the client area of that panel. Can I create a separate ScreenDC just for that, so that the tooltip could extend outside of the panel if it overlapped it?

···

On Friday, April 6, 2012 2:35:08 PM UTC-4, Chris wrote:

On Fri, Apr 6, 2012 at 11:28 AM, Cameron Leger cleger@thedvigroup.com wrote:

I’m sorry, that was very silly of me. It turns out I was trying to use the
PaintDC which is not the one I should have been using outside of a
wx.EVT_PAINT. I will try getting it to work without that, but my other
question still stands.

Was that the best way for me to do that? Draw the entire Gantt chart on that
panel? Or is there something else that will make it easier for me to have
the user interact with the tasks?
As I understand drawing, you must do all drawing during the paint
event. So what you should do is get the mouse coordinates and store
them, then force a refresh. During the refresh, you check for drawing
stuff based on the mouse position.

Similarly, if you want to be able to drag things, then you catch the
mouse down/up events, figure out which object was clicked on, and draw
it in a different position during the paint event.

-Chris

On Friday, April 6, 2012 12:41:32 PM UTC-4, Cameron Leger wrote:

I just recently created my first custom drawn thing.

Essentially, it is a Gantt Chart. I’ve created a class that inherits from
wx.ScrolledWindow. The wx.EVT_PAINT event triggers self.onPaint, which
paints the entire chart using a wx.GraphicsContext. I refresh the data the
chart stores when user input changes and it is all drawn based on this data.
I give it the date range to show and the tasks to show in it, and it uses
the tasks’ start and ending date and time to draw the tasks onto the chart.

So, it is all drawn directly onto a scrolled window, and everything works
just fine.

However, I’m looking to implement some more functionality, namely a small
hovering popup that shows more information about the task when the mouse
hovers over the task, as well as being able to move the tasks around
directly in the chart.

I can’t figure out how to do either of those, but maybe it’s just the way
that I’m drawing everything. I’ve got an event handler for wx.EVT_MOTION
that I can get the mouse’s coordinates from, and I was just trying to draw
text where the mouse is that says the current position. In my function that
gets called for wx.EVT_MOTION, I’m creating the wx.PaintDC and
wx.GraphicsContext and trying to draw, however I’m getting an error because
I’m not in a native paint event.

Do I have to force a refresh and then pass the data into the drawing
function to paint it then? Or how else can I do it?

I’m using wxPython 2.9.3.1 on a Mac.

Thank you.


To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Chris Weisiger wrote:

As I understand drawing, you must do all drawing during the paint
event. So what you should do is get the mouse coordinates and store
them, then force a refresh. During the refresh, you check for drawing
stuff based on the mouse position.

Quite often, that IS the best approach. You are supposed to be able to
recreate your entire window at any time in the paint event. If another
window cover you and then closes, you get a paint event, and it's up to
you to put things back the way they were. So, your paint handler is
going to have to be pretty thorough.

However, it is quite possible to do drawing in any event handler. You
just use a wx.ClientDC instead of a wx.PaintDC.

···

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

Hi Chris,

Thanks for your reply. I think you might be right. I just tried using a
ClientDC, and while it doesn't error, it doesn't actually draw anything.

It seems that my drawing function will be getting very big then.

You can pass the PaintDC you create in the drawing function down to
subsidiary functions. In fact I recommend that you create each of your
"tasks" as an instance of a "bare" class (not subclassing from
anything) that knows where it is and how big it is, and has a function
that accepts a DC and draws itself and any necessary tooltip. Then in
your top-level class you have something like this:

def onMouseEvent(self, event):
    self.mouseX, self.mouseY = event.GetPosition()
    self.Refresh()

def onPaint(self, event):
    dc = wx.PaintDC(self)
    for widget in self.widgets:
        widget.paint(dc, self.mouseX, self.mouseY)

...

(in Widget class)
def paint(self, dc, mouseX, mouseY):
    # Draw the widget
    if (mouseX, mouseY) is contained by my rect:
        # Draw the tooltip

Each widget will need to know how big it is when drawn, so it can
calculate its rect based on its position and that size. The rect in
turn allows you to detect if the mouse is over that widget, so you can
draw the tooltip as needed.

Make sense?

-Chris

···

On Fri, Apr 6, 2012 at 11:49 AM, Cameron Leger <cleger@thedvigroup.com> wrote:

By creating my own custom drawn "thing" (I guess I could call this a
widget?) I am missing all of the things that I would normally expect out of
a widget, such as being able to tell what is being hovered. So, I have to
write all of that stuff myself. Do you have any advice for that?

Currently, I will get the x and y position of the mouse. I have to translate
that into what it is on top of, if anything. I'm not storing any useful
information about what I've drawn and where, but I guess I will have to for
this to work. Unless there's a way to store the results of a drawn object
(like "drawnRect = gc.DrawRect(x, y, w, h)") and then have it return that
reference when hovering.

I guess I took for granted all of the other already built widgets. This is
fun, but a little overwhelming.

Forgive me for thinking outloud here. I think I can do this hover
information by storing each task's x1,y1,x2,y2 with it's ID, and then
checking if the mouse is inside of that region. That seems like it would be
a lot though, looping through each region to check if the mouse is inside of
it.

I was hoping for more of a tool-tip kind of feel, where the info would pop
up of the mouse was hovered over it for a second. I don't see a Mouse Event
that I could use for that, though.

It would be great if the tooltip was not a part of the PaintDC, because that
is limited by the client area of that panel. Can I create a separate
ScreenDC just for that, so that the tooltip could extend outside of the
panel if it overlapped it?

On Friday, April 6, 2012 2:35:08 PM UTC-4, Chris wrote:

On Fri, Apr 6, 2012 at 11:28 AM, Cameron Leger <cleger@thedvigroup.com> >> wrote:
> I'm sorry, that was very silly of me. It turns out I was trying to use
> the
> PaintDC which is not the one I should have been using outside of a
> wx.EVT_PAINT. I will try getting it to work without that, but my other
> question still stands.
>
> Was that the best way for me to do that? Draw the entire Gantt chart on
> that
> panel? Or is there something else that will make it easier for me to
> have
> the user interact with the tasks?

As I understand drawing, you must do all drawing during the paint
event. So what you should do is get the mouse coordinates and store
them, then force a refresh. During the refresh, you check for drawing
stuff based on the mouse position.

Similarly, if you want to be able to drag things, then you catch the
mouse down/up events, figure out which object was clicked on, and draw
it in a different position during the paint event.

-Chris

>
> On Friday, April 6, 2012 12:41:32 PM UTC-4, Cameron Leger wrote:
>>
>> I just recently created my first custom drawn thing.
>>
>> Essentially, it is a Gantt Chart. I've created a class that inherits
>> from
>> wx.ScrolledWindow. The wx.EVT_PAINT event triggers self.onPaint, which
>> paints the entire chart using a wx.GraphicsContext. I refresh the data
>> the
>> chart stores when user input changes and it is all drawn based on this
>> data.
>> I give it the date range to show and the tasks to show in it, and it
>> uses
>> the tasks' start and ending date and time to draw the tasks onto the
>> chart.
>>
>> So, it is all drawn directly onto a scrolled window, and everything
>> works
>> just fine.
>>
>> However, I'm looking to implement some more functionality, namely a
>> small
>> hovering popup that shows more information about the task when the
>> mouse
>> hovers over the task, as well as being able to move the tasks around
>> directly in the chart.
>>
>> I can't figure out how to do either of those, but maybe it's just the
>> way
>> that I'm drawing everything. I've got an event handler for
>> wx.EVT_MOTION
>> that I can get the mouse's coordinates from, and I was just trying to
>> draw
>> text where the mouse is that says the current position. In my function
>> that
>> gets called for wx.EVT_MOTION, I'm creating the wx.PaintDC and
>> wx.GraphicsContext and trying to draw, however I'm getting an error
>> because
>> I'm not in a native paint event.
>>
>> Do I have to force a refresh and then pass the data into the drawing
>> function to paint it then? Or how else can I do it?
>>
>> I'm using wxPython 2.9.3.1 on a Mac.
>>
>> Thank you.
>
> --
> To unsubscribe, send email to
> wxPython-users+unsubscribe@googlegroups.com
> or visit http://groups.google.com/group/wxPython-users?hl=en

--
To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Hi Chris,

Yes, that makes much more sense. That also takes care of things like being able to change their color if the mouse is over them, and more.

Can I create a ScreenDC at the same time as the PaintDC, and then draw the tooltip in the ScreenDC so it is not restrained by the client area of the panel? I guess that also means that I’ll have to convert the client’s coordinates to screen coordinates. Which also brings up this point: Drawing into a ScrolledWindow means that if I scroll, the mouse event’s position coordinates don’t change. I’ll also need a way to convert scrolled coordinates if I want to do this. It looks like I can pass a coordinate to CalcScrolledPosition. Is it safe to pass all coordinates through that, just in case?

Thanks for your help.

···

On Friday, April 6, 2012 3:03:41 PM UTC-4, Chris wrote:

On Fri, Apr 6, 2012 at 11:49 AM, Cameron Leger cleger@thedvigroup.com wrote:

Hi Chris,

Thanks for your reply. I think you might be right. I just tried using a
ClientDC, and while it doesn’t error, it doesn’t actually draw anything.

It seems that my drawing function will be getting very big then.
You can pass the PaintDC you create in the drawing function down to
subsidiary functions. In fact I recommend that you create each of your
“tasks” as an instance of a “bare” class (not subclassing from
anything) that knows where it is and how big it is, and has a function
that accepts a DC and draws itself and any necessary tooltip. Then in
your top-level class you have something like this:

def onMouseEvent(self, event):
self.mouseX, self.mouseY = event.GetPosition()
self.Refresh()

def onPaint(self, event):
dc = wx.PaintDC(self)
for widget in self.widgets:
widget.paint(dc, self.mouseX, self.mouseY)

(in Widget class)
def paint(self, dc, mouseX, mouseY):
# Draw the widget
if (mouseX, mouseY) is contained by my rect:
# Draw the tooltip

Each widget will need to know how big it is when drawn, so it can
calculate its rect based on its position and that size. The rect in
turn allows you to detect if the mouse is over that widget, so you can
draw the tooltip as needed.

Make sense?

-Chris

Hi,

not entirely sure where to fit this into the thread, but...

Thanks for your reply. I think you might be right. I just tried using a
ClientDC, and while it doesn't error, it doesn't actually draw anything.

That should work, however:

- it's considered best practice these days not to use a ClientDC, but
rather to call Refresh() when you need to change the drawing -- that
way the system can better time screen refreshes, etc.

Also, if you use a CientDC, you need to clean up the damaged area when
you want move whatever it is you are drawing when you move the mouse
(if, indeed, you want to do that). There are ticks for that, but...

Also, I have found ( a while back, maybe still not true) that the
"refresh() method" really can't keep up with stuff you want to do on
mouse move events, so what to do?

I'd take a look at wx.Overlay -- it was designed for just this sort of
thing -- to draw (usually temporary) stuff "on top" of what's in your
window. See the demo and google this list for discussion and examples.

It seems that my drawing function will be getting very big then.

indeed it can be -- though maybe should be multiple functions. YOu can
also loo into double buffering -- maybe buffer the gantt chart, so you
can draw new stuff on top (and remove that stuff) without re-drawing
the whole thing.

By creating my own custom drawn "thing" (I guess I could call this a
widget?) I am missing all of the things that I would normally expect out of
a widget, such as being able to tell what is being hovered. So, I have to
write all of that stuff myself. Do you have any advice for that?

Well, you're not the first one to ask "do I have to write all that
stuff myself?" nor the first to write it, so a few of us have written
gerneal purpopse libraries:

check out wx.lib.ogl and wx.lib.floatcanvas

OGL was originally designed for "boxes wtith lines connecting them" type usage.

floatcanvas was designed for zommable, pannable vector graphics.

Both have an "object" approach -- you create objects on a canvas or
something, and attach behavior, etc to them.

Your app would be pretty easy with FloatCanvas (and you'd zooming,
which maybe would be good?) I know less about OGL (I wrote
FloatCanvas).

See the recent thread on this list:

"get panel id while dragging across several panels"

for more info, a sample, and pointers to more samples...

By the way, FloatCanvas has a "foreground" layer that is kind of like
an Overlay -- I didn't use it in the example in that thread, but it
might work well for your use.

It also has a simple wrapping text box that could be handy.

HTH,

-Chris

···

On Fri, Apr 6, 2012 at 11:49 AM, Cameron Leger <cleger@thedvigroup.com> wrote:

--

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Hi Chris,

I will have a look at wx.Overlay, it sounds like it might be what I want.

When looking around at how to draw, I found there were many different libraries (all with different levels of being currently maintained) and I was a little overwhelmed. I only knew that it had to look pretty (I work for a creative video production company, and we like things to look pretty). In the demo I saw that wx.GraphicsContext wrapped a good Mac library that provided anti-aliasing and transparency, so I chose that. Already the chart itself looks really nice, so I’m happy with what I’ve done using the simple drawing tools available in the GraphicsContext.

I’ll look at OGL and FloatCanvas again. I thought the OGL library was only for 3D, or maybe it was the one in the demo where I could drag things around. Originally I was going to use a FloatCanvas, but that was for something different. I’ll be creating another custom widget soon that would be better suited for the FloatCanvas.

···

On Friday, April 6, 2012 4:57:51 PM UTC-4, Chris Barker wrote:

Hi,
not entirely sure where to fit this into the thread, but…

On Fri, Apr 6, 2012 at 11:49 AM, Cameron Leger cleger@thedvigroup.com wrote:

Thanks for your reply. I think you might be right. I just tried using a
ClientDC, and while it doesn’t error, it doesn’t actually draw anything.

That should work, however:

  • it’s considered best practice these days not to use a ClientDC, but
    rather to call Refresh() when you need to change the drawing – that
    way the system can better time screen refreshes, etc.

Also, if you use a CientDC, you need to clean up the damaged area when
you want move whatever it is you are drawing when you move the mouse
(if, indeed, you want to do that). There are ticks for that, but…

Also, I have found ( a while back, maybe still not true) that the
“refresh() method” really can’t keep up with stuff you want to do on
mouse move events, so what to do?

I’d take a look at wx.Overlay – it was designed for just this sort of
thing – to draw (usually temporary) stuff “on top” of what’s in your
window. See the demo and google this list for discussion and examples.

It seems that my drawing function will be getting very big then.

indeed it can be – though maybe should be multiple functions. YOu can
also loo into double buffering – maybe buffer the gantt chart, so you
can draw new stuff on top (and remove that stuff) without re-drawing
the whole thing.

By creating my own custom drawn “thing” (I guess I could call this a
widget?) I am missing all of the things that I would normally expect out of
a widget, such as being able to tell what is being hovered. So, I have to
write all of that stuff myself. Do you have any advice for that?

Well, you’re not the first one to ask “do I have to write all that
stuff myself?” nor the first to write it, so a few of us have written
gerneal purpopse libraries:

check out wx.lib.ogl and wx.lib.floatcanvas

OGL was originally designed for “boxes wtith lines connecting them” type usage.

floatcanvas was designed for zommable, pannable vector graphics.

Both have an “object” approach – you create objects on a canvas or
something, and attach behavior, etc to them.

Your app would be pretty easy with FloatCanvas (and you’d zooming,
which maybe would be good?) I know less about OGL (I wrote
FloatCanvas).

See the recent thread on this list:

“get panel id while dragging across several panels”

for more info, a sample, and pointers to more samples…

By the way, FloatCanvas has a “foreground” layer that is kind of like
an Overlay – I didn’t use it in the example in that thread, but it
might work well for your use.

It also has a simple wrapping text box that could be handy.

HTH,

-Chris

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

When looking around at how to draw, I found there were many different
libraries (all with different levels of being currently maintained) and I
was a little overwhelmed. I only knew that it had to look pretty (I work for
a creative video production company, and we like things to look pretty). In
the demo I saw that wx.GraphicsContext wrapped a good Mac library that
provided anti-aliasing and transparency, so I chose that. Already the chart
itself looks really nice, so I'm happy with what I've done using the simple
drawing tools available in the GraphicsContext.

Unfortunately, FloatCanvas was started before GraphicsContext
existed, so it used Dcs. which dont have all the "pretty" features.
You can hack GC drawing into it, but it's not there out of the box.
However, I've been thinking of re-factoring it to use GCs -- perhaps
this will give me some motivation (particularly if you want t o help!)

I'll look at OGL and FloatCanvas again. I thought the OGL library was only
for 3D,

It's confusing there is OpenGL -- which may get shortend to OGL
occasionaly, which is a 3-d rendering API (though also 2-d, but it's
not what you want). But I'm referring to wxOGL, which I think stands
for "Object Graphics Library" or something like that, and has nothing
to do with OpenGL.

Originally I was going to use a FloatCanvas, but that was for
something different. I'll be creating another custom widget soon that would
be better suited for the FloatCanvas.

Feel free to send questions to the FloatCanvas list.

-Chris

···

On Fri, Apr 6, 2012 at 2:39 PM, Cameron Leger <cleger@thedvigroup.com> wrote:

--

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov