You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyCharm中wxPython GUI调试运行与单步执行行为差异问题

调试模式与单步执行行为不一致问题

问题背景

以下是精简后的代码,在PyCharm调试模式运行时与单步调试时行为不一致。

环境信息

  • Windows 10
  • PyCharm: 2022.2.4
  • Python: 3.10.8
  • wxPython库: 4.2.0

代码片段

import wx


class AddBoard(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent)

        self.archive_board_cost = 0 
        self.archive_board_width, self.archive_board_height = [0, 0] 

        self.InitUI()
        self.onRadioButton()

        self.Bind(wx.EVT_RADIOBUTTON, self.onRadioButton, self.archive_radial)
        self.Bind(wx.EVT_RADIOBUTTON, self.onRadioButton, self.new_radial)

        self.Bind(wx.EVT_BUTTON, self.onOK, self.ok_button)

    def InitUI(self):
        frame_box = wx.BoxSizer(wx.VERTICAL)
        main_panel_box = wx.BoxSizer(wx.VERTICAL)
        labels_box = wx.BoxSizer(wx.HORIZONTAL)
        archive_box = wx.BoxSizer(wx.HORIZONTAL)
        new_box = wx.BoxSizer(wx.HORIZONTAL)

        self.main_panel = wx.Panel(self)

        self.label_width = wx.StaticText(self.main_panel, label='width', size=wx.Size(60, -1))
        self.label_height = wx.StaticText(self.main_panel, label='height', size=wx.Size(60, -1))
        self.label_cost = wx.StaticText(self.main_panel, label='cost', size=wx.Size(60, -1))

        labels_box.Add((66, 0), 0, wx.EXPAND, 0)
        labels_box.Add(self.label_width, 0, wx.ALL, 3)
        labels_box.Add(self.label_height, 0, wx.ALL, 3)
        labels_box.Add(self.label_cost, 0, wx.ALL, 3)

        self.archive_radial = wx.RadioButton(self.main_panel, label='archive', size=wx.Size(60, -1))
        self.archive_radial.SetValue(True)
        self.archive_width = wx.StaticText(self.main_panel, label=str(self.archive_board_width), size=wx.Size(60, -1))
        self.archive_height = wx.StaticText(self.main_panel, label=str(self.archive_board_height), size=wx.Size(60, -1))
        self.archive_cost = wx.StaticText(self.main_panel, label=str(self.archive_board_cost), size=wx.Size(60, -1))

        archive_box.Add(self.archive_radial, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        archive_box.Add(self.archive_width, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        archive_box.Add(self.archive_height, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        archive_box.Add(self.archive_cost, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)

        self.new_radial = wx.RadioButton(self.main_panel, label='new', size=wx.Size(60, -1))
        self.new_radial.SetValue(False)
        self.new_width = wx.TextCtrl(self.main_panel, value='0', size=wx.Size(60, -1),
                                     validator=IntValidator())
        self.new_height = wx.TextCtrl(self.main_panel, value='0', size=wx.Size(60, -1),
                                      validator=IntValidator())
        self.new_cost = wx.TextCtrl(self.main_panel, value='0', size=wx.Size(60, -1),
                                    validator=FloatValidator())

        new_box.Add(self.new_radial, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        new_box.Add(self.new_width, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        new_box.Add(self.new_height, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)
        new_box.Add(self.new_cost, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 3)

        self.standard_buttons = wx.StdDialogButtonSizer()
        self.ok_button = wx.Button(self.main_panel, wx.ID_OK)
        self.cancel_button = wx.Button(self.main_panel, wx.ID_CANCEL)
        self.standard_buttons.AddButton(self.ok_button)
        self.standard_buttons.AddButton(self.cancel_button)
        self.standard_buttons.Realize()

        main_panel_box.Add(labels_box, 0, wx.EXPAND, 0)
        main_panel_box.Add(archive_box, 0, wx.EXPAND, 0)
        main_panel_box.Add(new_box, 0, wx.EXPAND, 0)
        main_panel_box.Add((0, 20), 0, wx.EXPAND, 0)
        main_panel_box.Add(self.standard_buttons, 0, wx.ALL | wx.EXPAND, 3)

        self.main_panel.SetSizer(main_panel_box)
        self.main_panel.Layout()

        frame_box.Add(self.main_panel, 0, wx.ALL | wx.EXPAND, 5)

        self.SetSizer(frame_box)
        self.Fit()

    def onRadioButton(self, evt=None):
        self.DisableText()
        if self.archive_radial.GetValue():
            self.archive_width.Enable()
            self.archive_height.Enable()
            self.archive_cost.Enable()
        else:
            self.new_width.Enable()
            self.new_height.Enable()
            self.new_cost.Enable()

        self.CheckAllValues()
        self.Layout()

    def DisableText(self):
        self.archive_width.Enable(False)
        self.archive_height.Enable(False)
        self.archive_cost.Enable(False)

        self.new_width.Enable(False)
        self.new_height.Enable(False)
        self.new_cost.Enable(False)

    def CheckArchiveValues(self):
        colour = (255, 193, 193, 255)
        flag = True
        if self.archive_board_width <= 0:
            self.archive_width.SetBackgroundColour(colour)
            flag = False
        if self.archive_board_height <= 0:
            self.archive_height.SetBackgroundColour(colour)
            flag = False
        if self.archive_board_cost <= 0:
            self.archive_cost.SetBackgroundColour(colour)
            flag = False
        return flag

    def CheckAllValues(self):
        # refresh relevant values
        self.ClearChecks()
        if self.archive_radial.GetValue():
            return self.CheckArchiveValues()
        else:
            return all([self.new_width.Validate(), self.new_height.Validate(), self.new_cost.Validate()])

    def onOK(self, evt=None):
        if not self.CheckAllValues():
            self.Layout()
            return
        self.EndModal(wx.ID_OK)

    def ClearChecks(self):
        self.ClearWidget(self.archive_width)
        self.ClearWidget(self.archive_height)
        self.ClearWidget(self.archive_cost)
        self.ClearWidget(self.new_width)
        self.ClearWidget(self.new_height)
        self.ClearWidget(self.new_cost)
        self.Layout()

    @staticmethod
    def ClearWidget(widget):
        colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
        state = widget.IsEnabled()
        widget.Enable(True)
        widget.SetBackgroundColour(colour)
        widget.Enable(state)


class IntValidator(wx.Validator):
    def __init__(self):
        wx.Validator.__init__(self)

    def Clone(self):
        return IntValidator()

    def Validate(self, win):
        ctrl = self.GetWindow()
        text = ctrl.GetValue()

        try:
            if int(text) <= 0:
                # wx.MessageBox("Positive integers only.", "Error")
                ctrl.SetBackgroundColour((255, 193, 193, 255))
                ctrl.SetFocus()
                ctrl.Refresh()
                return False

        except ValueError:
            # wx.MessageBox("Positive integers only.", "Error")
            ctrl.SetBackgroundColour((255, 193, 193, 255))
            ctrl.SetFocus()
            ctrl.Refresh()
            return False

        ctrl.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
        ctrl.Refresh()
        return True

    def TransferToWindow(self):
        return True  # Not used

    def TransferFromWindow(self):
        return True  # Not used


class FloatValidator(wx.Validator):
    def __init__(self):
        wx.Validator.__init__(self)

    def Clone(self):
        return FloatValidator()

    def Validate(self, win):
        ctrl = self.GetWindow()
        text = ctrl.GetValue()

        try:
            if float(text) <= 0:
                # wx.MessageBox("Positive numbers only.", "Error")
                ctrl.SetBackgroundColour((255, 193, 193, 255))
                ctrl.SetFocus()
                ctrl.Refresh()
                return False

        except ValueError:
            # wx.MessageBox("Positive numbers only.", "Error")
            ctrl.SetBackgroundColour((255, 193, 193, 255))
            ctrl.SetFocus()
            ctrl.Refresh()
            return False

        ctrl.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
        ctrl.Refresh()
        return True

    def TransferToWindow(self):
        return True  # Not used

    def TransferFromWindow(self):
        return True  # Not used


def main():
    app = MainApp()
    app.MainLoop()


class MainWindow(wx.Frame):
    def __init__(self, parent):
        super(MainWindow, self).__init__(parent)
        dialog = AddBoard(self)
        if dialog.ShowModal() == wx.ID_OK:
            pass


class MainApp(wx.App):
    def __init__(self):
        wx.App.__init__(self)
        main_window = MainWindow(None)
        main_window.Show()


if __name__ == '__main__':
    main()

调试模式运行时行为

预期行为:选中单选按钮对应的行应触发验证失败(因值为0),另一行恢复正常状态。
实际行为:

  • 启动时'archive'行显示红色(正确);
  • 切换到'new'行时,'archive'行恢复正常,'new'行变红(正确);
  • 切回'archive'行时,'new'行恢复正常,但'archive'行未变回红色(错误)。

单步调试时行为

在onRadioButton方法第一行(第84行self.DisableText())设置断点,单步执行时符合预期行为。

疑问

首次遇到调试运行与单步执行的行为差异,按逻辑两者应完全一致。请确认该问题是否在其他IDE或Python/wxPython版本中存在,若能避免打包成EXE测试则更佳。


内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 02:02:31