Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrolling window #74

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/tk_scrollable_frame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import tkinter as tk
import platform


# ************************
# Scrollable Frame Class
# ************************
class ScrollFrame(tk.Frame):
"""
This is based on the code from this GIST: https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.

This class provides a scrollable frame as a nearly drop-in replacement for a tkframe.

Add items to the ScrollFrame.viewPort, rather than to the ScrollFrame.
```
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.scrollFrame = ScrollFrame(self) # add a new scrollable frame.

# Now add some controls to the scrollframe.
# NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself)
for row in range(100):
a = row
tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1",
relief="solid").grid(row=row, column=0)
t = "this is the second column for row %s" % row
tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.printMsg("Hello " + str(x))).grid(
row=row, column=1)

# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort)
self.scrollFrame.pack(side="top", fill="both", expand=True)

def printMsg(self, msg):
print(msg)


if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
```
"""
def __init__(self, parent):
super().__init__(parent) # create a frame (self)

self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") # place canvas on self
self.viewPort = tk.Frame(self.canvas,
background="#ffffff") # place a frame on the canvas, this frame will hold the child widgets
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) # place a scrollbar on self
self.canvas.configure(yscrollcommand=self.vsb.set) # attach scrollbar action to scroll of canvas

self.vsb.pack(side="right", fill="y") # pack scrollbar to right of self
self.canvas.pack(side="left", fill="both", expand=True) # pack canvas to left of self and expand to fil
self.canvas_window = self.canvas.create_window((4, 4), window=self.viewPort, anchor="nw",
# add view port frame to canvas
tags="self.viewPort")

self.viewPort.bind("<Configure>",
self.onFrameConfigure) # bind an event whenever the size of the viewPort frame changes.
self.canvas.bind("<Configure>",
self.onCanvasConfigure) # bind an event whenever the size of the canvas frame changes.

self.viewPort.bind('<Enter>', self.onEnter) # bind wheel events when the cursor enters the control
self.viewPort.bind('<Leave>', self.onLeave) # unbind wheel events when the cursorl leaves the control

self.onFrameConfigure(
None) # perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize

def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox(
"all")) # whenever the size of the frame changes, alter the scroll region respectively.

def onCanvasConfigure(self, event):
'''Reset the canvas window to encompass inner frame when required'''
canvas_width = event.width
self.canvas.itemconfig(self.canvas_window,
width=canvas_width) # whenever the size of the canvas changes alter the window region respectively.

def onMouseWheel(self, event): # cross platform scroll wheel event
if platform.system() == 'Windows':
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
elif platform.system() == 'Darwin':
self.canvas.yview_scroll(int(-1 * event.delta), "units")
else:
if event.num == 4:
self.canvas.yview_scroll(-1, "units")
elif event.num == 5:
self.canvas.yview_scroll(1, "units")

def onEnter(self, event): # bind wheel events when the cursor enters the control
if platform.system() == 'Linux':
self.canvas.bind_all("<Button-4>", self.onMouseWheel)
self.canvas.bind_all("<Button-5>", self.onMouseWheel)
else:
self.canvas.bind_all("<MouseWheel>", self.onMouseWheel)

def onLeave(self, event): # unbind wheel events when the cursor leaves the control
if platform.system() == 'Linux':
self.canvas.unbind_all("<Button-4>")
self.canvas.unbind_all("<Button-5>")
else:
self.canvas.unbind_all("<MouseWheel>")
22 changes: 15 additions & 7 deletions src/user_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import tkinter as tk
from tkinter import filedialog, ttk
from tk_scrollable_frame import ScrollFrame
import typing as tp
import platform

Expand Down Expand Up @@ -442,7 +443,7 @@ def show_exit_button_and_wait(self):
sys.exit(0)


class MainWindow:
class MainWindow(tk.Frame):
root: tk.Tk
input_folder: Path
output_folder: Path
Expand All @@ -469,21 +470,25 @@ def __init__(self):
app.iconbitmap(iconpath)

app.protocol("WM_DELETE_WINDOW", self.__on_close)
app.minsize(500,600)

# Scrollable Inner Frame
self.scroll = ScrollFrame(app)

self.__input_folder_picker = InputFolderPickerWidget(
app, self.__on_update)
self.__answer_key_picker = AnswerKeyPickerWidget(app, self.__on_update)
self.scroll.viewPort, self.__on_update)
self.__answer_key_picker = AnswerKeyPickerWidget(self.scroll.viewPort, self.__on_update)
self.__arrangement_map_picker = ArrangementMapPickerWidget(
app, self.__on_update)
self.scroll.viewPort, self.__on_update)
self.__output_folder_picker = OutputFolderPickerWidget(
app, self.__on_update)
self.scroll.viewPort, self.__on_update)

self.__status_text = tk.StringVar()
status = tk.Label(app, textvariable=self.__status_text)
status = tk.Label(self.scroll.viewPort, textvariable=self.__status_text)
status.pack(fill=tk.X, expand=1, pady=(YPADDING * 2, 0))
self.__on_update()

buttons_frame = tk.Frame(app)
buttons_frame = tk.Frame(self.scroll.viewPort)

# "Open Help" Button
pack(ttk.Button(buttons_frame,
Expand All @@ -509,6 +514,9 @@ def __init__(self):
side=tk.RIGHT)
pack(buttons_frame, fill=tk.X, expand=1)

# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort)
self.scroll.pack(side="top", fill="both", expand=True)

self.__ready_to_continue = tk.IntVar(name="Ready to Continue")
app.wait_variable("Ready to Continue")

Expand Down
2 changes: 1 addition & 1 deletion test/end-to-end/150q-core/output/results.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Q1,Q2,Q3,Q4,Q5
,,,,12345678,,a.jpg,A,B,B,D,E
,,,,12334235,,b.jpg,A,,C,D,E
,,,,12345678,,a.jpg,A,B,B,D,E
2 changes: 1 addition & 1 deletion test/end-to-end/150q-core/output/scores.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Total Score (%),Total Points,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10,Q11,Q12,Q13,Q14,Q15,Q16,Q17,Q18,Q19,Q20,Q21,Q22,Q23,Q24,Q25,Q26,Q27,Q28,Q29,Q30,Q31,Q32,Q33,Q34,Q35,Q36,Q37,Q38,Q39,Q40,Q41,Q42,Q43,Q44,Q45,Q46,Q47,Q48,Q49,Q50,Q51,Q52,Q53,Q54,Q55,Q56,Q57,Q58,Q59,Q60,Q61,Q62,Q63,Q64,Q65,Q66,Q67,Q68,Q69,Q70,Q71,Q72,Q73,Q74,Q75,Q76,Q77,Q78,Q79,Q80,Q81,Q82,Q83,Q84,Q85,Q86,Q87,Q88,Q89,Q90,Q91,Q92,Q93,Q94,Q95,Q96,Q97,Q98,Q99,Q100,Q101,Q102,Q103,Q104,Q105,Q106,Q107,Q108,Q109,Q110,Q111,Q112,Q113,Q114,Q115,Q116,Q117,Q118,Q119,Q120,Q121,Q122,Q123,Q124,Q125,Q126,Q127,Q128,Q129,Q130,Q131,Q132,Q133,Q134,Q135,Q136,Q137,Q138,Q139,Q140,Q141,Q142,Q143,Q144,Q145,Q146,Q147,Q148,Q149,Q150
,,,,12345678,,a.jpg,80.0,4,1,1,0,1,1
,,,,12334235,,b.jpg,80.0,4,1,0,1,1,1
,,,,12345678,,a.jpg,80.0,4,1,1,0,1,1
2 changes: 1 addition & 1 deletion test/end-to-end/rotation/output/results.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Q1,Q2,Q3,Q4,Q5
,,,,12334235,,90deg.jpg,A,,C,D,E
,,,,12334235,,180deg.jpg,A,,C,D,E
,,,,12334235,,270deg.jpg,A,,C,D,E
,,,,12334235,,90deg.jpg,A,,C,D,E