2D-Objekte dynamisch erstellen und mit Events verknüpfen

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

Hallo zusammen!

Leider bin ich noch nicht auf eine Lösung für mein Problem gestossen; und hier im Forum habe ich auch nichts gefunden.
Aus einer DB hole ich mir Einträge zu Dokumenten und deren spezifischen Daten. Diese Dokumente gehe ich in einer Schleife durch. Nun möchte ich für jedes der vorhandenen Dokumente ein grafisches Objekt erstellen und dieses mit Events verknüpfen. Bisher bin ich aber bereits an der Benennung der Panels gescheitert. Meine erste Frage: kann ich die Objekte bei der Initialisierung irgendwie mit dem Schleifenzähler verbinden?

Also z. B.:

Code: Alles auswählen

for i in range(len(self.model.docs)):
   self.docPnl [i] = wx.Panel(self, -1) # Das Panel soll hier mit [i] verknüpft werden
Das grössere Problem ist jedoch die Verknüpfung mit den Events. Später möchte ich beim MouseOver mehr über die Dokumente abrufen. Nun muss aber die Bind()-Methode bereits im Konstruktor aufgerufen werden; meine Panels (oder welche Obkjekte auch immer) möchte ich aber in einer Funktion erstellen lassen. Kann ich die Events trotzdem irgendwie mit den Objekten verknüpfen? Ich komme da leider nicht weiter; mir ist klar wo der Fehler liegt, aber nicht, wie ich Ihn beheben kann.

Kennt jemand einen Weg, Objekte anhand einer Liste zu erstellen und mit Events zu verknüpfen? Ich bin für jeden Vorschlag dankbar, denn wenn so etwas nicht möglich ist muss ich mein Projekt wohl umstellen :(

Vielen Dank, lg
BlackJack

@Ruler: Was genau passiert denn bei den gezeigten Zeilen? Explodiert der Rechner? Bekommst Du eine Fehlermeldung? Wenn ja *welche*?

Musst Du `docPnl` abkürzen? Werden Dir Vokale extra in Rechnung gestellt? ;-)

An Stelle der -1 bei `Panel()` würde ich `wx.ID_ANY` verwenden, das macht deutlicher was die magische Zahl dort bedeutet.

Wieso musst Du `Bind()` im Konstruktor aufrufen!? Diese Einschränkung wäre mir neu.
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Hi Ruler,

natürlich kann man Widgets dynamisch erstellen und an Ereignisse binden; du bist da keineswegs auf den Konstruktor beschränkt. Wenn das bei dir nicht funktioniert dann läuft etwas anderes in deinem Code schief; zeig doch mal ein bisschen mehr (bitte mit python-tags formatieren).

Ansonsten als Anregung:
- ausgehend von einem Dictionary mit den Dokumentenobjekten
- jedes Objekt speichert eine Referenz auf das Widget (Panel? Window? StaticBitmap?...), das das Dokument in der GUI repräsentiert; vom Ereignis kannst du dann über das auslösende Objekt den Dictionaryeintrag finden
- oder du schickst dem Ereignis-Handler über eine lokale Funktion gleich den Key mit

Gruß
Norbert
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

Hallo zusammen! Danke schonmal für eure Anregungen; ich sehe ich muss noch viel lernen...

@Blackjack: Was kann ich mit wx.ID_ANY genau machen? Wo liegen die Vorteile? Ich kenn mich da nicht aus...
Die Bezeichnung "Pnl" oder "Btn" hat unser Dozent damals imer verwendet, und ich hab mir das so angewöhnt, ausschreiben wär schon schöner aber dann hab ich noch mehr unnötigen Code :)

Anbei mal die Funktion (ich weiss leider nicht wie man "mit Python-Tags" formatiert...). Die Funktion wird jeweils in der OnSize()-Funktion aufgerufen, nur tut sie nur zur Hälfte das, was ich mir wünsche :)

Code: Alles auswählen

def drawDocs(self):
		
		self.model.getDocs()
		self.docSizer.Clear()
		
		if self.model.curr_pat.connected:
			print "Laenge:", len(self.model.docs)
			for i in range(len(self.model.docs)):
				print "Ich bin ein Dokument!"
				panel = wx.Panel(self, -1, size=((10), 10))
				#self.docSizer.Add(panel, 10, 5)
				panel.SetBackgroundColour("red")
				panel.SetPosition((i*10, 10))
				panel.Bind(wx.EVT_LEFT_DOWN, self.testPrint)
Leider klebt das Panel immer nur am linken Rand. Die Position verändern scheint nicht zu funktionieren.
Zudem wird anscheinend immer nur ein Panel erstellt und nicht mehrere, so wie es sein sollte
Folgende Dinge sind mir unklar:
- Kann die Positionierung überhaupt so funktionieren? Ich hoffe doch...
- Mein "grösstes" Problem: die spätere Unterscheidung der Panels. @ntrunk, wie könnte so eine Funktion aussehen?
Wenn ich den Schleifenzähler (i) an den Bezeichner "panel" anhänge, erkennet er das Panel angeblich nicht (global name 'panel' is not defined). Wenn ich es so wie oben ausführe klappt es. Nur wird dann halt nur ein Panel gezeichnet.
Anbei noch eine Skizze, wie das Ganze ungefähr zu verstehen ist. Bitte nur den unteren Teil beachten; dort sollen die Panels anhand der jeweiligen Screengrösse positioniert werden. Die ganze Berechnung ist schon gemacht, ich muss nur noch wissen wie ich die Panels positionieren kann. Vielen Dank schon mal für eure Hilfe!

Ich lese mich glaub mal in Dictionaries ein :)

http://img210.imageshack.us/i/guientwurf.png/
BlackJack

@Ruler: `wx.ID_ANY` kannst Du überall da benutzen, wo eine ID erwartet wird, Du aber keine explizit angeben möchtest, sondern wxWidgets die Wahl überlässt. Und jemand der den Quelltext liest (inklusive Dir) weiss sofort "Ah, an der Stelle wird eine ID übergeben", im Gegensatz zu einer -1, die ja sonstwas bedeuten kann.

Lesbarer(er) Quelltext ist eigentlich nie unnötiger Code. Ich habe mir mal angewöhnt Variablennamen mit maximal zwei Zeichen abzukürzen weil das CBM BASIC eh nicht mehr berücksichtige und Speicher schnell knapp wurde. Die Zeiten sind mittlerweile $GOTT sei Dank vorbei und man kann verständliche, ausgeschriebene Bezeichner verwenden, bei denen man nicht erst raten muss welche Vokale wo dazwischen gehören und was da abgekürzt wurde.

Die Positionierung dürfte so nicht gehen. Aber ich habe in `wx` auch noch nie etwas absolut positioniert. Ich blicke den Zusammenhang zwischen dem Bild und dem Quelltext nicht ganz!? Was wären denn da die `panel`\s?

Musst Du das überhaupt selbst Layouten? Kann man das nicht versuchen mit einem `GridBagSizer` zu machen?

Bei jedem `OnSize()` alle Objekte neu zu erstellen (und das auch noch ohne die alten zu zerstören) erscheint mir auch etwas ineffizient.

Was meinst Du mit »i an den Bezeichner "panel" anhänge[n]«? Und es werden bestimmt auch alle Panels gezeichnet, nur halt alle exakt an der gleichen Stelle weil die Positionieren so nicht klappt.

Wenn Du Dich in Dictionaries erst einlesen musst, dann solltest Du das Projekt solange zurückstellen, bis Du die Grundlagen von Python kennst. Also zum Beispiel das Tutorial in der Dokumentation durchgearbeitet hast. Dictionaries sind neben Listen wohl der meist genutzte Grunddatentyp in Python.

Ansonsten wäre es hilfreich, wenn Du einfach mal ein lauffähiges Minimalbeispiel schreibst. Sowohl für Dich, damit Du etwas zum Experimentieren hast, als auch für uns, damit wir das was Du hast, auch laufen lassen können und nicht nur Bruchstücke von einem grösseren Programm sehen.
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

@Ruler:
Ich kann Blackjack in seinen Ausführungen nur zustimmen.
Ergänzend dazu:
- Mit "python-tags" formatieren meint genau das, was du gemacht hast: deinen Code im Textfeld markieren und den Button mit der Aufschrift 'python' klicken... :-)
- Das mit der lokalen Funktion erspare ich dir erst mal, da du anscheinend noch Probleme mit den Grundlagen hast.
- Ich glaube, ich weiß, was du mit "(i) an 'panel' hängen" meinst: du möchtest vermutlich dynamisch Bezeichner erstellen. Das geht zwar prinzipiell schon, aber für dein Problem ist das eher nicht das Mittel der Wahl. Nimm eine Liste und hänge mit append() deine Objekte dran.
- So, und als Starthilfe ein Minimalbeispiel zum Weiterexperimentieren (in python 2.6) ohne lokale Funktionen und Dictonaries (aber bitte vertiefe dich trotzdem in die Grundlagen):

Code: Alles auswählen

import wx

class MyDocument:
    def __init__(self, name):
        self.name = name

class MyFrame(wx.Frame):
    def __init__ (self, documents):
        wx.Frame.__init__(self, None, title='')
        self.documents = documents
        self.doc_panel = wx.Panel(self)
        self.doc_panel.SetBackgroundColour('#a0b0c0')
        self.doc_panel.Bind(wx.EVT_SIZE, self.on_size)
        self.create_doc_icons()

    def create_doc_icons(self):
        # in dieser Liste werden die Icons verwaltet
        self.doc_icons = []
        for document in self.documents:
            # Icon für Document erzeugen
            doc_icon = wx.Window(self.doc_panel, size=(20,30))
            doc_icon.SetBackgroundColour('#ffffff')
            doc_icon.Bind(wx.EVT_LEFT_UP, self.on_doc_click)
            # Das Icon erhält einen Verweis auf das Document
            doc_icon.document = document
            self.doc_icons.append(doc_icon)

    def on_size(self, event):
        # die Icons werden einfach nebeneinander aufgereiht
        for i,doc_icon in enumerate(self.doc_icons):
            doc_icon.SetPosition((i*25+5, 5))

    def on_doc_click(self, event):
        # das Event liefert einen Verweis auf das Icon => das Icon hält den
        # Verweis auf das Document
        document = event.GetEventObject().document
        wx.MessageBox('Document icon "%s" was clicked.' % document.name)

if __name__ == '__main__':

    # testweise 10 Dokumente in eine Liste stecken
    mydocuments = []
    for i in range(10):
        mydocuments.append(MyDocument('Dokument #%d' % i))

    app = wx.PySimpleApp()
    frame = MyFrame(mydocuments)
    frame.Show()
    app.MainLoop()
@Blackjack:
Wenn man eine veränderliche Liste an gleichartigen Objekten hat, ist es m.E. schon mal sinnvoll, die selbst im Ausgabefenster anzuordnen; insbesondere, wenn die Objekte eine feste Größe besitzen und die Anzahl der Spalten und Zeilen deshalb je nach Größe des Fensters variiert. Hey, das wäre doch eine reizvolle Aufgabe, einen Sizer zu schreiben, der ein Grid mit fester Zellengröße und deshalb variabler Spalten- und Zeilenanzahl verwaltet; oder gibt es das am Ende schon?
BlackJack

@ntrunk: Ich weiss nicht ob ich Deine "Wunschsizer-Beschreibung" richtig verstanden habe, aber ich musste mal Zeitpläne mit verschieden langen Zeitspannen, aber in einem festen Zeitraster (minimal 15 Minuten-Abschnitte) irgendwie druckbar darstellen und habe das als einfachstes Mittel was mir einfiel mit einer HTML-Tabelle mit 96 Zellen (96 * 15 min = 24 h) pro Zeile (1 Zeile = 1 Tag) gemacht und da bei den Zeitspannen halt entsprechend viele Zellen mit ``colspan`` zusammengefasst. So etwas kann man mit dem `GridBagSizer` sicher auch machen. Das GUI Bild sah ein bisschen so aus, als wenn man dass da vielleicht auch hätte machen können.
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

BlackJack hat geschrieben:@ntrunk: Ich weiss nicht ob ich Deine "Wunschsizer-Beschreibung" richtig verstanden habe, aber ich musste mal Zeitpläne mit verschieden langen Zeitspannen, aber in einem festen Zeitraster (minimal 15 Minuten-Abschnitte) irgendwie druckbar darstellen und habe das als einfachstes Mittel was mir einfiel mit einer HTML-Tabelle mit 96 Zellen (96 * 15 min = 24 h) pro Zeile (1 Zeile = 1 Tag) gemacht und da bei den Zeitspannen halt entsprechend viele Zellen mit ``colspan`` zusammengefasst. So etwas kann man mit dem `GridBagSizer` sicher auch machen. Das GUI Bild sah ein bisschen so aus, als wenn man dass da vielleicht auch hätte machen können.
Gute Idee, das. Hätte ich auch schon mal brauchen können, da hätte ich mir einiges an Arbeit erspart.
Mit dem selbstgemachten Sizer meine ich aber eigentlich einen Sizer, der eine Liste mit Widgets verwaltet, die immer die gleiche Größe haben und diese dann je nach verfügbarer Breite in einer variablen Anzahl von Spalten anzeigt. Das ist vermutlich genau das, was der OP bräuchte und was er nun manuell programmieren muss. Die verfügbaren Sizer verändern die Größe der verwalteten Widgets, so dass der Platz optimal genutzt wird, d.h. sie machen eben das worauf der Name schließen lässt ;-)

Gruß
Norbert
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

@ntrunk: Vielen Dank für das Beispiel, ist genau das wonach ich gesucht habe! Und mit den dynamischen Bezeichnern hast Du auch ins Schwarze getroffen! Aber eben ich für meinen Teil muss noch viel lernen diesbezüglich. Nun habe ich noch eine Frage, welche sich mit den Icons befasst:

Da sich die Icons an den zur Verfügung stehenden Platz anpassen sollen, möchte ich diese gerne jedesmal neu zeichnen (in Abhängigkeit der Panelgrösse, die Berechnung hierfür habe ich bereits gemacht). Bei der von Dir zur Verfügung gestellten Variante werden die Icons einmal platziert und danach bleiben Sie an dieser Stelle.
Wenn ich eine Zeichnungsfläche hätte, könnte ich diese mit bspw. dc.Clear() wieder löschen. Aber beim Panel geht sowas glaube ich nicht... Oder täsuche ich mich da? Und wenn so was möglich wäre, wo müsste ich die "Clear-Funktion" aufrufen? Wenn ich das noch rausfinden würde,wäre ich bald mal am Ziel :)

Nochmals danke an alle!
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Ruler hat geschrieben:[...]
Da sich die Icons an den zur Verfügung stehenden Platz anpassen sollen, möchte ich diese gerne jedesmal neu zeichnen (in Abhängigkeit der Panelgrösse, die Berechnung hierfür habe ich bereits gemacht). Bei der von Dir zur Verfügung gestellten Variante werden die Icons einmal platziert und danach bleiben Sie an dieser Stelle.
[...]
In meinem Beispiel werden die Dokument-Widgets bei jedem Size-Event neu positioniert. Wenn du dabei auch noch die Größe anpassen möchtest, spricht eigentlich nichts dagegen.
Wenn du als Symbol für deine Dokumente irgendwelche Bitmaps verwenden möchtest, solltest du als Widget statt wx.Window wx.StaticBitmap verwenden, da kannst du dein Bitmap setzen und brauchst dich um das Zeichnen nicht zu kümmern.
Falls das Aussehen allerdings dynamisch gestaltet werden soll, kommst du um das Verwenden des Paint-Events mit Selberzeichnen mit wx.PaintDC nicht herum. Schau mal in die Demo, da gibt es sehr aufschlussreiche Beispiele.

Gruß
Norbert
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

Danke für Deine Ausführungen. Die Grösse der wx.Window-spielt eigentlich keine Rolle, bzw. wäre nicht so ein Problem. Was mir Schwiergkeiten bereitet ist, das ja bei jedem Size-Event neue Icons dazukommen, und die alten nicht gelöscht werden. Bei mehrmaligen resizen wird das ganze also ziemlich schmierig. Ich suche nach einer Möglicheit, die "alten" Icons zu löschen und immer nur die neuesten anzuzeigen. Ich habe mich da vielleicht nicht ganz klar ausgedrückt.

Bei der anderen "Anzeigemethode" (wx.PaintDC) kann ich mit einem Refresh() des Panels die Objekte neu zeichnen. Analog dazu suche ich nach einer solchen Lösung für die Icons. Gerne würde ich es bei dem momentanen Ansatz lassen, da ich nicht weiss wie ich Events von Objekten, die ich mit Drawxy gezeichnet habe, abfangen soll... :K
In der Demo ist erklärt, wie das mit Bilder funktioniert, ich bräuchte hier aber wirklich nur Linien. Aber wichtiger ist das "neuzeichnen" der Icons :(
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

@Ruler:
Schau dir meinen Beispielcode nochmals an, beim Resizen des Panels werden lediglich die Positionen der Widgets neu berechnet, da werden keine Objekte neu erzeugt und demnach bleibt auch kein "Müll" übrig, der entsorgt werden müsste. Ich habe den Eindruck, wir reden da aneinander vorbei...

Neuer Versuch: Ich habe mir deinen Screenshot nochmals angesehen: Kann es sein, dass du eigentlich ein Diagramm über einen Zeitraum darstellen willst, und deine "Dokumente" sollen als Zeitspanne, sprich verschieden lange Linien in diesem Diagramm dargestellt werden?

Gruß
Norbert
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

Anbei meine Klasse, mal zum besseren Verständnis... Irgwndwo habe ich wohl einen Aufruf zu viel bzw. werden halt immer neue Objekte erstellt. @ntrunk: Ich habe Deinen Code nochmal angschaut und gesehen, dass da nichts überigbleibt, ja... so sollte das bei mir auch sein.

Zur Erläuterung:

Die Funktionen getDocs() und getMeds() liefern jeweils Listen. In meiner Klasse habe ich die "Dummy-Liste" der Dokumente aus Deinem Beispiel mit der Liste gefüttert, welche mir getDocs() liefert. Ich verstehe nicht warum die immer neu erstellt werden... Ich habe es mal mit self.drawn versucht, nur werden die Icons dann nicht mehr neu positioniert. Ich kann das hier leider nicht als laufendes Beispiel posten, da es nur ein Teil eines grösseren Programmes ist.

Code: Alles auswählen

class TimePnl(wx.Panel):
	def __init__(self, *args, **kwds):
		wx.Panel.__init__(self, *args, **kwds)
		
		self.model = self.Parent.Parent.model
		#self.drawn = False
		self.Bind(wx.EVT_PAINT, self.OnPaint)
		self.createDocIcons()

	def createDocIcons(self):
			self.model.getDocs() #Muss unbedingt hier aufgerufen werden!
			
			# in dieser Liste werden die Icons verwaltet
			self.doc_icons = []
			
			if self.model.curr_pat.connected and not self.drawn:
				for document in range(len(self.model.docs)):
					# Icon fuer Document erzeugen
					doc_icon = wx.Window(self, size=(5,5))
					doc_icon.SetBackgroundColour('#ff0000')
					doc_icon.Bind(wx.EVT_LEFT_UP, self.on_doc_click)
					# Das Icon erhaelt einen Verweis auf das Document
					doc_icon.document = document
					self.doc_icons.append(doc_icon)
					#self.drawn = True
	
	def drawMeds(self):
		
		self.model.getMeds()
		
		if self.model.curr_pat.connected:
			for i in range(len(self.model.meds)):
				#self.model.meds[i]['started']
				
				s = math.ceil(self.model.calcDayRatio() * math.ceil(self.model.calcMedPos(i)[0]))
				l = math.ceil(self.model.calcDayRatio() * math.ceil(self.model.calcMedPos(i)[1]))
				
				print "Startpunkt Medi:", i, s
				
				dc.SetPen(wx.Pen('grey', 5))
				dc.DrawLine(s, ((i+1)*10)+20, s+l, ((i+1)*10)+20)
				pass

	def OnSize(self):
		self.model.getDocs()
		self.createDocIcons()
		
		# die Icons werden einfach nebeneinander aufgereiht
		for i,doc_icon in enumerate(self.doc_icons):
			#self.Refresh()
			pos = math.ceil(self.model.calcDayRatio() * math.ceil(self.model.calcDocPos(i)))
			doc_icon.SetPosition((pos, 5))

	def OnPaint(self, evt):
		global dc
		dc = wx.PaintDC(self)
		self.Refresh()
		self.drawMeds()

	def on_doc_click(self, evt):
		# das Event liefert einen Verweis auf das Icon => das Icon haelt den
		# Verweis auf das Document
		document = evt.GetEventObject().document
		# Naechste Zeile waere mit document.name
		#print self.model.docs[document]['clin_when']
		'''
		for part in self.model.docs[document].parts:
			part.display_via_mime()
		'''
		
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

@Ruler:
Puh, tut mir leid das so hart sagen zu müssen, aber dein Code enthält einiges an Unstimmigkeiten und Unsauberkeiten :? (ist nicht böse gemeint, sondern einfach eine Feststellung)

Im Detail:
- self.Parent sollte m.E. nicht direkt verwendet werden (in der Doku ist es nicht aufgeführt), sondern stattdessen der Rückgabewert von self.GetParent(). *Aber:* Sobald du deine GUI anders organisierst, funktioniert dein Code nicht mehr. Besser wäre es, "model" als Argument beim Erzeugen des Panels mitzugeben.

- self.model.getDocs() liefert keine Liste, und wenn doch, wird diese Liste verworfen. Evtl. erzeugt die Funktion die Liste? Dann sollte sie auch so heißen.

- dito für self.model.getMeds()

- das Size-Event wird nicht gebunden, und wenn:

- rufst du bei jedem Size-Event self.createDocIcon() auf. Deshalb werden bei dir auch jedesmal neue Widgets erzeugt.

- global: nicht verwenden, das ist böse und keine gute Idee! Übergib besser den DC beim Aufruf an deine Funktion als Argument. Kann es sein, das du dir nicht im Klaren darüber bist, dass man Funktionen Parameter als Argumente übergeben kann (und sollte!) sowie dass Funktionen Werte zurückgeben können? Wenn du davon Gebrauch machst, kannst du deinen Code besser kapseln und kommst *ohne* global aus.

- Refresh im Paint-Event? Das ist bestenfalls überflüssig und führt schlimmstenfalls zum Absturz.

- wenn du ein lauffähiges Minimalbeispiel nicht oder nur unter Schwierigkeiten erstellen kannst, ist das ein deutlicher Hinweis, dass dein Code keine klaren Strukturen aufweist. Konkret heißt das, dein Code ist wie ein kompliziertes Uhrwerk, bei dem ein Rädchen ins andere greift. Weil jeder Teil von vielen anderen abhängt, ist es nicht mehr möglich, ein Teil zu isolieren, z.B. um es zu testen. Hier hilft eigentlich nur ein Redesign des Programms mit sauberer Trennung zwischen den einzelnen Komponenten.

Ist es ausserdem so (wie ich bereits im letzten Posting vermutet habe), dass du eigentlich ein Diagramm erstellen willst und die sog. "Dokumente" beschreiben eine Aktivität über einen bestimmte Zeitspanne, die du dann im Diagramm grafisch darstellen willst?
Wenn dem so ist, ist der ganze Umweg über die Dokumenten-Icons unnötig, du kannst direkt deine Linien in das Panel zeichnen (was du ja bereits versuchst) und dann anhand des Motion-Events auf die Mausaktivitäten reagieren.

Gruß (und nichts für Ungut, die meisten deiner Fehler habe ich selbst auch schon gemacht)
Norbert
Ruler
User
Beiträge: 8
Registriert: Donnerstag 21. Oktober 2010, 15:25

Hallo Norbert

Das ist kein Problem - ich bin froh wenn mich jemand auf grobe Schnitzer hinweist und mir Tipps geben kann... dafür gibt's ja das Forum :)

Ich bin anhand Deiner Tipps weiter gekommen und habe ein bisschen was verbessern können.
rufst du bei jedem Size-Event self.createDocIcon() auf. Deshalb werden bei dir auch jedesmal neue Widgets erzeugt.
Wird nun nur einmal aufgerufen, und beim Size-Event werden nur die jeweils neu positiniert - so wie es gedacht ist.
global: nicht verwenden, das ist böse und keine gute Idee! Übergib besser den DC beim Aufruf an deine Funktion als Argument. Kann es sein, das du dir nicht im Klaren darüber bist, dass man Funktionen Parameter als Argumente übergeben kann (und sollte!) sowie dass Funktionen Werte zurückgeben können? Wenn du davon Gebrauch machst, kannst du deinen Code besser kapseln und kommst *ohne* global aus.
So gelöst:

Code: Alles auswählen

	def OnPaint(self, evt):
		dc = wx.PaintDC(self.cPnl.timePnl)
		self.drawMeds(dc)


def drawMeds(self, dc):
		if self.data.curr_pat.connected:
			for i in range(len(self.data.meds)):
				#self.model.meds[i]['started']
				
				s = math.ceil(self.data.calcDayRatio() * math.ceil(self.data.calcMedPos(i)[0]))
				s = (self.data.xPnlSize - s - 20)
				
				#if self.data.meds[]
				l = math.ceil(self.data.calcDayRatio() * math.ceil(self.data.calcMedPos(i)[1]))
				
				dc.SetPen(wx.Pen('grey', 5))
				dc.DrawLine(s, ((i+1)*10)+20, s+l, ((i+1)*10)+20)

Refresh im Paint-Event? Das ist bestenfalls überflüssig und führt schlimmstenfalls zum Absturz.
Ebenfalls eliminiert.
wenn du ein lauffähiges Minimalbeispiel nicht oder nur unter Schwierigkeiten erstellen kannst, ist das ein deutlicher Hinweis, dass dein Code keine klaren Strukturen aufweist. Konkret heißt das, dein Code ist wie ein kompliziertes Uhrwerk, bei dem ein Rädchen ins andere greift. Weil jeder Teil von vielen anderen abhängt, ist es nicht mehr möglich, ein Teil zu isolieren, z.B. um es zu testen. Hier hilft eigentlich nur ein Redesign des Programms mit sauberer Trennung zwischen den einzelnen Komponenten.
Das "Problem" hierbei ist, dass ich nur mit zwei Dateien arbeiten kann, diese aber nur Teile einer grösseren Software sind. Ich kann zwar die Panels alleine testen, aber ohne die Daten der "Logik" kann ich leider nichts nachvollziehen.
Ist es ausserdem so (wie ich bereits im letzten Posting vermutet habe), dass du eigentlich ein Diagramm erstellen willst und die sog. "Dokumente" beschreiben eine Aktivität über einen bestimmte Zeitspanne, die du dann im Diagramm grafisch darstellen willst?
Wenn dem so ist, ist der ganze Umweg über die Dokumenten-Icons unnötig, du kannst direkt deine Linien in das Panel zeichnen (was du ja bereits versuchst) und dann anhand des Motion-Events auf die Mausaktivitäten reagieren.
Das hast Du richtig vermutet, allerdings sollen die Dokumente wirklich nur als Punkte erscheinen, und die sind nun auch so wie sie sein sollen. Allerdings möchte ich andere Aktivitäten (Einnahmedauer von Medikamenten) als Zeitspanne mit HIlfe von Linien zeichnen. Dort ist mir nicht klar, wie ich Events "holen" kann; also wenn ich z.B. mit der Maus über einer mit DrawLine gezeichneten Linie bin. Wäre Klasse wenn sowas auch funktionieren würde. Die Frage ist, an was ich welchen Event binden soll?

Dann habe ich noch weitere Fragen... sorry aber ich hoffe ich mache dann weniger Fehler... Ich habe eine Klasse mit meinen Berechnungen, welche von einer anderen erbt (nur der Konstruktor):

Code: Alles auswählen

class cTimelinePnl(wxgTimelinePnl.wxgTimelinePnl):

	def __init__(self, *args, **kwargs):
		wxgTimelinePnl.wxgTimelinePnl.__init__(self, *args, **kwargs)
		self.__register_interests()
		self.data = cTimelineData()

Die andere Klasse:

Code: Alles auswählen

import wx

class wxgTimelinePnl(wx.Panel):
	def __init__(self, *args, **kwds):
		kwds["style"] = wx.TAB_TRAVERSAL
		wx.Panel.__init__(self, *args, **kwds)
		
		self.vPnl = ViewPanel(self, -1, style=wx.SIMPLE_BORDER)
		self.cPnl = ControlPanel(self, -1, style=wx.NO_BORDER)
		
		self.vPnl.SetBackgroundColour(wx.Colour(0, 255, 0))
		
		mainSizer = wx.BoxSizer(wx.VERTICAL)
		mainSizer.Add(self.vPnl, 70, wx.EXPAND | wx.ALL, 10)
		mainSizer.Add(self.cPnl, 30, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
		self.SetSizer(mainSizer)
		mainSizer.Fit(self)

class TimePnl(wx.Panel):
	def __init__(self, *args, **kwds):
		wx.Panel.__init__(self, *args, **kwds)
		
		
class ControlPanel(wx.Panel):
	def __init__(self, *args, **kwds):
		wx.Panel.__init__(self, *args, **kwds)
		
		#self.model = self.Parent.model
		self.data = gmTimelineWidgets()
		
		
		self.x1 = 0
		self.x2 = 0
		self.rectDrawn = False
		
		self.rectX = 0
		self.rectW = 0
		
		self.pnlSize = None
		
		self.rectPnl = wx.Panel(self, -1)
		self.timePnl = TimePnl(self, 1)
		self.checkPnl = wx.Panel(self, -1)
		
		self.rectPnl.SetBackgroundColour(wx.Colour(0, 127, 255))
		self.timePnl.SetBackgroundColour(wx.Colour(255, 255, 255))
		self.checkPnl.SetBackgroundColour(wx.Colour(255, 255, 133))
		
		self.controlSizer = wx.BoxSizer(wx.VERTICAL)
		self.controlSizer.Add(self.rectPnl, 10, wx.EXPAND, 0)
		self.controlSizer.Add(self.timePnl, 80, wx.EXPAND, 0)
		self.controlSizer.Add(self.checkPnl, 10, wx.EXPAND, 0)
		
		self.bilderCb = wx.CheckBox(self.checkPnl, -1, "Bilder")
		'''
		self.videoCb = wx.CheckBox(self.checkPnl, -1, "Videos")
		self.dokCb = wx.CheckBox(self.checkPnl, -1, "Dokumente")
		self.sonstigeCb = wx.CheckBox(self.checkPnl, -1, "Sonstige")
		self.hospitalCb = wx.CheckBox(self.checkPnl, -1, "Aufenthalte Krankenhaus")
		self.opCb = wx.CheckBox(self.checkPnl, -1, "Operationen")
		'''
		self.mediCb = wx.CheckBox(self.checkPnl, -1, "Medikation")
		
		
		sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
		sizer_1.Add(self.bilderCb, 0, 0, 0)
		'''
		sizer_1.Add(self.videoCb, 0, 0, 0)
		sizer_1.Add(self.dokCb, 0, 0, 0)
		sizer_1.Add(self.sonstigeCb, 0, 0, 0)
		sizer_1.Add((50, 20), 0, 0, 0)
		sizer_1.Add(self.hospitalCb, 0, 0, 0)
		sizer_1.Add(self.opCb, 0, 0, 0)
		'''
		sizer_1.Add(self.mediCb, 0, 0, 0)
		'''
		'''
		self.checkPnl.SetSizer(sizer_1)
		self.SetSizer(self.controlSizer)
		


class ViewPanel(wx.Panel):
	def __init__(self, *args, **kwds):
		wx.Panel.__init__(self, *args, **kwds)
		#self.model = self.Parent.model


Wenn ich nun eine OnPaint-Methode zum zeichnen verwenden, soll diese in der Klasse mit den Panels oder in der "Logik"-Klasse ausgeführt werden? Der Untscherschied leuchtet mir nicht ein.
Was mir zudem noch aufgefallen ist: wenn ich den EVT_PAINT binde, werden bei mir Tooltips nicht angezeigt. Kommentiere ich den entsprechenden Aufruf aus (=OnPaint wird nicht mehr ausgeführt), erscheinen die Tooltips. Hängt das irgendwie zusammen? Puh, viele Fragen meinerseits, aber wenn ich für die Unterstützung nicht so dankbar wäre würde ich es sein lassen. Also nochmals danke!
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Ruler hat geschrieben: [...]
Das hast Du richtig vermutet, allerdings sollen die Dokumente wirklich nur als Punkte erscheinen, und die sind nun auch so wie sie sein sollen. Allerdings möchte ich andere Aktivitäten (Einnahmedauer von Medikamenten) als Zeitspanne mit HIlfe von Linien zeichnen. Dort ist mir nicht klar, wie ich Events "holen" kann; also wenn ich z.B. mit der Maus über einer mit DrawLine gezeichneten Linie bin. Wäre Klasse wenn sowas auch funktionieren würde. Die Frage ist, an was ich welchen Event binden soll?
Ich würde einfach beim Resizen die Koordinaten der Striche und Punkte berechnen und in einer Liste (z.B. als wx.Rect) speichern. Im Paint-Handler kann man dann anhand dieser Liste das Diagramm erstellen. Dann noch einen Handler für wx.EVT_MOTION (Details in der Doku) geschrieben, in dem anhand der Mausposition in der Liste nachgesehen wird, ob sich der Mauszeiger über einem der grafischen Symbole befindet.
Ruler hat geschrieben: Dann habe ich noch weitere Fragen... sorry aber ich hoffe ich mache dann weniger Fehler... Ich habe eine Klasse mit meinen Berechnungen, welche von einer anderen erbt (nur der Konstruktor):
[...]
Wenn ich nun eine OnPaint-Methode zum zeichnen verwenden, soll diese in der Klasse mit den Panels oder in der "Logik"-Klasse ausgeführt werden? Der Untscherschied leuchtet mir nicht ein.
Trenne auf jeden Fall die GUI von der Programmlogik. Versuche, das Objektmodell so zu gestalten, dass es für sich alleine lauffähig (d.h. nicht abhängig von der GUI) ist und auch so getestet werden kann. Wenn du Logik und GUI verzahnst schaffst du dir nur Probleme. Das ist deine Design-Entscheidung; technisch gesehen kannst deine Event-Handler unterbringen, wo du willst, auch in Klassen, die mit deiner Application gar nichts zu tun haben.
Ruler hat geschrieben: Was mir zudem noch aufgefallen ist: wenn ich den EVT_PAINT binde, werden bei mir Tooltips nicht angezeigt. Kommentiere ich den entsprechenden Aufruf aus (=OnPaint wird nicht mehr ausgeführt), erscheinen die Tooltips. Hängt das irgendwie zusammen?
Kannst du das auf ein lauffähiges Minimalbeispiel reduzieren? Dieses Verhalten kann ich bei mir (Ubuntu, Python2.6, wxWidgets2.8 ) nicht bestätigen.

Gruß
Norbert
Antworten