Pyfa: Crash when draging fit to graph window.

Created on 21 Feb 2018  路  11Comments  路  Source: pyfa-org/Pyfa

This is for version 2.0.0b3

Attempt to drag a fit to graph window.

Results in
OS version: Windows-10-10.0.16299-SP0
Python version: 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)]
wxPython version: 4.0.0b2 (wxWidgets 3.0.4)
SQLAlchemy version: 1.1.10
Logbook version: 1.0.0
Requests version: 2.11.1
Dateutil version: 2.6.0

#

Traceback (most recent call last):
File "C:\Users\holme\Documents\Sync\Git\blitzmann\Pyfa\gui\builtinShipBrowser\pfBitmapFrame.py", line 57, in OnWindowPaint
AttributeError: 'AutoBufferedPaintDC' object has no attribute 'SelectObject'

Dragging another fit result in a crash.

2.x bug fixed logged

All 11 comments

I found the same issue in this version. This issue can be reproduced by attempting to drag a fit from the "Fittings" tab. You don't have to drop it anywhere, as soon as the drag attempt is done, the error reported above is triggered.

wx.AutoBufferedPaintDC is simply a typedef of wx.PaintDC on platforms that have native double-buffering, otherwise, it is a typedef of wx.BufferedPaintDC.

I assume win 10 is native double buffered so it tries to use a wx.PaintDC object which has no SelectObject method.

Solution using wx.BufferedPaintDC instead of wx.AutoBufferedPaintDC appears to work.

EDIT thou a more graceful solution would be?

I will poke Robin Dunn about this, he's the guy that deals with wxPython. Seems like it could be a bug on their side. https://github.com/wxWidgets/Phoenix/issues/766

I want to give that issues a day or two just to see if we can get more info on if this is a bug or not. But, for pyfa, I don't mind using wx.BufferedPaintDC. Honestly, the whole drawing aspect of wx still confuses me, so I have no idea what the difference between any of them are x_x

I'm not @RobinD42, but I posted back on the issue.
Just using wx.BufferedPaintDC will work for the time being. Just beware the proneness to flickering.

I should get the necessary change in before the next major release. I'll post back here when the fix gets merged back in Phoenix.

Alright.. I've poked around a bit more and this is not a bug. (Guess I jumped the gun a bit.)
The AutoBufferedDC class is doing what its supposed to do down in wxWidgets' code. The intent of AutoBufferedPaintDC is to abstract away the need to select objects. If buffering is not the default on the system you get a BufferedPaintDC, and if it is you just get a PaintDC, which will be lacking SelectObject.

In this case (from the code example given at wxWidgets/Phoenix/issues/766) It looks like you're just drawing to a bitmap, and not to any particular widget. If that's the case, using BufferedPaintDC would be correct, and AutoBufferedPaintDC would not fit the use case.

Hi there @mesalu, thanks for the comments!

So, a lot of the custom widget drawing in this project was done before I took it over, and it's always been one of those things that I never really fully understood. Would you know of any resources that would be good to get my head around the concepts and terminology of it? For example,

Just beware the proneness to flickering.

I kind of know what this means as I've seen flickering before, but wouldn't know why it happens. And I've always felt out of my depth with it

This particular use case is to create a drag image when dragging an element. See https://github.com/pyfa-org/Pyfa/blob/0f9455769959ac3ac001b4274b23fa4716e24aba/gui/builtinShipBrowser/pfBitmapFrame.py#L53 for the class, and https://github.com/pyfa-org/Pyfa/blob/5f0ce58c295cb3e91319c770016c25643b4e3b6a/gui/builtinShipBrowser/fitItem.py#L426 on where it's instantiated.

Again, thanks for the insight, it's really appreciated!

Hey, @blitzmann

I'm not the most versed, summoning @RobinD42, or prowling the wxPython IRC channels may be a better option.

As for the DC in question, you should be able to use AutoBufferedPaintDC and not need to bother with selecting a buffer bitmap. If its needed, it'll be made down in wxWidgets, if its not then it'll be handled even further down.

In essence what's happening with buffered DCs is that you're drawing to a bitmap in memory, which is inherently faster than drawing to the widget. Then when drawing is done and the BufferedDC is being released (at the end of the event handler) the contents of the buffer bitmap are dumped onto the widget.

Flickering can be caused by a few things, in this case I believe its the extra unnecessary buffering causing extra delays in the drawing pipeline. Draw to memory -> draw to widget -> draw to (different) memory -> draw to screen. With all the memory (de)allocations and math in between it basically undoes the performance gain from buffering.

@RobinD42 will step in and correct me if I'm wrong there though

Edit: He did.
Flickering is a result of more than one thing being drawn simultaneously.

Other notes:

I hesitate to bring this up because "If it ain't broke, don't fix it" but there is a class pre-baked into wxWidgets for dragging images around:
https://wxpython.org/Phoenix/docs/html/wx.DragImage.html
You could pass it the grayscaled image and use that instead of this other class. I seem to recall there was some issues a few months back about it not being as smooth as desired on some port. Either way you'd lose out on some of the custom behavior in the existing code.

May be worth investigating though. :)

Other Other Note

We recently pushed some changes to add context management to DC objects. It was found that in some scenarios, it was taking python's garbage collector too long to deallocate DCs and that would mess up future paint events. While its not strictly necessary, I do recommend using the context management on the mdc object, just to be safe.

(ex:

with wx.AutoBufferedPaintDC(self) as mdc:
   # do the drawing stuff

)

Hi all,

I'll try to cover a few general summary points here that will hopefully answer the questions above.

Flicker

The number one cause of flicker in a GUI system is multiple things happening on the screen and the user being able to see one or more steps between the start and the end of the paint event. For example, erasing the window, drawing this, drawing that, drawing something else, done. The concept of buffered drawing (or double-buffering) addresses that by drawing all the intermediate steps into a bitmap, and then when done blitting that bitmap to the screen. That way there is only one operation that the user can see and that is the transition from the window's old image to the new one. And since GUI systems are highly optimized to do that then it can almost always be done quicker than the human eye can register that a change is happening, so it looks smooth and flicker-free.

More modern systems have double-buffering built in, so there is no need for the programmer to have to deal with it in their own code on those platforms. In fact, on OSX the flushing of the buffer usually happens between the screen refreshes so there is no possibility of seeing something in a partial state. However that imposes some limits like transitory drawing with wx.CientDCs not being as useful there (or working at all) as on other platforms.

WIndows can do built-in double buffering too, but the default is still to do it the old way so for cross-platform work we need to be aware of that and deal with it one way or another.

Buffering with DCs

The basic steps of buffered drawing in a paint event handler go something like this:

  1. Create a bitmap the size of the window. In some cases it makes sense to keep this bitmap around and reuse it for each paint event, perhaps updating it outside of paint events (and then calling Refresh) if that model works well for the application. In other cases recreating it for each paint event is fine.

  2. Create a wx.MemoryDC and select the bitmap into it.

  3. Draw the contents of the window to the memory dc.

  4. Blit the bitmap to the wx.PaintDC

Since that pattern is so common, we added some extra DC classes to help implement it. wx.BufferedDC is associated with a wx.ClientDC and will automatically flush itself to that dc when the buffered DC goes out of scope (step 4 above). It manages its own wx.MemoryDC and can either be given an existing bitmap to use as the buffer, or it can create its own (step2).

wx.BufferedPaintDC is basically the same, except it must be used in a paint event handler, and it will create the wx.PaintDC itself. If you give it an existing buffer bitmap and nothing else needs to be drawn to it, then it's possible that the paint event handler can be just a single line. This is shown in the ScrolledWindow sample in the demo.

wx.AutoBufferedPaintDC is documented as deriving from wx.BufferedPaintDC but on some platforms it doesn't, where instead it derives directly from wx.PaintDC. It depends on if the platform has implicit buffering or not. The idea is that you can let it either do the internal buffering, or draw directly to the system buffer, and not have to worry about providing it with a buffer bitmap yourself. Hence the SelectObject method doesn't need to be there and shouldn't have been exposed in Classic.

Based on my experience it doesn't really hurt to always use a buffer bitmap and wx.BufferedPaintDC. On the platforms that don't need it you end up triple-buffering but unless performance is a serious problem the few extra milliseconds you waste probably won't be noticed.

Thanks so much to both of you for all the information!

I will attempt to go heads down into various drawing tutorials to better grasp it all, but this as very helpful.

As for the issue, I'll be converting it to wx.BufferedPaintDC and hopefully evaluating other drawing aspects soon to see if there needs to be any tweaks.

@RobinD42 Been looking over the following wiki pages https://wiki.wxpython.org/RecipesImagesAndGraphics

Looks ike there could be a lot of great info here on the concepts of drawing, however it's kind of scattered about, with some pages not completed. Are there any plans to update and modernize this information? Is there a new wiki for Pheonix?

The wiki is pretty much just community maintained these days, although there hasn't been a lot of action there for a while. I would love to see somebody take it under their wing and do some organizing and get things up to date.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ArtificialQualia picture ArtificialQualia  路  7Comments

sincomil picture sincomil  路  9Comments

Tetractys picture Tetractys  路  8Comments

MarHelmer picture MarHelmer  路  9Comments

Dawnkeeper picture Dawnkeeper  路  5Comments