Mridang Agarwalla
Mridang Agarwalla

Reputation: 44978

Return value from wxPython Frame

Could someone show me how I could return a value from a wxPython Frame? When the use clicks close, I popup a message dialog asking him a question. I would like to return the return code of this message dialog to my calling function.

Thanks

Upvotes: 8

Views: 8073

Answers (8)

David L Ernstrom
David L Ernstrom

Reputation: 459

Because the wxFrame has events that process via the app.MainLoop() functionality, the only way to get at the return value of a wx.Frame() is via catching an event.

The standard practice of handling events is typically from within the class which derives from wx.Window itself (e.g., Frame, Panel, etc.). Since you want code exterior to the wx.Frame to receive information that was gathered upon processing the OnClose() event, then the best way to do that is to register an event handler for your frame.

The documentation for wx.Window::PushEventHandler is probably the best resource and even the wxpython wiki has a great article on how to do this. Within the article, they register a custom handler which is an instance of "MouseDownTracker." Rather than instantiating within the PushEventHandler call, you'd want to instantiate it prior to the call so that you can retain a handle to the EventHandler derived class. That way, you can check on your derived EventHandler class-variables after the Frame has been destroyed, or even allow that derived class to do special things for you.

Here is an adaptation of that code from the wx python wiki (admittedly a little convoluted due to the requirement of handling the results of a custom event with a "calling" function):

import sys
import wx
import wx.lib.newevent
    
(MyCustomEvent, EVT_CUSTOM) = wx.lib.newevent.NewEvent()
    
class CustomEventTracker(wx.EvtHandler):
    def __init__(self, log, processingCodeFunctionHandle):
        wx.EvtHandler.__init__(self)
        self.processingCodeFunctionHandle = processingCodeFunctionHandle
        self.log = log
        EVT_CUSTOM(self, self.MyCustomEventHandler)
    
    def MyCustomEventHandler(self, evt):
        self.log.write(evt.resultOfDialog + '\n')
        self.processingCodeFunctionHandle(evt.resultOfDialog)
        evt.Skip()
    
class MyPanel2(wx.Panel):
    def __init__(self, parent, log):
        wx.Panel.__init__(self, parent)
        self.log = log
    
    def OnResults(self, resultData):
        self.log.write("Result data gathered: %s" % resultData)
    
class MyFrame(wx.Frame):
    def __init__(self, parent, ID=-1, title="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
        wx.Frame.__init__(self, parent, ID, title, pos, size, style)
        self.panel = panel = wx.Panel(self, -1, style=wx.TAB_TRAVERSAL | wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add((25, 25))
    
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add((25,1))
        m_close = wx.Button(self.panel, wx.ID_CLOSE, "Close")
        m_close.Bind(wx.EVT_BUTTON, self.OnClose)
        row.Add(m_close, 0, wx.ALL, 10)
        sizer.Add(row)
        self.panel.SetSizer(sizer)
    
    def OnClose(self, evt):
        dlg = wx.MessageDialog(self, "Do you really want to close this frame?", "Confirm Exit", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
        result = dlg.ShowModal()
        dlg.Destroy()
        if result == wx.ID_CANCEL:
            event = MyCustomEvent(resultOfDialog="User Clicked CANCEL")
            self.GetEventHandler().ProcessEvent(event)
        else: # result == wx.ID_OK
            event = MyCustomEvent(resultOfDialog="User Clicked OK")
            self.GetEventHandler().ProcessEvent(event)
        self.Destroy()
    
app = wx.App(False)
f2 = wx.Frame(None, title="Frame 1 (for feedback)", size=(400, 350))
p2 = MyPanel2(f2, sys.stdout)
f2.Show()
eventTrackerHandle = CustomEventTracker(sys.stdout, p2.OnResults)
f1 = MyFrame(None, title="PushEventHandler Tester (deals with on close event)", size=(400, 350))
f1.PushEventHandler(eventTrackerHandle)
f1.Show()
app.MainLoop()

Upvotes: 5

Zack
Zack

Reputation: 4375

A few years late for the initial question, but when looking for the answer to this question myself, I stumbled upon a built-in method of getting a return value from a modal without messing with any custom event funniness. Figured I'd post here in case anyone else needs it.

It's simply this guy right here:

wxDialog::EndModal void EndModal(int retCode)

Ends a modal dialog, passing a value to be returned from the *wxDialog::ShowModal invocation.*

Using the above, you can return whatever you want from the Dialog.

An example usage would be subclassing a wx.Dialog, and then placing the EndModal function in the button handlers.

class ProjectSettingsDialog(wx.Dialog):
    def __init__(self):
        wx.Dialog.__init__(self, None, -1, "Project Settings", size=(600,400))

        sizer = wx.BoxSizer(wx.VERTICAL) #main sized
        sizer.AddStretchSpacer(1) 

        msg = wx.StaticText(self, -1, label="This is a sample message")
        sizer.Add(msg, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 15)


        horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(self, -1, 'OK')
        self.Bind(wx.EVT_BUTTON, self.OnOK, okButton)

        cancelBtn = wx.Button(self, -1, "Cancel")
        self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelBtn)

        horizontal_sizer.Add(okButton, 0, wx.ALIGN_LEFT)
        horizontal_sizer.AddStretchSpacer(1)
        horizontal_sizer.Add(cancelBtn, 0, wx.ALIGN_RIGHT)

        sizer.Add(horizontal_sizer, 0)

        sizer.AddStretchSpacer(1)
            self.SetSizer(sizer)

    def OnOK(self, event):
        self.EndModal(wx.ID_OK) #returns numeric code to caller
        self.Destroy()


    def OnCancel(self, event):
        self.EndModal(wx.ID_CANCEL) #returns numeric code to caller
        self.Destroy()

(Note: I just banged this code out quickly; didn't test the sizers)

As you can see, all you need to do is call the EndModal from a button event to return a value to whatever spawned the dialog.

Upvotes: 3

Jacob Wang
Jacob Wang

Reputation: 75

user1594322's answer works but it requires you to put all of your controls in your wx.App, instead of wx.Frame. This will make recycling the code harder.

My solution involves define a "PassBack" variable when defining your init function. (similar to "parent" variable, but it is normally used already when initiating a wx.Frame)

From my code:

class MyApp(wx.App):
    def __init__ (self, parent=None, size=(500,700)):
        wx.App.__init__(self, False)
        self.frame = MyFrame(parent, -1, passBack=self) #Pass this app in
        self.outputFromFrame = "" #The output from my frame


    def getOutput(self):
        self.frame.Show()
        self.MainLoop()
        return self.outputFromFrame

and for the frame class:

class MyFrame(wx.Frame):
    def __init__(self, parent, ID, passBack, title="My Frame"):
        wx.Frame.__init__(self, parent, ID, title, size=(500, 700))
        self.passBack = passBack #this will be used to pass back variables/objects

and somewhere during the execution of MyFrame

self.passBack.outputFromFrame = "Hello"

so all in all, to get a string from an application

app = MyApp()
val = app.getOutput()
#Proceed to do something with val

Upvotes: 1

user1594322
user1594322

Reputation: 2048

I wanted to do the same thing, to have a graphical "picker" that I could run from within a console app. Here's how I did it.

# Fruit.py

import wx

class Picker (wx.App):
    def __init__ (self, title, parent=None, size=(400,300)):
        wx.App.__init__(self, False)
        self.frame = wx.Frame(parent, title=title, size=size)

        self.apple_button = wx.Button(self.frame, -1, "Apple", (0,0))
        self.apple_button.Bind(wx.EVT_BUTTON, self.apple_button_click)

        self.orange_button = wx.Button(self.frame, -1, "Orange", (0,100))
        self.orange_button.Bind(wx.EVT_BUTTON, self.orange_button_click)

        self.fruit = None
        self.frame.Show(True)

    def apple_button_click (self, event):
        self.fruit = 'apple'
        self.frame.Destroy()

    def orange_button_click (self, event):
        self.fruit = 'orange'
        self.frame.Destroy()

    def pick (self):
        self.MainLoop()
        return self.fruit

Then from a console app, I would run this code.

# Usage.py

import Fruit

picker = Fruit.Picker('Pick a Fruit')
fruit = picker.pick()
print 'User picked %s' % fruit

Upvotes: 1

Kevin
Kevin

Reputation: 128

I think I just had the same problem as you. Instead of making that popup a frame, I made it a dialog instead. I made a custom dialog by inheriting a wx.dialog instead of a wx.frame. Then you can utilize the code that joaquin posted above. You check the return value of the dialog to see what was entered. This can be done by storing the value of the textctrl when the user clicks ok into a local variable. Then before it's destroyed, you get that value somehow.

The custom dialog section of this site helped me out greatly. http://zetcode.com/wxpython/dialogs/

Upvotes: 0

minou
minou

Reputation: 16563

I don't think a wxFrame can return a value since it is not modal. If you don't need to use a wxFrame, then a modal dialog could work for you. If you really need a frame, I'd consider using a custom event.

It would go something like this: (1) User clicks to close the wxFrame (2) You override OnClose (or something like that) to pop up a dialog to ask the user a question (3) Create and post the custom event (4) Close the wxFrame (5) Some other code processes your custom event

Upvotes: 0

joaquin
joaquin

Reputation: 85603

You can get the result of clicking the OK, CANCEL buttons from the Dialog ShowModal method.
Given dialog is an instance of one of the wxPython Dialog classes:

result = dialog.ShowModal()
if result == wx.ID_OK:
    print "OK"
else:
    print "Cancel"
dialog.Destroy()

Upvotes: 2

F. P.
F. P.

Reputation: 5086

Check this answer on comp.lang.python: Linkie

Upvotes: 0

Related Questions