Scrollbar scrollt nicht ganz nach oben.

Fragen zu Tkinter.
Antworten
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
habe das Problem, dass die Y-Scrollbar nicht ganz bis oben, beim Start scrollt.
Es handelt sich dabei um die Ausgabe einer Tabelle.
Es fehlt immer ein kleiner Tick, bis oben. Mit der Maus kann ich zwar manuell, dann den Rest nach oben scrollen, nervt aber einfach, dass man das machen muss.
Mit der Scrollbar selbst, habe ich keine Probleme, funktioniert alles prima, außer diesem kleinen Problem.
Hätte gern ein Bild zur Anschauung eingestellt, weiß aber nicht, wie das mit "Bild einfügen" geht.

Ich habe auch schon versucht, nach dem Erstellen des Fensters mit:
self.canvas.yview_scroll(-1, 'unit')
weiter nach oben zu scrollen, lleider passiert da aber nichts.

Ist Euch vielleicht diese Phänomen auch bekannt?
Wenn ja, an was könnte es liegen?

Grüße Nobuddy
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Bitte poste ein Beispielprogramm, anhand dessen man Dein Problem nachvollziehen kann.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Versuche ich zu machen, wird aber etwas dauern, da die verschiedenen Komponenten ausgelagert sind.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Habe es geschafft. Code ist lauffähig und das betreffende Problem, ist zu erkennen.

Code: Alles auswählen

import tkinter as tk

class WindowBuilding(object):

	def __init__(self, parent, task):

		# Ermittle die Größe des Bildschirmes
		screenx = parent.win.winfo_screenwidth()
		screeny = parent.win.winfo_screenheight()
		parent.bg_scroll = 'green'
		parent.scrollactiv = 'red'
		parent.scrolltrough = 'yellow'
		parent.ypos = 0
		parent.xpos = 0
		# Initializing root and create canvas and frame.
		AutoScrollBar(parent)
		task()
		# Upate root and output window.
		OutputWindow(parent)


class ControlAutoScrollbar(tk.Scrollbar):

	def set(self, low, high):
		if float(low) <= 0.0 and float(high) >= 1.0:
			self.tk.call("grid", "remove", self)
		else:
			self.grid()
		tk.Scrollbar.set(self, low, high)

	def pack(self, **kw):
		raise (TclError,"pack cannot be used with this widget")

	def place(self, **kw):
		raise (TclError, "place cannot be used  with this widget")


class AutoScrollBar(object):

	def __init__(self, parent):

		yscrollbar = ControlAutoScrollbar(parent.win,
			bg=parent.bg_scroll, activebackground=parent.scrollactiv,
			troughcolor=parent.scrolltrough)
		yscrollbar.grid(row=0, column=1, sticky=tk.NS)
		parent.yscrollbar_width = yscrollbar.winfo_reqwidth()

		xscrollbar = ControlAutoScrollbar(parent.win,
			orient=tk.HORIZONTAL,  bg=parent.bg_scroll,
			activebackground=parent.scrollactiv,
			troughcolor=parent.scrolltrough)
		xscrollbar.grid(row=1, column=0, sticky=tk.EW)
		parent.xscrollbar_height = xscrollbar.winfo_reqheight()

		parent.canvas = tk.Canvas(parent.win,
			yscrollcommand=yscrollbar.set, xscrollcommand=xscrollbar.set)
		parent.canvas.grid(row=0, column=0, sticky=tk.NSEW)

		yscrollbar.config(command=parent.canvas.yview)
		xscrollbar.config(command=parent.canvas.xview)

		parent.win.grid_rowconfigure(0, weight=1)
		parent.win.grid_columnconfigure(0, weight=1)

		parent.frame = tk.Frame(parent.canvas)
		parent.frame.rowconfigure(1, weight=1)
		parent.frame.columnconfigure(1, weight=1)


class OutputWindow(object):

	def __init__(self, parent):
		# Integrate widgets in canvas.
		parent.canvas.create_window(0, 0, anchor=tk.NW, window=parent.frame)
		# Update frame.
		parent.frame.update_idletasks()
		# Configure scrollregion in canvas.
		parent.canvas.config(scrollregion=parent.canvas.bbox(tk.ALL))

		try:
			window = parent.win
		except AttributeError:
			window = parent.window
		# Ermittle die Größe des Bildschirmes
		screenx = parent.win.winfo_screenwidth()
		screeny = parent.win.winfo_screenheight()
		x, y, w, h = parent.canvas.bbox(tk.ALL)
		try:
			w += (parent.xscrollbar_height + 3)
		except AttributeError:
			parent.xscrollbar_height = 0
		if w > screenx:
			w = screenx - parent.xscrollbar_height
		try:
			h += (parent.yscrollbar_width)
		except AttributeError:
			parent.yscrollbar_width = 0
		if h > screeny:
			h = screeny - parent.yscrollbar_width
		# Zentriere das Fenster auf dem Bildschirm
		x, y, hdw, hdh = ScreenCenter(screenx, screeny, w, h).screen
		window.geometry('{}x{}+{}+{}'.format(x, y, hdw, hdh))
		window.update()
		# Create window.
		parent.win.mainloop()
		return


class ScreenCenter(object):
	"""
	Zentrieren des Fensters
	"""

	def __init__(self, screenx, screeny, xpos, ypos):

		# Berechne die halbe Differenz zwischen Bildschirm und Fenster
		x, y = 0, 0
		if ((screenx - xpos) / 2) > 0:
			x = round((screenx - xpos) / 2)
		half_diff_hight = 0
		if ((screeny - ypos) / 2) > 0:
			y = round((screeny - ypos) / 2)
		# Werte für das Zentrieren das Fensters auf dem Bildschirm
		self.screen = xpos, ypos, x, y
		return


def main():

	import tkinter as tk

	class start(object):

		def __init__(self):

			# Create window by Tk or Toplevel.
			self.win = tk.Tk()
			# Upate root and output window.
			WindowBuilding(self, self.task)

		def task(self):
			"""
			Create Widgets
			"""
			head_label = [str(i) for i in range(50)]
			dataset = ['Das ist Value {} von Datensatz.'.format(i)
				for i in range(50)]
			self.ypos = 0
			for y, text in enumerate(head_label):
				self.xpos = 0
				x = 0
				width = 10
				var = tk.StringVar(value = text)
				label = tk.Label(self.canvas, width=width, bd=1,
					highlightthickness=1, textvariable=var, anchor=tk.NW)
				label.grid(row=y, column=x, padx=5, pady=3)
				label.width = label.winfo_reqwidth()
				label.height = label.winfo_reqheight()
				label.width = label.winfo_reqwidth()
				self.xpos += label.width
				self.canvas.create_window(self.xpos, self.ypos,
					window=label, anchor=tk.E)
				x += 1
				width = 30
				var = tk.StringVar(value = dataset[y])
				label = tk.Label(self.canvas, width=width, bd=1,
					highlightthickness=1, textvariable=var, anchor=tk.NW)
				label.grid(row=y, column=x, padx=5, pady=3)
				label.height = label.winfo_reqheight()
				label.width = label.winfo_reqwidth()
				self.ypos += label.height
				self.xpos += label.width
				self.canvas.create_window(self.xpos, self.ypos,
					window=label, anchor=tk.E)
				if y == 0:
					self.last_widget = label
					label.focus_set()
	start()

if __name__ == '__main__':
	main()
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Habe einen Fehler im unteren Bereich gefunden und berichtigt.
Jetzt tritt das Problem in dem Beispiel nicht mehr auf.
Der Fehler ist bei mir leider noch vorhanden, glaube aber da könnt Ihr mir nicht dabei helfen, sofern Ihr nicht meinen kompletten Code habt. :|

Hier die Korrektur:

Code: Alles auswählen

def main():

	import tkinter as tk

	class start(object):

		def __init__(self):

			# Create window by Tk or Toplevel.
			self.win = tk.Tk()
			self.win.title('TITEL')
			# Upate root and output window.
			WindowBuilding(self, self.task)

		def task(self):
			"""
			Create Widgets
			"""
			head_label = [str(i) for i in range(50)]
			dataset = ['Das ist Value {} von Datensatz.'.format(i)
				for i in range(50)]
			self.ypos = 0
			for y, text in enumerate(head_label):
				self.xpos = 0
				x = 0
				width = 10
				var = tk.StringVar(value = text)
				label = tk.Label(self.canvas, width=width, bd=1,
					highlightthickness=1, textvariable=var, anchor=tk.NW)
				label.grid(row=y, column=x, padx=5, pady=3)
				label.width = label.winfo_reqwidth()
				label.height = label.winfo_reqheight()
				label.width = label.winfo_reqwidth()
				self.ypos += label.height
				self.xpos += label.width
				self.canvas.create_window(self.xpos, self.ypos,
					window=label, anchor=tk.E)
				x += 1
				width = 30
				var = tk.StringVar(value = dataset[y])
				label = tk.Label(self.canvas, width=width, bd=1,
					highlightthickness=1, textvariable=var, anchor=tk.NW)
				label.grid(row=y, column=x, padx=5, pady=3)
				label.height = label.winfo_reqheight()
				label.width = label.winfo_reqwidth()
				self.xpos += label.width
				self.canvas.create_window(self.xpos, self.ypos,
					window=label, anchor=tk.E)
				if y == 0:
					self.last_widget = label
					label.focus_set()
	start()

if __name__ == '__main__':
	main()
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Mein Problem, habe ich inzwischen gelöst. :)
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: bei 881 Beiträgen hier im Forum ist es sehr unwahrscheinlich, dass nicht alle Anmerkungen schomal bei Dir gelandet sind, aber hier nochmal, in der Hoffnung dass es irgend wann einmal fruchtet:
Eingerückt wird immer mit 4 Leerzeichen pro Eben, KEINE Tabs.
`WindowBuilding` ist keine Klasse, es besteht nur aus __init__ indem nicht einmal self benutzt wird.
Dass hier ausschließlich mit `parent` gearbeitet wird, sagt aber eindeutig, dass das alles in der __init__-Methode der `parent`-Klasse stehen sollte.
`screenx` und `screeny` werden nicht benutzt.
Dass weitere Klasseninstanze erzeugt werden, ohne dass sie einer Variable zugewiesen werden oder sonst was damit gemacht wird, deutet auf einen Designfehler hin, später wohl noch mehr dazu.
In `ControlAutoScrollbar.set` wird tk.call benutzt, warum nicht direkt grid_remove?
raise sollte eine Exception-Instanz bekommen, kein Tuple. Woher hast Du denn sowas?
`AutoScrollBar` setzt Attribute in `parent`. Das ist total unübersichtlich, verhindert, dass man mehrere dieser Objekte im selben Fenster verwenden kann. Üblicherweise sind komplexere Widgets von Frame abgeleitet, die man dann im `parent` beliebig plazieren kann. `AutoScrollBar` ist dann der falsche Name, weil es sich ja nicht nur um Scrollbars handelt, sondern tatsächlich ein AutoScrollFrame ist.
`OutputWindow` ist dann wieder keine Klasse, weil wieder in __init__ nichteinmal `self` benutzt wird.
Das ganz gehört also wieder in die __init__-Methode der `parent`-Klasse.
Das was bei Dir an `parent.frame` gebunden ist, wird in `AutoScrollBar` erzeugt, dann aber erst in `OutputWindow` ins Canvas gesetzt. Sehr unübersichtlich. In wirklichkeit wird der Frame dann aber gar nicht benutzt.
`AttributeError` deutet normalerweise auf Programmierfehler hin, hier ist es ein Zeichen dafür, dass Du die Übersicht verloren hast, ob Attribute existieren, oder nicht.
Der ganze Code zum Ermitteln der Bildschirmgröße gehört gelöscht. Es ist Aufgabe des Fenstermanagers (der die Präferenzen des Nutzers umsetzt) ein Fenster zu platzieren, die einzelnen Programm sollten es nicht besser wiessen wollen.
`mainloop` sollte nicht in `__init__` stehen, sondern, denn dann läuft __init__ ewig, man hat also nie ein fertig initialisierte Klasse; der Kommentar `# Create window.` ist zudem noch falsch.
Die Klasse `ScreenCenter` zentriert gar kein Fenster (was auch Aufgabe einer Funktion wäre und nicht einer Klasse), ist wieder keine richtige Klasse, sollte eine Funktion mit dem Namen `get_screen_center` sein, fliegt aber eh raus, weil wie schon geschrieben, Fenster nicht explizit platziert werden.

Die Klasse `start` ist nicht nur falsch geschrieben, `start` ist auch kein Name für eine Klasse. Importe gehören an den Anfang der Datei, nicht in Funktionen, und Klasse gehören auch nicht in Funktionen.
`xpos` und `ypos` sind keine Attribute, weil sie nur innerhalb von `task` in einer Schleife benutzt werden.
Man speichert nicht zusammengehörende Daten, wie Label und Dataset in zwei Listen, sondern in einer Liste.
Die StringVar-Objekte werden gar nicht benutzt, sind eh nur Konstanten.
Die grid-Aufrufe sind überflüssig, da Du ja create_window benutzt. Würdest Du anchor=NW benutzen, wäre das die obere Linke Ecke zur Positionierung, was viel intuitiver wäre als die Mitte der rechten Seite.

Code: Alles auswählen

import tkinter as tk


class ControlAutoScrollbar(tk.Scrollbar):
    def set(self, low, high):
        if float(low) <= 0.0 and float(high) >= 1.0:
            self.grid_remove()
        else:
            self.grid()
        tk.Scrollbar.set(self, low, high)

    def pack(self, **kw):
        raise TclError("pack cannot be used with this widget")

    def place(self, **kw):
        raise TclError("place cannot be used  with this widget")


class AutoScrollFrame(tk.Frame):

    def __init__(self, parent, bg_scroll='green', scrollactiv='red', scrolltrough='yellow'):
        tk.Frame.__init__(self, parent.win)
        yscrollbar = ControlAutoScrollbar(self,
            bg=bg_scroll, activebackground=scrollactiv,
            troughcolor=scrolltrough)
        yscrollbar.grid(row=0, column=1, sticky=tk.NS)

        xscrollbar = ControlAutoScrollbar(self,
            orient=tk.HORIZONTAL,  bg=bg_scroll,
            activebackground=scrollactiv,
            troughcolor=scrolltrough)
        xscrollbar.grid(row=1, column=0, sticky=tk.EW)

        self.canvas = tk.Canvas(self,
            yscrollcommand=yscrollbar.set, xscrollcommand=xscrollbar.set)
        self.canvas.grid(row=0, column=0, sticky=tk.NSEW)

        yscrollbar.config(command=self.canvas.yview)
        xscrollbar.config(command=self.canvas.xview)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

    def update_size(self):
        self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))


class Application(object):

    def __init__(self):
        # Create window by Tk or Toplevel.
        self.win = tk.Tk()
        self.last_widget = None
        # Initializing root and create canvas and frame.
        self.scrollframe = AutoScrollFrame(self)
        self.scrollframe.pack(fill=tk.BOTH, expand=True)
        self.create_tasks()
        self.scrollframe.update_size()

    def create_tasks(self):
        """
        Create Widgets
        """
        canvas = self.scrollframe.canvas
        datasets = [
            (str(i), f'Das ist Value {i} von Datensatz.')
            for i in range(10)
        ]
        ypos = 0
        for label_text, dataset_text in datasets:
            label_head = tk.Label(canvas, width=10, bd=1,
                highlightthickness=1, text=label_text, anchor=tk.NW)
            label_data = tk.Label(canvas, width=30, bd=1,
                highlightthickness=1, text=dataset_text, anchor=tk.NW)
            width = label_head.winfo_reqwidth()
            canvas.create_window(0, ypos, window=label_head, anchor=tk.NW)
            canvas.create_window(width, ypos, window=label_data, anchor=tk.NW)
            if self.last_widget is None:
                self.last_widget = label_data
                label_data.focus_set()
            ypos += label_head.winfo_reqheight()

def main():
    application = Application()
    application.win.mainloop()

if __name__ == '__main__':
    main()
Zum Problem: ein Canvas kann beliebig in die positive und negative Richtung gehen, und da Du die Scrollbars an die Canvas-Größe anpasst, ging im ersten Programm der Scrollbar von -0.5 * label_height bis (n - 0.5) * label_height, so dass bei der default-Scrollbar-Position 0 nach oben noch ein bißchen Platz war.
Ein deinem zweiten Programm geht jetzt der Scrollbar von + 0.5 * label_height bis (n + 0.5) * label_height. Jetzt liegt der default-Wert 0 außerhalb des Scrollbar-Bereichs, wird also automatisch auf den kleinsten Wert gesetzt, also 0.5 * label_height, was jetzt den Anschein hat, dass alles paßt.
Das ist aber nicht der Fall, denn in beiden Fällen bist Du ein halbes Label daneben.
Antworten