Scrollbar in Frame

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Alex92
User
Beiträge: 3
Registriert: Samstag 10. Dezember 2016, 18:32

Guten Abend,

ich beschäftige mich gerade das erste Mal mit wxPython, wie man wahrscheinlich schon am Titel sieht :lol: , und bräuchte ein paar Tipps wie ich das was mir vorschwebt umsetzen kann.

Ich möchte etwas in der Art wie in dem Paint-Bild hier erstellen: Bild

Vereinfacht dargestellt habe ich bisher das hier gemacht:

Code: Alles auswählen

# -*- coding: utf-8 -*- 


import wx
import wx.xrc


class Frame ( wx.Frame ):
	
	def __init__( self ):
		wx.Frame.__init__ ( self, None, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 500,500 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
		
		
		gSizer1 = wx.GridSizer( 0, 2, 0, 0 )
		
		self.search = wx.TextCtrl( self, wx.ID_ANY, u"", wx.DefaultPosition, wx.DefaultSize, wx.TE_CENTRE )
		gSizer1.Add( self.search, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
		
		self.button = wx.Button( self, wx.ID_ANY, u"MyButton", wx.DefaultPosition, wx.DefaultSize, 0 )
		gSizer1.Add( self.button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
		
		
		
		
		gSizer2 = []
		
		for i in range(0,5):
			self.pic = wx.StaticBitmap( self, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, 0 )
			gSizer1.Add( self.pic, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 5 )
			
			gSizer2.append(wx.GridSizer( 0, 2, 0, 0 ))
			
			self.text = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_CENTRE|wx.TE_READONLY )
			gSizer2[i].Add( self.text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
			
			self.pic2 = wx.StaticBitmap( self, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, 0 )
			gSizer2[i].Add( self.pic2, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
			
			self.spin = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 10, 0 )
			gSizer2[i].Add( self.spin, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
			
			self.add = wx.Button( self, wx.ID_ANY, u"Add", wx.DefaultPosition, wx.DefaultSize, 0 )
			gSizer2[i].Add( self.add, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )
			
			
			gSizer1.Add( gSizer2[i], 1, wx.EXPAND, 5 )
		
		
		self.SetSizer( gSizer1 )
		self.Layout()
		
		self.Centre( wx.BOTH )
		
		# Connect Events
		self.search.Bind( wx.EVT_TEXT, self.searchOnText )
		self.button.Bind( wx.EVT_BUTTON, self.buttonOnButtonClick )
		self.spin.Bind( wx.EVT_SPINCTRL, self.spinOnSpinCtrl )
		self.add.Bind( wx.EVT_BUTTON, self.addOnButtonClick )
		
		
		self.Show()
		
	def __del__( self ):
		pass
	
	
	# Virtual event handlers, overide them in your derived class
	def searchOnText( self, event ):
		event.Skip()
	
	def buttonOnButtonClick( self, event ):
		event.Skip()
	
	def spinOnSpinCtrl( self, event ):
		event.Skip()
	
	def addOnButtonClick( self, event ):
		event.Skip()
	
app = wx.App()
window = Frame()
app.MainLoop()
Die Suche soll eine Liste durchsuchen und Treffer in der wx.TextCtrl darunter ausgeben.
Das habe ich jetzt hinbekommen. Ich würde allerdings gerne die Größe der einzelnen GridSizers irgendwie konstant halten und nicht an die Fenstergröße anpassen so, dass man eine Scrollbar oder ähnliches bekommt wenn nicht alles auf das Fenster passt.

Außerdem würde mich interessieren wie man es hinbekommt, dass die Anzahl der einzelnen Boxen (also die Häufigkeit, die die for-Schleife durchlaufen wird) variabel ist und nicht konstant bleibt. So wie ich das jetzt gemacht habe wird das ja irgendwie anfangs einmal erzeugt und danach kann ich es nicht mehr verändern.

Mache ich das überhaupt ansatzweise richtig oder sollte man das ganz anders angehen? :lol:

Würde mich über eine Antwort freuen. Auch wenn meine Frage wahrscheinlich ziemlich dumm ist.^^

Mit freundlichen Grüßen
Alex92
Zuletzt geändert von Anonymous am Samstag 10. Dezember 2016, 21:46, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Alex92: Du suchst wahrscheinlich ein `ScrolledPanel`. Der Teil der Scrollbar sein soll, kommt dann dort als Inhalt hinein.

Wenn die Anzahl der Elemente in der Liste vom Suchergebnis abhängt, dann darfst Du die erst erzeugen wenn die Suche gelaufen ist und damit das Ergebnis feststeht. Damit erledigt sich die Frage nach der Anzahl der Schleifendurchläufe. Nach einer Suche müsste man die eventuell vorhandene vorherige Suchanzeige zerstören und eine neue erstellen.

Der `GridSizer` ist hier nicht so günstig. Bei diesem `Sizer` sind alle Zellen gleich breit und gleich hoch. Das heisst so hoch wie der höchste Inhalt irgendwo im gesamten Gitter und so breit wie der breiteste Inhalt im gesamten Gitter. Und Deine Suchergebnisse erstrecken sich nicht über das gesamte Fenster sondern sind nur in der zweiten Spalte von dem Gitter.

Die Sucheingabe und das Ergebnis scheinen mir unabhängig voneinander zu sein. Die könnte man mit einem `BoxSizer` übereinander anordnen.

Es macht keinen Sinn die Objekte in der Schleife an das Objekt zu binden. Nach der Schleife ist dann jeweils nur das letzte Objekt an das Objekt gebunden. Dementsprechend machen die `Bind`-Aufrufe für solche Objekte nach der Schleife ebenfalls keinen Sinn, denn die sind ja dann auch nur für das jeweils *letzte* in der Schleife erzeugte Widget.

Die `__del__()`-Methode muss weg! Nicht nur das die nichts macht, alleine die Existenz ist schon ”gefährlich”. Vergiss am besten das es diese Methode überhaupt gibt.

Die Leerzeichensetzung ist insbesondere um Klammern herum etwas gewöhnungsbedürftig. Style Guide for Python Code sieht vor und nach Klammern von Aufrufen keine Leerzeichen vor. Bei der Schreibweise von Namen macht es Sinn von den Richtlinien abzuweichen wo sich eine Bibliothek nicht daran hält, weil sie wie `wx` eine Anbindung an eine Bibliothek in einer anderen Programmiersprache mit anderen Namenskonventionen ist. Das kann man als Chance sehen durch die Namenschreibweisen die Trennung zwischen Programmlogik und GUI-Code sichtbarer zu machen. Denn bei Code der nicht von der GUI abhängt, würde ich mich trotzdem weiter an die Python-Konventionen halten.

Der Sinn von Defaultwerten bei Argumenten ist, dass man die nicht immer alle übergeben muss. Es macht keinen Sinn bei Aufrufen nochmal explizit alle Defaultwerte zu übergeben.

Wenn man Bilder im Prototyp hat, dann sollte man keine `NullBitmap` verwenden, sondern einen Platzhalter mit einer realistischen Grösse. Ich würde da einfach ”leere” `Bitmap`-Objekte verwenden. Dann sieht man etwas und es ist eventuell auch nicht so langweilig wenn der Speicher für die Bitmap zufälligen Datenmüll enthält. :-)

`Show()`-Aufrufe sollten von aussen kommen und nicht vom Objekt selbst. Das schränkt die Verwendungsmöglichkeiten unnötig ein.

Auf Modulebene sollte nur Code stehen der Konstanten, Klassen, und Funktionen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Der Zwischenstand weiter unten hat sicher noch nicht so ganz das Layout das Du letztendlich haben möchtest, aber das Suchergebnis ist schon mal in einem scrollbaren Bereich. Ich habe Dir da noch das `InspectTool` auskommentiert reingeschrieben. Wenn Du das aktivierst, bekommst Du ein zusätzliches Fenster in dem Du die Widget-Hierarchie anschauen und untersichen kannst. Oben bei den Schaltflächen kann man die Sizer mit im Baum anzeigen lassen und es gibt eine Schaltfläche mit der man das im Baum ausgewählte Element in der GUI hervorheben lassen kann. So findet man Fehler im Layout wenn die Hierarchy aus dem Quelltext nicht so leicht zu erkennen ist.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import wx
from wx.lib.inspection import InspectionTool
from wx.lib.scrolledpanel import ScrolledPanel


class Frame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(
            self,
            None,
            title='Test',
            size=(500, 500),
            style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL,
        )

        vbox_sizer = wx.BoxSizer(wx.VERTICAL)

        search_sizer = wx.BoxSizer()
        self.search_ctrl = wx.SearchCtrl(self)
        search_sizer.Add(self.search_ctrl)
        self.search_button = wx.Button(self, label='Go')
        search_sizer.Add(self.search_button)
        vbox_sizer.Add(search_sizer)

        results_panel = ScrolledPanel(
            self, style=wx.TAB_TRAVERSAL | wx.BORDER_SUNKEN
        )
        vbox_sizer.Add(results_panel, 1, flag=wx.EXPAND)

        results_sizer = wx.FlexGridSizer(0, 2, 5, 5)
        large_bitmap = wx.EmptyBitmap(200, 200)
        small_bitmap = wx.EmptyBitmap(50, 50)
        for i in xrange(5):
            bitmap = wx.StaticBitmap(results_panel, bitmap=large_bitmap)
            results_sizer.Add(
                bitmap,
                0,
                wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL,
            )

            result_subsizer = wx.GridSizer(0, 2)

            text = wx.TextCtrl(
                results_panel,
                value=str(i),
                style=wx.TE_CENTRE | wx.TE_READONLY,
            )
            result_subsizer.Add(text, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL)

            bitmap = wx.StaticBitmap(results_panel, bitmap=small_bitmap)
            result_subsizer.Add(bitmap, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL)

            spin = wx.SpinCtrl(results_panel, max=10)
            result_subsizer.Add(spin, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL)

            add = wx.Button(results_panel, label='Add')
            result_subsizer.Add(add, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL)

            results_sizer.Add(result_subsizer, 0, wx.EXPAND, 5)

        results_panel.SetSizer(results_sizer)
        results_panel.SetAutoLayout(True)
        results_panel.SetupScrolling()
        self.SetSizer(vbox_sizer)
        self.Layout()

        self.search_ctrl.Bind(wx.EVT_TEXT, self.on_search_text_change)
        self.search_button.Bind(wx.EVT_BUTTON, self.on_search_button)

    def on_search_text_change(self, event):
        event.Skip()

    def on_search_button(self, event):
        event.Skip()


def main():
    app = wx.App()
    window = Frame()
    window.Show()
    #InspectionTool().Show()
    app.MainLoop()


if __name__ == '__main__':
    main()
Alex92
User
Beiträge: 3
Registriert: Samstag 10. Dezember 2016, 18:32

Hallo BlackJack,

danke für die schnelle und ausführliche Antwort. Genau das habe ich gesucht.

Ich bin jetzt erst dazu gekommen weiterzumachen und habe jetzt alles so wie ich es erstmal haben wollte.
Das Panel wird jetzt nach der Sucheingabe zerstört und dann neu erstellt. Was mich dabei nur etwas stört ist die recht lange Ladezeit wenn das Panel neu erstellt wird.
Aber ich denke das ist nicht so einfach optimierbar oder?


Weißt du zufällig warum recht viele wxPython Codes von anderen nicht bei mir ohne umschreiben funktionieren? Die ganzen Demos funktionieren nicht direkt und auch bei deinem Code habe ich anfangs ein paar Fehler bekommen. z.B.:

Code: Alles auswählen

test5.py:35: wxPyDeprecationWarning: Call to deprecated item EmptyBitmap. Use :class:`wx.Bitmap` instead
  large_bitmap = wx.EmptyBitmap(200, 200)
test5.py:36: wxPyDeprecationWarning: Call to deprecated item EmptyBitmap. Use :class:`wx.Bitmap` instead
  small_bitmap = wx.EmptyBitmap(50, 50)
Traceback (most recent call last):
  File "test5.py", line 90, in <module>
    main()
  File "test5.py", line 83, in main
    window = Frame()
  File "test5.py", line 38, in __init__
    bitmap = wx.StaticBitmap(results_panel, bitmap=large_bitmap)
TypeError: StaticBitmap(): arguments did not match any overloaded call:
  overload 1: too many arguments
  overload 2: 'bitmap' is not a valid keyword argument
und:

Code: Alles auswählen

  File "test5.py", line 45, in __init__
    result_subsizer = wx.GridSizer(0, 2)
TypeError: GridSizer(): arguments did not match any overloaded call:
  overload 1: not enough arguments
  overload 2: argument 2 has unexpected type 'int'
  overload 3: not enough arguments
  overload 4: not enough arguments
Gruß Alex92
BlackJack

@Alex92: Kann es sein, dass Du wxPython Phoenix verwendest? 99% der Codebeispiele und Programme die man findet werden irgendwas zwischen wxPython 2.7.x bis 3.x verwenden, also die stabilen Versionen und was bei Linux-Distributionen so ausgeliefert wird, bei denen sich an der API auch nicht viel dramatisch verändert hat. Um mal von der wxPython-Homepage zu zitieren: „While Phoenix is currently in a pre-alpha state, it is already in a very usable state and many people are already using it in their projects.“
Alex92
User
Beiträge: 3
Registriert: Samstag 10. Dezember 2016, 18:32

Ja, habe mir Phoenix geholt, da es in dem Tutorial, welches ich durchgemacht habe, genutzt wurde.
Sollte ich da wechseln?

Ich hätte noch eine Frage um kein neues Thema aufmachen zu müssen.
Sollte der Befehl 'wx.EmptyBitmapRGBA' nicht ein Bild mit genau dieser Farbe ausgeben? Da ich Phoenix nutze habe ich 'wx.Bitmap.FromRGBA' benutzt. Es wird zwar etwas erstellt das Platz im Programm einnimmt allerdings wird nichts angezeigt.
Oder muss man da etwas anderes nutzen?

Gruß Alex92
BlackJack

@Alex92: Ich kann zu Phoenix nix sagen. Wenn es hinreichend gut funktioniert dann kannst Du dabei bleiben. Sparst Du ein eventuelles späteres portieren wenn es stable releases gibt.

Wie hast Du die Farbe denn angegeben? Bzw. hast Du den Alphawert auch angegeben? Denn der Defaultwert 0 ist blöd gewählt → komplett transparent.
Antworten