wxButton wxClientDC trouble

I'm trying to make a button that has 2 functions:
    - left clicking - performs some function
    - right clicking - alternately locks and unlocks the button
    when the locked:
    - left clicking is temporarlily disabled
    - the button displays an "X" across its face
    To make the "X" acroos the face when locked, I'm trying to get a wxCLientDC
and draw the "X"; but I'm having trouble. The "X" ends up getting drawn on
the button's parent window rather than on the button.

Am I doing something wrong or is there some other problem here?

I'm on linux box running RH9, using python 2.2. I see this problem with
wxPythonGTK-py2.2-2.4.0.7-1 and wxPythonGTK-py2.2-2.4.2.4-1.

Sample code below --------------------------------------------------------

from wxPython.wx import *

class LockButton( wxButton ):
    def __init__( self, parent, *args, **kwargs ):
        self.state = 1 # unlocked
                wxButton.__init__( self, parent, *args, **kwargs )
                EVT_LEFT_UP( self, self.OnLeftClick )
        EVT_RIGHT_UP( self, self.OnRightClick )
            def OnLeftClick(self, event):
        if self.state:
            event.Skip()
            def OnRightClick(self, event): # lock / unlock
        if self.state == 1:
            self.lock( 0 )
        else:
            self.lock( 1 )
                def lock( self, state ):
        if state == 0: # Lock
            self.state = 0
            self.SetBackgroundColour( wxColor(255,0,0 ) )
            w,h = self.GetSizeTuple()
            dc = wxClientDC( self )
            dc.BeginDrawing()
            dc.SetPen( wxPen( (0,0,0), 3 ) )
            dc.DrawLine( 0,0, w,h )
            dc.DrawLine( w,0, 0,h )
            dc.EndDrawing()
        else: # Unlock
            self.state = 1
            self.SetBackgroundColour( wxColor(0,255,0) )
            dc = wxClientDC( self )
            dc.Clear()

···

##----------------------------------------------------------------------

if __name__ == '__main__':
    class TestFrame(wxFrame):
        def __init__(self, parent):
            wxFrame.__init__(self, parent, -1, "Lock Button Test",
                size=wxSize( 300, 200) )
            panel = wxPanel( self, -1 )
                        button = LockButton( panel, id=50, label='',
                pos=wxPoint(20, 50) )
            EVT_BUTTON(button, 50, self.OnClick)
                def OnClick(self, event):
            print "Click! (%d)" % event.GetId()
                class App(wxApp):
        def __init__( self ):
            wxApp.__init__(self, 0)
            def OnInit( self ):
            frame = TestFrame( None )
            frame.Show(True)
            self.SetTopWindow(frame)
                        return True
                app = App()
    app.MainLoop()

gary7 wrote:

I'm trying to make a button that has 2 functions:
    - left clicking - performs some function
    - right clicking - alternately locks and unlocks the button

when the locked:
    - left clicking is temporarlily disabled
    - the button displays an "X" across its face

To make the "X" acroos the face when locked, I'm trying to get a wxCLientDC
and draw the "X"; but I'm having trouble. The "X" ends up getting drawn on
the button's parent window rather than on the button.

Am I doing something wrong or is there some other problem here?

I see the same problem, and I can't tell you why, but I have a couple of
thoughts:

1) YOu may not be abel to subclass a wxButton and draw on it this way.
I'm pretty sure wxButtons are wrappers around native buttons, so you
might not have full control of how they are drawn.

2) you catch the left-up and right-up events, so you have the
interesting effect that if right mouse button is down, the user drags
the mouse onto the button, then released it, you'll interpret that as a
click. You may or may not want that.

3) You may be better off using the wxGeneric button class to subclass
your special button from. It is pure wxPython, and therefore you should
have full control of how it behaves. See the demo under More controls->
Generic Buttons

4) A small thing:

You don't need to use wxPoint and wxSize objects most of the time, you
can usually just pass in a tuple.:

button = LockButton( panel, id=50, pos=(20, 50), size = (60,20) )

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                        
NOAA/OR&R/HAZMAT (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

Chris Barker wrote:

gary7 wrote:

I'm trying to make a button that has 2 functions:
   - left clicking - performs some function
   - right clicking - alternately locks and unlocks the button
   
when the locked:
   - left clicking is temporarlily disabled
   - the button displays an "X" across its face

To make the "X" acroos the face when locked, I'm trying to get a wxCLientDC
and draw the "X"; but I'm having trouble. The "X" ends up getting drawn on
the button's parent window rather than on the button.

Am I doing something wrong or is there some other problem here?
   
I see the same problem, and I can't tell you why, but I have a couple of
thoughts:

1) YOu may not be abel to subclass a wxButton and draw on it this way.
I'm pretty sure wxButtons are wrappers around native buttons, so you
might not have full control of how they are drawn.

humm ... it's heviour is quite peculiar so that kinda makes sense.

2) you catch the left-up and right-up events, so you have the
interesting effect that if right mouse button is down, the user drags
the mouse onto the button, then released it, you'll interpret that as a
click. You may or may not want that.

thanks, I'll worry about that later. I just want to make it work and figure out the right general strategy for now.

3) You may be better off using the wxGeneric button class to subclass
your special button from. It is pure wxPython, and therefore you should
have full control of how it behaves. See the demo under More controls->
Generic Buttons

thanks. thats a good pointer. I was looking mostly at the documentaion rather than the demo and missed the generic buttons because they aren't in the documentation (at least not the help thingy) - which makes them a little harder to use. Looks like I have to use the source, which I'm working on.

With my first attempt there is still odd behavior though. I took my sample and simply replaced wxButton with wxGenButton. For some reason, after my first click on the button, the window grabs the focus and I can no longer get to any other windows. I can't even get to my test window's frame to close it. I can't even get to gnome's process monitor to kill it. Thank heavens for linux's virtual terminals - thats the only way I can kill it.

Looks like I just need to study the genButton a little more before just hacking away at it like that.

4) A small thing:

You don't need to use wxPoint and wxSize objects most of the time, you
can usually just pass in a tuple.:

button = LockButton( panel, id=50, pos=(20, 50), size = (60,20) )

Another good pointer. I kind of noticed that with wxColor. A simple tuple seems to work as well as calling a wxColor. I had just never taken the time to sort out what is really required in each case.

Here is my solution so far. I decided to use a wxBitmapButton rather than wxGenButton. If I study the source I could probably figure out how to make it work. However, the main problem with simply drawing to the button is that what ever you draw gets erased if the button's portion of the window gets redrawn. I knew this all along; but I was just trying to get the basic method down, then maybe use a buffered dc instead of just drawing to the button (as in the wxScrolledWindow demo).

After thinking about it a bit, I figured out how to use a BitmapButton to do what I need.

···

#-----------------------------------------------------------------------------------------------

from wxPython.wx import *
from random import randint

class BitmapLockButton( wxBitmapButton ):
    def __init__( self, parent, size=(80,20), color=(0,0,0),
                  *args, **kwargs ):
        w,h = size
        bw = w - 3; bh = h - 3
        self.lockbmp = wxEmptyBitmap( bw,bh )
        self.unlockbmp = wxEmptyBitmap( bw,bh )
        self.drawbmps( color )
        kwargs['bitmap'] = self.unlockbmp
        kwargs['size'] = (w,h)
        self.state = 1
               wxBitmapButton.__init__( self, parent, *args, **kwargs )
        EVT_LEFT_UP( self, self.OnLeftClick )
        EVT_RIGHT_UP( self, self.OnRightClick )
       def drawbmps( self, color ):
        w = self.lockbmp.GetWidth(); h = self.lockbmp.GetHeight()
        r,g,b = color
        if (r + g + b)/3 < 50: pcolor = (255,255,255)
        else: pcolor = (0,0,0)
        # unlockbmp
        dc = wxMemoryDC()
        dc.SelectObject( self.unlockbmp )
        dc.SetBackground( wxBrush( color ) )
        dc.Clear()
        dc.EndDrawing()
        # lockbmp
        dc = wxMemoryDC()
        dc.SelectObject( self.lockbmp )
        dc.SetBackground( wxBrush( color ) )
        dc.Clear()
        dc.BeginDrawing()
        dc.SetPen( wxPen( pcolor, 3 ) )
        dc.DrawLine( 0,0, w,h )
        dc.DrawLine( w,0, 0,h )
        dc.EndDrawing()
           def setColor( self, color ):
        #~ print 'setColor( %s )' % (color,)
        self.drawbmps( color )
        self.SetBitmapFocus( self.unlockbmp )
        self.SetBitmapLabel( self.unlockbmp )
        self.SetBitmapSelected( self.unlockbmp )
           def OnLeftClick(self, event):
        if self.state:
            print 'left click'
            event.Skip()
           def OnRightClick(self, event): # lock
        if self.state == 1:
            print 'lock'
            self.state = 0
            self.SetBitmapFocus( self.lockbmp )
            self.SetBitmapLabel( self.lockbmp )
            self.SetBitmapSelected( self.lockbmp )
        else:
            print 'unlock'
            self.state = 1
            self.SetBitmapFocus( self.unlockbmp )
            self.SetBitmapLabel( self.unlockbmp )
            self.SetBitmapSelected( self.unlockbmp )
       if __name__ == '__main__':
    class TestFrame(wxFrame):
        def __init__(self, parent):
            wxFrame.__init__(self, parent, -1, "Lock Button Test",
                size=wxSize( 300, 200) )
            self.state = 1
                       panel = wxPanel( self, -1 )
                       self.button = BitmapLockButton( panel, id=30,
                color=(0,0,255), pos=wxPoint(160, 20) )
            EVT_BUTTON(self, 30, self.OnClick)
                   def OnClick(self, event):
            #~ if self.state:
                #~ print "Click! (%d)\n" % event.GetId()
            print "Click! (%d)" % event.GetId()
            self.button.setColor( (randint( 0,255 ),
                                    randint( 0,255 ),
                                    randint( 0,255 ) ) )
        def OnRightClick(self, event):
            print 'right click'
               class App(wxApp):
        def __init__( self ):
            #~ print 'App.__init__()'
            wxApp.__init__(self, 0)
           def OnInit( self ):
            #~ print 'App.OnInit()'
            frame = TestFrame( None )
            frame.Show(True)
                       self.SetTopWindow(frame)
            self.frame = frame
                       return True
               app = App()
    app.MainLoop()

Chris Barker wrote:

I see the same problem, and I can't tell you why, but I have a couple of
thoughts:

1) YOu may not be abel to subclass a wxButton and draw on it this way.
I'm pretty sure wxButtons are wrappers around native buttons, so you
might not have full control of how they are drawn.

On wxGTK the wxButton control (and other controls too) are not real X-windows but just gadgets that are drawn on directly on their parent window, so the client DC you are getting for the button is really for the parent. For reasons like this changing the low level behaviours of the native controls like this sometimes works, but is not supported nor guaranteed to work on any platform.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!