Home > Mobile >  wxPython - How to hide and show items in a centred BoxSizer without moving everything else?
wxPython - How to hide and show items in a centred BoxSizer without moving everything else?

Time:06-13

I have a button to copy text from a TextCtrl and I want to display a message next to it to confirm that the text has been copied to the clipboard. So I'm using a StaticText that hides and shows when the button is clicked.

I have put the button and the StaticText in a horizontal centred BoxSizer. The thing is when the StaticText shows, as the BoxSizer's children are centred it moves them. Here is my code:

def __init__(self, *args, **kw) -> None:
        super(Window, self).__init__(*args, **kw)

        # create a panel in the frame
        pnl = wx.Panel(self)

        # create some widgets in the panel
        self.text_area1 = wx.TextCtrl(pnl, style=wx.TE_MULTILINE | wx.TE_NOHIDESEL)
        self.text_area2 = wx.TextCtrl(pnl, style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_NOHIDESEL | wx.TE_RICH2)
        btn_correct = wx.Button(pnl, label="Correct >>>")
        btn_correct.Bind(wx.EVT_BUTTON, self.on_press_correct)
        self.btn_copy = wx.Button(pnl, label="Copy")
        self.btn_copy.Bind(wx.EVT_BUTTON, self.on_press_copy)
        self.label = wx.StaticText(pnl, label="Text copied")
        self.label.Hide()
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer, self.timer)

        # create a sizer to manage the layout of child widgets
        sizer = wx.BoxSizer(wx.VERTICAL)
        sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.text_area1, 1, wx.ALL | wx.EXPAND, 5)
        sizer.Add(btn_correct, 0, wx.ALL | wx.CENTER, 5)
        sizer.Add(self.text_area2, 1, wx.ALL | wx.EXPAND, 5)
        sub_sizer.Add(self.btn_copy, 0, wx.ALL | wx.CENTER, 5)
        sub_sizer.Add(self.label, 0, wx.ALL | wx.CENTER, 5)
        sizer.Add(self.sub_sizer, 0, wx.BOTTOM | wx.CENTER, 5)
        pnl.SetSizer(sizer)

def on_press_copy(self, event):
        data_obj = wx.TextDataObject()
        data_obj.SetText(self.text_area2.GetValue())
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(data_obj)
            wx.TheClipboard.Close()
            self.btn_copy.Disable()
            self.label.Show()
            self.label.GetParent().GetSizer().Layout()
            self.timer.StartOnce(3000)
        else:
            wx.MessageBox("Can't open clipboard", "Error")

def on_timer(self, event):
        self.btn_copy.Enable()
        self.label.Show(show=False)
        self.label.GetParent().GetSizer().Layout()

How can I achieve that without moving the button when the StaticText shows?

CodePudding user response:

I have added a dummy element (filler - size >= length of label text) and used that to the left of the button and created a sizer (label_sizer which contains the filler element) to the right. That means the button does not move when the text is shown

import wx

class MainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(None, *args, **kwargs)
        self.Title = 'Wx App'

        self.panel = MainPanel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel)
        self.SetSizer(sizer)
        self.Center()
        self.Show()


class MainPanel(wx.Panel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

        # create some widgets in the panel
        self.text_area1 = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_NOHIDESEL)
        self.text_area2 = wx.TextCtrl(self, style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_NOHIDESEL | wx.TE_RICH2)
        btn_correct = wx.Button(self, label="Correct >>>")
        btn_correct.Bind(wx.EVT_BUTTON, self.on_press_correct)
        self.btn_copy = wx.Button(self, label="Copy")
        self.btn_copy.Bind(wx.EVT_BUTTON, self.on_press_copy)

        self.label_text = "Text copied"
        filler = (100, 0)
        self.label = wx.StaticText(self, label=self.label_text)
        self.label.Hide()
        self.label_dummy = filler
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer, self.timer)

        # create a sizer to manage the layout of child widgets
        sizer = wx.BoxSizer(wx.VERTICAL)
        sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
        label_sizer = wx.BoxSizer(wx.VERTICAL)

        sizer.Add(self.text_area1, 1, wx.ALL | wx.EXPAND, 5)
        sizer.Add(btn_correct, 0, wx.ALL | wx.CENTER, 5)
        sizer.Add(400,0)

        label_sizer.Add(filler)
        label_sizer.Add(self.label)
        sizer.Add(self.text_area2, 1, wx.ALL | wx.EXPAND, 5)
        sub_sizer.Add(self.label_dummy, 0, wx.ALL | wx.CENTER, 5)
        sub_sizer.Add(self.btn_copy, 0, wx.ALL | wx.CENTER, 5)
        sub_sizer.Add(label_sizer, 0, wx.ALL | wx.CENTER, 5)
        sizer.Add(sub_sizer, 0, wx.BOTTOM | wx.CENTER, 5)
        self.SetSizer(sizer)

    def on_press_copy(self, event):
            data_obj = wx.TextDataObject()
            data_obj.SetText(self.text_area2.GetValue())
            if wx.TheClipboard.Open():
                wx.TheClipboard.SetData(data_obj)
                wx.TheClipboard.Close()
                self.btn_copy.Disable()
                self.label.Show()
                self.label.GetParent().GetSizer().Layout()
                self.timer.StartOnce(3000)
            else:
                wx.MessageBox("Can't open clipboard", "Error")

    def on_press_correct(self, event):
        print('Correct')

    def on_timer(self, event):
            self.btn_copy.Enable()
            self.label.Show(show=False)
            self.label.GetParent().GetSizer().Layout()


if __name__ == '__main__':
    wx_app = wx.App()
    MainFrame()
    wx_app.MainLoop()

CodePudding user response:

For reference, a partial code snippet, with errors, won't help get your question attention.

Obviously, centring 1 item versus 2 items, is never going to work. There will always be a difference.
You'll need to find another way.
You could output a messagebox or update the status line or use a different sizer, which probably gives you an approximation, of what you're after.
e.g.

Here I use a GridBagSizer and simply update the label text.

import wx
import time

class Application(wx.Frame):
    def __init__(self, *args, **kw) -> None:
        super(Application, self).__init__(*args, **kw)

        # create a panel in the frame
        pnl = wx.Panel(self)

        # create some widgets in the panel
        self.text_area1 = wx.TextCtrl(pnl, style=wx.TE_MULTILINE | wx.TE_NOHIDESEL)
        self.text_area2 = wx.TextCtrl(pnl, style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_NOHIDESEL | wx.TE_RICH2)
        btn_correct = wx.Button(pnl, label="Correct >>>")
        btn_correct.Bind(wx.EVT_BUTTON, self.on_press_correct)
        self.btn_copy = wx.Button(pnl, label="Copy")
        self.btn_copy.Bind(wx.EVT_BUTTON, self.on_press_copy)
        self.label = wx.StaticText(pnl, label="           ")
        #self.label.Hide()
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer, self.timer)

        # create a sizer to manage the layout of child widgets
        sizer = wx.BoxSizer(wx.VERTICAL)
        sub_sizer = wx.GridBagSizer()
        sizer.Add(self.text_area1, 1, wx.ALL | wx.EXPAND, 5)
        sizer.Add(btn_correct, 0, wx.ALL | wx.CENTER, 5)
        sizer.Add(self.text_area2, 1, wx.ALL | wx.EXPAND, 5)
        sub_sizer.Add((-1, -1), pos=(0, 0), span=(0, 3), flag=wx.EXPAND, border=5)
        sub_sizer.Add(self.btn_copy, pos=(0, 3), flag=wx.ALL | wx.EXPAND, border=5)
        sub_sizer.Add(self.label,  pos=(0, 4), flag=wx.ALIGN_CENTER, border=5)
        sizer.Add(sub_sizer, 0, wx.BOTTOM | wx.CENTER, 5)
        pnl.SetSizer(sizer)
        self.Show()

    def on_press_correct(self, event):
        return

    def on_press_copy(self, event):
        data_obj = wx.TextDataObject()
        data_obj.SetText(self.text_area2.GetValue())
        if wx.TheClipboard.Open():
            wx.TheClipboard.SetData(data_obj)
            wx.TheClipboard.Close()
            self.btn_copy.Disable()
            self.label.SetLabel("Text copied")
            #elf.label.GetParent().GetSizer().Layout()
            self.timer.StartOnce(3000)
        else:
            wx.MessageBox("Can't open clipboard", "Error")

    def on_timer(self, event):
        self.btn_copy.Enable()
        #self.label.Show(show=False)
        #self.label.GetParent().GetSizer().Layout()
        self.label.SetLabel("           ")

app = wx.App(False)
frame = Application(None)
app.MainLoop()

It isn't perfect but it's close.

  • Related