Skip to content

functions

Various tkinter functions

about_menu(root=None)

About menu items

Source code in mmg_toolbox/tkguis/misc/functions.py
def about_menu(root: tk.Misc | None = None):
    """About menu items"""
    menu = {
        'Help': {
            'Docs': open_docs,
            'About': lambda: popup_about(root)
        }
    }
    return menu

capture(root, filename='img.png')

Take screenshot of the passed widget. Untested!

Source code in mmg_toolbox/tkguis/misc/functions.py
def capture(root: tk.Misc, filename='img.png'):
    """Take screenshot of the passed widget. Untested!"""

    x0 = root.winfo_rootx()
    y0 = root.winfo_rooty()
    x1 = x0 + root.winfo_width()
    y1 = y0 + root.winfo_height()

    im = ImageGrab.grab(bbox=(x0, y0, x1, y1))  # bbox means boundingbox, which is shown in the image below
    im.save(filename)  # Can also say im.show() to display it

check_new_file(parent, filename=None)

Check if filename is new and writable

Source code in mmg_toolbox/tkguis/misc/functions.py
def check_new_file(parent, filename: str | None = None) -> str | None:
    """Check if filename is new and writable"""
    path, name = os.path.split(filename)
    if not os.access(path, os.W_OK):
        show_error(f"new file cannot be written as path is not writable: '{path}'",
                   parent=parent, raise_exception=False)
        return None

    if os.path.exists(filename):
        if os.access(filename, os.W_OK):
            answer = messagebox.askyesnocancel(
                title='Create File',
                message=f"Overwrite {name}? Yes to overwrite or No to create new file.",
                parent=parent
            )
            if answer is None:
                return None
            if answer:
                return filename
        # amend name
        name, ext = os.path.splitext(name)
        n_tries = 1
        while os.path.exists(filename):
            if n_tries > 100:
                return None
            filename = os.path.join(path, name + str(n_tries) + ext)
            n_tries += 1
    return filename

config_menu(root, config)

Config menu items

Source code in mmg_toolbox/tkguis/misc/functions.py
def config_menu(root: tk.Misc, config: dict) -> dict:
    """Config menu items"""
    from ..apps.config_editor import ConfigEditor
    from .config import reset_config
    menu = {
        'Config': {
            'Edit Config.': lambda: ConfigEditor(root, config),
            'Reset Config.': lambda: reset_config(config),
        }
    }
    return menu

copy_image_to_clipboard(root, canvas)

copy tk canvas to clipboard. Untested!

Source code in mmg_toolbox/tkguis/misc/functions.py
def copy_image_to_clipboard(root: tk.Misc, canvas: tk.Canvas):
    """copy tk canvas to clipboard. Untested!"""

    # https://www.tutorialspoint.com/how-to-copy-a-picture-from-tkinter-canvas-to-clipboard
    import io
    from PIL import Image

    # Retrieve the image from the canvas
    canvas_image = canvas.postscript()

    # Create an in-memory file-like object
    image_buffer = io.BytesIO()

    # Save the canvas image to the buffer in PNG format
    image = Image.open(canvas_image)
    image.save(image_buffer, format="PNG")
    image_buffer.seek(0)

    # Copy the image to the clipboard
    root.clipboard_clear()
    root.clipboard_append(image_buffer, format="image/png")

create_scrollable_window(root, scroll_x=True, scroll_y=True, width=None, height=None)

Create scrollable window

Parameters:

Name Type Description Default
root Misc

tk.Misc

required
scroll_x

bool

True
scroll_y

bool

True
width int | None

int

None
height int | None

int

None

Returns:

Type Description
Frame

ttk.Frame

Source code in mmg_toolbox/tkguis/misc/functions.py
def create_scrollable_window(root: tk.Misc, scroll_x=True, scroll_y=True,
                             width: int | None = None, height: int | None = None) -> ttk.Frame:
    """
    Create scrollable window

    :param root: tk.Misc
    :param scroll_x: bool
    :param scroll_y: bool
    :param width: int
    :param height: int
    :return: ttk.Frame
    """

    # --- Container ---
    container = ttk.Frame(root)
    container.pack(fill="both", expand=True)

    # --- Canvas ---
    canvas = tk.Canvas(container, width=width, height=height)
    canvas.grid(row=0, column=0, sticky="nsew")

    # --- Scrollbars ---
    if scroll_y:
        scroll_y = ttk.Scrollbar(container, orient="vertical", command=canvas.yview)
        scroll_y.grid(row=0, column=1, sticky="ns")
        canvas.configure(yscrollcommand=scroll_y.set)
    if scroll_x:
        scroll_x = ttk.Scrollbar(container, orient="horizontal", command=canvas.xview)
        scroll_x.grid(row=1, column=0, sticky="ew")
        canvas.configure(xscrollcommand=scroll_x.set)

    # --- Inner frame (THIS HOLDS YOUR PLOTS) ---
    inner_frame = ttk.Frame(canvas)

    # Add frame to canvas
    canvas.create_window((0, 0), window=inner_frame, anchor="nw")

    # --- Resize logic ---
    def on_configure(event):
        canvas.configure(scrollregion=canvas.bbox("all"))

    inner_frame.bind("<Configure>", on_configure)

    # Optional: make canvas expand properly
    container.rowconfigure(0, weight=1)
    container.columnconfigure(0, weight=1)
    return inner_frame

entry_with_placeholder(root, text, placeholder_text, **kwargs)

Create an entry widget with placeholder text

Source code in mmg_toolbox/tkguis/misc/functions.py
def entry_with_placeholder(root: tk.Misc, text: tk.Variable, placeholder_text: str, **kwargs) -> ttk.Entry:
    """Create an entry widget with placeholder text"""

    def on_focus_in(event):
        if entry.get() == placeholder_text:
            entry.delete(0, tk.END)
            entry.config(fg="black")

    def on_focus_out(event):
        if entry.get() == "":
            entry.insert(0, placeholder_text)
            entry.config(fg="grey")

    entry = tk.Entry(root, textvariable=text, fg="grey", **kwargs)
    entry.insert(0, placeholder_text)

    entry.bind("<FocusIn>", on_focus_in)
    entry.bind("<FocusOut>", on_focus_out)
    return entry

open_close_all_tree(treeview, branch='', openstate=True)

Open or close all items in ttk.treeview

Source code in mmg_toolbox/tkguis/misc/functions.py
def open_close_all_tree(treeview, branch="", openstate=True):
    """Open or close all items in ttk.treeview"""
    treeview.item(branch, open=openstate)
    for child in treeview.get_children(branch):
        open_close_all_tree(treeview, child, openstate)  # recursively open children

open_docs()

Open web-browser at docs site

Source code in mmg_toolbox/tkguis/misc/functions.py
def open_docs():
    """Open web-browser at docs site"""
    import webbrowser
    webbrowser.open_new_tab("https://diamondlightsource.github.io/mmg_toolbox/")

popup_about(root=None)

Create about message

Source code in mmg_toolbox/tkguis/misc/functions.py
def popup_about(root: tk.Misc | None = None):
    """Create about message"""
    from mmg_toolbox import version_info, module_info, title
    msg = (
        f"{version_info()}\n\n" +
        "A selection of useful functions and methods for the mmg beamlines at Diamond" +
        "\n\n" +
        f"Module Info:\n{module_info()}\n\n" +
        "By Dan Porter, Diamond Light Source Ltd"
    )
    if root is not None:
        msg += f"\n\nScreen size: {root.winfo_screenwidth()}x{root.winfo_screenheight()}"
    messagebox.showinfo(
        title=f"About: {title()}",
        message=msg,
        parent=root,
    )

post_right_click_menu(menu, xpos, ypos)

Post menu on arrow position

Source code in mmg_toolbox/tkguis/misc/functions.py
def post_right_click_menu(menu: tkinter.Menu, xpos: int, ypos: int):
    """Post menu on arrow position"""

    def destroy(evt):
        menu.unpost()

    try:
        menu.bind('<FocusOut>', destroy)
        menu.tk_popup(xpos, ypos)
    finally:
        menu.grab_release()

select_folder(parent, initial_directory=None)

Select folder

Source code in mmg_toolbox/tkguis/misc/functions.py
def select_folder(parent, initial_directory: str | None = None):
    """Select folder"""
    foldername = filedialog.askdirectory(
        initialdir=initial_directory,
        title='Select folder...',
        mustexist=True,
        parent=parent,
    )
    return foldername

select_hdf_file(parent, initial_directory=None)

Select HDF file using filedialog

Source code in mmg_toolbox/tkguis/misc/functions.py
def select_hdf_file(parent, initial_directory: str | None = None) -> str | None:
    """Select HDF file using filedialog"""
    from h5py import is_hdf5
    filename = filedialog.askopenfilename(
        title='Select file to open',
        filetypes=[('NXS file', '.nxs'),
                   ('HDF file', '.h5'), ('HDF file', '.hdf'), ('HDF file', '.hdf5'),
                   ('All files', '.*')],
        parent=parent,
        initialdir=initial_directory
    )
    if filename and not is_hdf5(filename):
        messagebox.showwarning(
            title='Incorrect File Type',
            message=f"File: \n{filename}\n can't be read by h5py",
            parent=parent
        )
        filename = None
    return filename

show_error(message, parent=None, raise_exception=True)

Display and raise error

Source code in mmg_toolbox/tkguis/misc/functions.py
def show_error(message, parent=None, raise_exception=True):
    """Display and raise error"""
    messagebox.showwarning(
        title="HDF File Error",
        message=message,
        parent=parent,
    )
    if raise_exception:
        raise Exception(message)

topmenu(root, menu_dict, add_themes=False, add_about=False, config=None)

Add a file menu to root

Parameters:

Name Type Description Default
root RootWithStyle

tkinter root

required
menu_dict dict

{Menu name: {Item name: function}}

required
add_themes

add themes menu item

False
add_about

add about menu item

False
config dict | None

add Config menu if config dict is added

None

Returns:

Type Description

None

Source code in mmg_toolbox/tkguis/misc/functions.py
def topmenu(root: RootWithStyle, menu_dict: dict, add_themes=False, add_about=False,
            config: dict | None = None):
    """
    Add a file menu to root
    :param root: tkinter root
    :param menu_dict: {Menu name: {Item name: function}}
    :param add_themes: add themes menu item
    :param add_about: add about menu item
    :param config: add Config menu if config dict is added
    :return: None
    """
    if config is not None:
        menu_dict.update(config_menu(root, config))
    if add_themes and hasattr(root, 'style'):
        menu_dict.update(theme_menu(root.style))
    if add_about:
        menu_dict.update(about_menu(root))

    def add_menu(menu: tk.Menu, **this_menu_dict):
        for name, item in this_menu_dict.items():
            if type(item) is dict:
                new_menu = tk.Menu(menu, tearoff=False)
                add_menu(new_menu, **item)
                menu.add_cascade(label=name, menu=new_menu)
            else:
                menu.add_command(label=name, command=item)

    menubar = tk.Menu(root)
    add_menu(menubar, **menu_dict)
    root.config(menu=menubar)

treeview_sort_column(treeview, col, reverse, sort_col=None)

Function to sort columns in ttk.Treeview, tree.heading("#0", command=lambda _col="#0": treeview_sort_column(tree, _col, False))

Parameters:

Name Type Description Default
treeview Treeview

ttk.Treeview instance

required
col str

str, column specifier for items to sort

required
reverse bool

Bool, sort direction

required
sort_col str | None

str or None, sort alternative column

None

Returns:

Type Description
Source code in mmg_toolbox/tkguis/misc/functions.py
def treeview_sort_column(treeview: ttk.Treeview, col: str, reverse: bool, sort_col: str | None = None):
    """
    Function to sort columns in ttk.Treeview,
        tree.heading("#0", command=lambda _col="#0": treeview_sort_column(tree, _col, False))
    :param treeview: ttk.Treeview instance
    :param col: str, column specifier for items to sort
    :param reverse: Bool, sort direction
    :param sort_col: str or None, sort alternative column
    :return:
    """
    if sort_col is None:
        sort_col = col
    if col == "#0":
        def item(iid):
            return treeview.item(iid)['text']
    else:
        def item(iid):
            return treeview.set(iid, col)

    items = [(item(iid), iid) for iid in treeview.get_children('')]
    items.sort(reverse=reverse)

    # rearrange items in sorted positions
    for index, (val, k) in enumerate(items):
        treeview.move(k, '', index)
        if treeview.item(k)['text'] == '..':  # keep at top of column
            treeview.move(k, '', 0)

    # reverse sort next time
    treeview.heading(sort_col, command=lambda _col=col: treeview_sort_column(treeview, _col, not reverse, sort_col))