How to write an event handler for the buttons or what-widgets-ever of wx.FileDialog?

Hello.

I have a question around wx.FileDialog.

I googled a short example, or how to use wx.FileDialog, like this.

#!/usr/bin/env python3

import wx

def onButton(event):
    print("Button pressed.")

app = wx.App()

frame = wx.Frame(None, -1, 'win.py')
frame.SetSize(0,0,200,50)

# Create open file dialog
openFileDialog = wx.FileDialog(frame, "Open", "", "", 
      "Python files (*.py)|*.py", 
       wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

openFileDialog.ShowModal()
print(openFileDialog.GetPath())
openFileDialog.Destroy()

As you know, onButton here means an event handler, however no binds to any buttons wx.FileDialog automatically creates here; in other words, this event handler is useless.
To bind a button to an event handler, we have to know the “Name” or the variable of buttons stated on the source code; however, we do not know the name of “widgets” that wx.FileDialog makes.
I have checked the statements such as:

  • wx.FileDialog
  • wx.FileDialogCustomize
  • wx.FileDialogCustomizeHook
  • wx.FIleDialogButton

,nonetheless, nothing is stated.
What I would like to know especially is wx.FD_OPEN or wx.FD_SAVE. They make typical pop-ups an OS make, but I do not know what kind of name of buttons or textctrls for event handlers.
Is there any resources stating around it, or could anybody give some examples?

Thanks.

The normal way of using the wx.FileDialog is to check the value returned by the ShowModal() method. This will be an ID value. It will be set to wx.ID_OK if the Open button was pressed, or wx.ID_CANCEL if the Cancel button or the ‘x’ button on the frame’s decoration was pressed, or the Esc key was pressed.

Modifying your example:

import wx

app = wx.App()

frame = wx.Frame(None, -1, 'win.py')
frame.SetSize(0,0,200,50)

# Create open file dialog
openFileDialog = wx.FileDialog(frame, "Open", "", "",
      "Python files (*.py)|*.py",
       wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)

result = openFileDialog.ShowModal()

if result == wx.ID_OK:
    print("OK")
elif result == wx.ID_CANCEL:
    print("Cancel")

print(openFileDialog.GetPath())

openFileDialog.Destroy()

See also: wx.FileDialog — wxPython Phoenix 4.2.0 documentation

Hello, Mr. RichardT, and thanks for your replying.

O.K., wx.FileDialog.ShowModal() itself returns a value.
What I am interested now is something like this.
Usually, for instance, file dialogs are activated by pull-down menus.
With wxGlade, those functions would be relatively easily made.
wxGlade automatically , for example, creates a Python file like this:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 1.0.4 on Wed Apr 12 17:29:10 2023
#

import wx

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
# end wxGlade


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("frame")

        # Menu Bar
        self.frame_menubar = wx.MenuBar()
        wxglade_tmp_menu = wx.Menu()
        item = wxglade_tmp_menu.Append(wx.ID_ANY, "item", "")
        self.Bind(wx.EVT_MENU, lambda evt: MyDialog(self).ShowModal(), item)
        self.frame_menubar.Append(wxglade_tmp_menu, "item")
        self.SetMenuBar(self.frame_menubar)
        # Menu Bar end

        self.panel_1 = wx.Panel(self, wx.ID_ANY)

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        sizer_1.Add((0, 0), 0, 0, 0)

        self.panel_1.SetSizer(sizer_1)

        self.Layout()

        # end wxGlade

# end of class MyFrame

class MyDialog(wx.Dialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyDialog.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE
        wx.Dialog.__init__(self, *args, **kwds)
        self.SetTitle("dialog")
        self.Layout()
        # end wxGlade

# end of class MyDialog

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

# end of class MyApp

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

Replacing wx.Dialog with wx.FileDialog is O.K. Replacing wx.DEFAULT_DIALOG_STYLE with wx.FD_SAVE or what-so-ever prepared is O.K.
The Menu Bar’s event handler would be something like this:

lambda evt: onMyDialog(self)

and this would activate the FileDialog.
The Event Handler as a function here is like this:

def onMyDialog(self):
    with MyDialog(self) as fileDialog:
        if fileDialog.ShowModal() == wx.ID_CANCEL:
            return
        elif fileDialog.ShowModal() == wx.ID_OK:
            print('OK')

Then… Yes, I am sort of stuck here, showing the pop-up twice!

Do I misunderstand anything here?

Thanks.

In your onMyDialog() method, the dialog’s ShowModal() method will be called twice if the user doesn’t click on the Cancel button.

The possible values returned by ShowModal() are wx.ID_CANCEL and wx.ID_OK.

If the returned value is wx.ID_CANCEL, the event handler can just return without doing anything.

Otherwise the code can assume the returned value was wx.ID_OK and it can then get the pathname and do the appropriate action with the selected file.

    def onMyDialog(self):
        with wx.FileDialog(self, "Open", "", "",
                           "Python files (*.py)|*.py",
                           wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return

            pathname = fileDialog.GetPath()
            print(pathname)

Thanks, Mr. RichardT.
With your help, I barely complete an MVC GUI style experiment(I actually did not know how to send messages from wx.FileDialog by using PyPubSub).

CLI Style(Read-Eval-Print Loop):

#!/usr/bin/env python3

import os, pickle, re, sys

class Content(object):
    def __init__(self, title, artist, rating, ripped):
        self.title = title
        self.artist = artist
        self.rating = rating
        self.ripped = ripped
    def __repr__(self):
        return f'Title:  {self.title}\nArtist: {self.artist}\nRating: {self.rating}\nRipped: {self.ripped}\n'

class Env(object):
    def __init__(self, package, ls = [], fname="", flag = False):
        self.package = package
        self.ls = ls
        self.fname = fname
        self.flag = flag
    def __repr__(self):
        return f'<package: {self.package} list: {self.ls} filename: {self.fname} flag: {self.flag}>'

def install_package():
    def insert_value(arg, env):
        return Env(env.package, ls = [Content(*arg)] + env.ls, fname = env.fname)
    def select(arg, env):
        return Env(env.package, ls = env.ls, fname = env.fname, flag = True)
    def new_file(arg, env):
        with open(''.join(arg) + ".dat", "wb") as f:
            pass
        return Env(env.package, ls = env.ls, fname = arg)
    def open_file(arg, env):
        fname = ''.join(arg)
        with open(fname, "rb") as f:
            ls = pickle.load(f)
        return Env(env.package, ls = ls, fname = fname)
    def save_file(arg, env):
        if env.fname == '':
            return save_file_as('tmp.dat', env)
        else:
            os.rename(env.fname, env.fname + '.bak')
            with open(env.fname, "wb") as f:
                pickle.dump(env.ls, f)
            return Env(env.package, ls = env.ls, fname = env.fname)
    def save_file_as(arg, env):
        fname = ''.join(arg)
        with open(fname, "wb") as f:
            pickle.dump(env.ls, f)
        return Env(env.package, ls = env.ls, fname = fname)
    def delete(arg, env):
        return Env(env.package,
                   ls = [i for i in env.ls if i != env.ls[int(arg[0])]],
                   fname = env.fname)
    return {'insert_value': insert_value,
            'select': select,
            'new_file': new_file,
            'open_file': open_file,
            'save_file': save_file,
            'save_file_as': save_file_as,
            'quit': lambda arg, env: sys.exit(),
            'delete': delete}

# Eval
def interp(x, env):
    x = [i.strip() for i in re.split("\(|,|、|\)", x) if i != ""]
    return env.package[x[0]](x[1:], env)

# Print
def display(env):
    if env.flag:
        [print(i) for i in env.ls]
    # print(env) # デバッグ用
    return env

if __name__ == '__main__':
    env = display(Env(install_package()))
    while True:
        try:
            env = display(interp(input("simple database> "), env))
        except KeyError:
            env
        except ValueError:
            env
        except IndexError:
            env

GUI Style(MVC):

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 1.0.4 on Wed Apr 12 23:57:09 2023
#

import wx

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
from pubsub import pub
from simple_database import Env, install_package, interp
import sys

class Controller(object):
    def __init__(self):
        pub.subscribe(self.read, "Controller")
        self.table = {"OnNewFile": "new_file({})",
                      "OnOpen": "open_file({})",
                      "OnSaveFile": "save_file",
                      "OnSaveFileAs": "save_file_as({})",
                      "OnQuit": "quit",
                      "OnAddCD": "insert_value({})"}
    def read(self, x, env):
        pub.sendMessage("Model.eval",
                        x = self.table[x[0]].format(", ".join(x[1:])),
                        env = env)

class Model(object):
    def __init__(self):
        pub.subscribe(self.eval, "Model")
    def eval(self, x, env):
        pub.sendMessage("View.SetProperties", env = interp(x, env))

def OnNewFile(self):
    with NewFile(self) as fileDialog:
        if fileDialog.ShowModal() == wx.ID_CANCEL:
            pass
        pub.sendMessage("Controller.read",
                        x = ["OnNewFile", fileDialog.GetPath()],
                        env = self.env)

def OnOpen(self):
    with OpenFile(self) as fileDialog:
        if fileDialog.ShowModal() == wx.ID_CANCEL:
            pass
        pub.sendMessage("Controller.read",
                        x = ["OnOpen", fileDialog.GetPath()],
                        env = self.env)

def OnSaveFileAs(self):
    with SaveFileAs(self) as fileDialog:
        if fileDialog.ShowModal() == wx.ID_CANCEL:
            pass
        pub.sendMessage("Controller.read",
                        x = ["OnSaveFileAs", fileDialog.GetPath()],
                        env = self.env)
# end wxGlade


class View(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: View.__init__
        self.env = Env(install_package())
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("Simple Database")

        # Menu Bar
        self.frame_menubar = wx.MenuBar()
        wxglade_tmp_menu = wx.Menu()
        item = wxglade_tmp_menu.Append(wx.ID_ANY, u"新規作成\tCtrl+N", "")
        self.Bind(wx.EVT_MENU, lambda event: OnNewFile(self), item)
        item = wxglade_tmp_menu.Append(wx.ID_ANY, u"開く...\tCtrl+O", "")
        self.Bind(wx.EVT_MENU, lambda event: OnOpen(self), item)
        item = wxglade_tmp_menu.Append(wx.ID_ANY, u"保存\tCtrl+S", "")
        self.Bind(wx.EVT_MENU, lambda event: pub.sendMessage('Controller.read', x = ['OnSaveFile'], env = self.env), item)
        item = wxglade_tmp_menu.Append(wx.ID_ANY, u"名前を付けて保存...\tShift+Ctrl+S", "")
        self.Bind(wx.EVT_MENU, lambda event: OnSaveFileAs(self), item)
        item = wxglade_tmp_menu.Append(wx.ID_ANY, "Quit\tCtrl+Q", "")
        self.Bind(wx.EVT_MENU, lambda event: pub.sendMessage('Controller.read', 'OnQuit'), item)
        self.frame_menubar.Append(wxglade_tmp_menu, u"ファイル")
        self.SetMenuBar(self.frame_menubar)
        # Menu Bar end

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        self.AddCD = wx.Button(self, wx.ID_ANY, "AddCD")
        sizer_1.Add(self.AddCD, 0, 0, 0)

        self.list_ctrl_1 = wx.ListCtrl(self, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        self.list_ctrl_1.AppendColumn(u"タイトル", format=wx.LIST_FORMAT_LEFT, width=-1)
        self.list_ctrl_1.AppendColumn(u"アーティスト", format=wx.LIST_FORMAT_LEFT, width=-1)
        self.list_ctrl_1.AppendColumn(u"レーティング", format=wx.LIST_FORMAT_LEFT, width=-1)
        self.list_ctrl_1.AppendColumn(u"リップ", format=wx.LIST_FORMAT_LEFT, width=-1)
        sizer_1.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_1)

        self.Layout()

        self.Bind(wx.EVT_BUTTON, lambda event: ControllerDialog(self).ShowModal(), self.AddCD)
        # end wxGlade
        pub.subscribe(self.SetProperties, 'View')
        
    def SetProperties(self, env):
        self.env = env
        self.list_ctrl_1.DeleteAllItems()
        [[self.list_ctrl_1.InsertItem(sys.maxsize, obj) if col == 0
          else self.list_ctrl_1.SetItem(index, col, obj) for col, obj
          in enumerate([item.title, item.artist, item.rating, item.ripped])]
          for index, item in enumerate(self.env.ls)]

# end of class View

class ControllerDialog(wx.Dialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: ControllerDialog.__init__
        self.env = args[0].env
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE
        wx.Dialog.__init__(self, *args, **kwds)
        self.SetTitle("Add CD")

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        grid_sizer_1 = wx.GridSizer(4, 2, 0, 0)
        sizer_1.Add(grid_sizer_1, 1, wx.EXPAND, 0)

        Title = wx.StaticText(self, wx.ID_ANY, u"タイトル")
        grid_sizer_1.Add(Title, 0, wx.ALIGN_CENTER, 0)

        self.text_ctrl_1 = wx.TextCtrl(self, wx.ID_ANY, "")
        grid_sizer_1.Add(self.text_ctrl_1, 0, wx.ALIGN_CENTER, 0)

        Artist = wx.StaticText(self, wx.ID_ANY, u"アーティスト")
        grid_sizer_1.Add(Artist, 0, wx.ALIGN_CENTER, 0)

        self.text_ctrl_2 = wx.TextCtrl(self, wx.ID_ANY, "")
        grid_sizer_1.Add(self.text_ctrl_2, 0, wx.ALIGN_CENTER, 0)

        Rating = wx.StaticText(self, wx.ID_ANY, u"レーティング")
        grid_sizer_1.Add(Rating, 0, wx.ALIGN_CENTER, 0)

        self.text_ctrl_3 = wx.TextCtrl(self, wx.ID_ANY, "")
        grid_sizer_1.Add(self.text_ctrl_3, 0, wx.ALIGN_CENTER, 0)

        Ripped = wx.StaticText(self, wx.ID_ANY, u"リップ")
        grid_sizer_1.Add(Ripped, 0, wx.ALIGN_CENTER, 0)

        self.text_ctrl_4 = wx.TextCtrl(self, wx.ID_ANY, "")
        grid_sizer_1.Add(self.text_ctrl_4, 0, wx.ALIGN_CENTER, 0)

        sizer_2 = wx.StdDialogButtonSizer()
        sizer_1.Add(sizer_2, 0, wx.ALIGN_RIGHT | wx.ALL, 4)

        self.button_OK = wx.Button(self, wx.ID_OK, "")
        self.button_OK.SetDefault()
        sizer_2.AddButton(self.button_OK)

        self.button_CANCEL = wx.Button(self, wx.ID_CANCEL, "")
        sizer_2.AddButton(self.button_CANCEL)

        sizer_2.Realize()

        self.SetSizer(sizer_1)
        sizer_1.Fit(self)

        self.SetAffirmativeId(self.button_OK.GetId())
        self.SetEscapeId(self.button_CANCEL.GetId())

        self.Layout()

        self.Bind(wx.EVT_BUTTON, lambda event: self.OnAddCD(), self.button_OK)
        self.Bind(wx.EVT_BUTTON, lambda event: self.Destroy(), self.button_CANCEL)
        # end wxGlade
        
    def OnAddCD(self):
        pub.sendMessage('Controller.read',
                        x = ['OnAddCD',
                             self.text_ctrl_1.GetLineText(0),
                             self.text_ctrl_2.GetLineText(0),
                             self.text_ctrl_3.GetLineText(0),
                             self.text_ctrl_4.GetLineText(0)],
                        env = self.env)
        self.Destroy()

# end of class ControllerDialog

class NewFile(wx.FileDialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: NewFile.__init__
        self.env = args[0].env
        kwds["style"] = kwds.get("style", 0) | wx.FD_SAVE
        wx.FileDialog.__init__(self, *args, **kwds)
        self.SetTitle(u"新規作成")
        self.Layout()
        # end wxGlade

# end of class NewFile

class OpenFile(wx.FileDialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: OpenFile.__init__
        self.env = args[0].env
        kwds["style"] = kwds.get("style", 0) | wx.FD_OPEN
        wx.FileDialog.__init__(self, *args, **kwds)
        self.SetTitle(u"開く...")
        self.Layout()
        # end wxGlade

# end of class OpenFile

class SaveFileAs(wx.FileDialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: SaveFileAs.__init__
        self.env = args[0].env
        kwds["style"] = kwds.get("style", 0) | wx.FD_SAVE
        wx.FileDialog.__init__(self, *args, **kwds)
        self.SetTitle("SaveFileAs")
        self.Layout()
        # end wxGlade

# end of class SaveFileAs

class SimpleDatabase(wx.App):
    def OnInit(self):
        self.View = View(None, wx.ID_ANY, "")
        self.SetTopWindow(self.View)
        self.View.Show()
        return True

# end of class SimpleDatabase

if __name__ == "__main__":
    m = Model()
    SimpleDatabase = SimpleDatabase(0)
    c = Controller()
    SimpleDatabase.MainLoop()

To be honest, to use wx.ListCtrl was much more difficult than wx.FileDialog… It took a time to figure out what is happened.
This implementation is still incomplete('cause I do not know how to do with right clicks on wx.ListCtrl); however, my basic purpose was done successfully.
Anyway, thank you very much!!