Skip to main content

A reusable tkinter/ttkbootstrap UI framework

Project description

Jabs Mimir

Jabs Mimir is a lightweight, extensible UI micro-framework built on top of tkinter and ttkbootstrap, designed for rapid internal tool development and structured form workflows.

It provides:

  • Reusable UI primitives with validation and tooltips
  • Support for block-based form components with dynamic variable binding
  • Integration with custom validation logic
  • Modular architecture suitable for internal tooling and small boilerplate projects

Installation

pip install jabs-mimir

Quick Start

from jabs_mimir import Mimir, DataBlockWrapper, UtilityModule
import tkinter as tk
import ttkbootstrap as tb

class App(tb.Window):
    def __init__(self):
        super().__init__(title="Mimir Demo")
        self.ui = Mimir(self)
        self.ui.setValidatorResolver(lambda name: {"not_empty": lambda val: bool(val.strip())}.get(name))

        self.ui.switchView(self.mainView)

    def mainView(self, ui, *args):
        frame = tb.Frame(self)
        fields = [
            {"type": "heading", "label": "Basic Info"},
            {"label": "Name", "key": "name", "variable": tk.StringVar(), "validation": "not_empty"},
            {"label": "Age", "key": "age", "variable": tk.IntVar()}
        ]

        meta = UtilityModule.buildBlockMeta(fields)
        block = DataBlockWrapper(meta)

        ui.renderFields(frame, fields)
        ui.addNextButton(frame, row=len(fields)+1, command=lambda: print(UtilityModule.getBlockValues(block)))

        return frame

if __name__ == "__main__":
    app = App()
    app.mainloop()

Components

Mimir

Manages UI views, tooltips, validation, field rendering, and form logic.

DataBlockWrapper

A wrapper for block-level form metadata and values. Supports dot-access and .get()/.set() calls.

UtilityModule

Helper methods for building field metadata, extracting values, validating blocks, etc.


📚 API Reference

Module Mimir

Abstract Mimir (updated): Reusable UI controller with abstract block rendering

"""

Abstract Mimir (updated): Reusable UI controller with abstract block rendering

"""

import tkinter as tk

import tkinter.font as tkFont

import tkinter.messagebox as msg

import ttkbootstrap as tb

from ttkbootstrap.constants import *

from ttkbootstrap.tooltip import ToolTip

from jabs_mimir.DataBlockWrapper import DataBlockWrapper



class Mimir:

    widthEntry = 20

    widthCombo = widthEntry - 2

    def __init__(self, app):

        """

        Initialize Mimir with reference to the root app window.

        """

        self.app = app

        self.currentView = None

        self.invalidEntries = set()

        self._validatorResolver = lambda name: None

        self.fieldRenderers = {

            "entry": self._renderEntryField,

            "combobox": self._renderComboboxField,

            "heading": self._renderHeading,

        }

        self._allowThemeToggle = False

    def allowDarkMode(self, enabled=True):

        """Enable or disable theme toggle button in the top right."""

        self._allowThemeToggle = enabled

    def _toggleTheme(self):

        """Switch between light and dark themes."""

        current = self.app.style.theme.name

        self.app.style.theme_use("darkly" if current != "darkly" else "cosmo")

    def setValidatorResolver(self, resolverFunc):

        """Register a function that returns validation callbacks by name."""

        self._validatorResolver = resolverFunc

    def switchView(self, newFrameFunc, gridOptions=None, **kwargs):

        """

        Replace the current view with a new frame built by newFrameFunc.

        Automatically configures geometry and grid placement.

        """        

        if self.currentView:

            self.currentView.destroy()

        container = tb.Frame(self.app)

        container.grid(row=0, column=0, sticky="nsew")

        container.columnconfigure(0, weight=1)

        topBar = tb.Frame(container)

        topBar.grid(row=0, column=0, sticky="ew")

        if self._allowThemeToggle:

            tb.Button(topBar, text="Theme", command=self._toggleTheme).pack(side="right", padx=10, pady=10)

        newFrame = newFrameFunc(self, **kwargs)

        options = gridOptions or {"row": 1, "column": 0, "sticky": "n", "padx": 50, "pady": 20}

        newFrame.grid(**options)

        self.app.rowconfigure(options.get("row", 0), weight=1)

        self.app.columnconfigure(options.get("column", 0), weight=1)

        self.currentView = newFrame

        self.app.update_idletasks()

        self.app.geometry("")

        return newFrame

    def popupView(self, viewFunc, title="Popup", size="fit", width=500, height=400, modal=False):

        """

        Open a popup window and embed a view frame into it.

        If `modal=True`, interaction with other windows is blocked.

        """        

        popup = tk.Toplevel(self.app)

        popup.title(title)

        popup.transient(self.app)

        popup.resizable(False, False)

        if size == "fit":

            popup.update_idletasks()

            popup.geometry("")

        else:

            popup.geometry(f"{width}x{height}")

        popup.grid_rowconfigure(0, weight=1)

        popup.grid_columnconfigure(0, weight=1)

        viewFrame = viewFunc(self, popup)

        viewFrame.grid(row=0, column=0, sticky="nsew")

        if modal:

            popup.grab_set()

            popup.focus_set()

        return popup

    def renderBlockUI(self, container, fields, blockList, layout="horizontal", meta=None, label=None, noRemove=False):

        """

        Render a labeled input block with specified fields and layout style.

        Automatically attaches the block to blockList and handles UI labels and deletion logic.

        """        

        index = len(blockList) + 1

        labelText = label or f"Block {index}"

        frame = tb.LabelFrame(container, text=labelText)

        frame.configure(labelanchor="n") 

        if layout == "vertical":

            frame.grid(row=index - 1, column=0, padx=10, pady=10, sticky="w")

        else:

            frame.grid(row=0, column=index - 1, padx=10, pady=10, sticky="n")

        boldFont = self.getCurrentFont()

        boldFont.configure(weight="bold")

        if meta is None:

            meta = {}

        if "custom_label" not in meta and label:

            meta["custom_label"] = label

        blockMeta = {

            "frame": frame,

            "fields": fields,

            "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f},

            "layout": layout,

            **meta

        }



        wrapper = DataBlockWrapper(blockMeta)

        blockList.append(wrapper)

        row = 0

        for field in fields:

            ftype = field.get("type", "entry")

            renderer = self.fieldRenderers.get(ftype)

            if renderer:

                renderer(frame, row, field)

                # Adjust row offset based on field type

                if ftype == "fileupload":

                    row += 3

                elif ftype == "heading":

                    row += 1

                else:

                    row += 2

        if not noRemove:

            removeBtn = tb.Button(

                frame,

                text="🗑️ Ta bort",

                bootstyle="danger-outline",

                command=lambda w=wrapper: self.removeBlock(blockList, w)

            )

            removeBtn.grid(row=row, column=0, columnspan=2, pady=(10, 5))

        return wrapper



    def registerFieldType(self, name, renderer):

        """Register a custom field renderer by name."""        

        self.fieldRenderers[name] = renderer

    def _renderEntryField(self, parent, row, field):

        """Internal: Render an entry field with label."""

        self.addLabeledEntry(

            parent=parent,

            row=row,

            label=field["label"],

            variable=field["variable"],

            validation=field.get("validation"),

            **field.get("options", {})  # <-- wildcard pass-through

        )

    def _renderComboboxField(self, parent, row, field):

        """Internal: Render a combobox field with label."""

        self.addLabeledCombobox(

            parent=parent,

            row=row,

            label=field["label"],

            variable=field["variable"],

            values=field["values"],

            **field.get("options", {})  # <-- pass all UI customization

        )

    def _renderHeading(self, parent, row, field):

        """Internal: Render a bold heading label within a form."""

        font = self.getCurrentFont()

        font.configure(weight="bold")

        align = field.get("options", {}).get("align", "w")  # default to left

        tb.Label(

            parent,

            text=field["label"],

            font=font

        ).grid(row=row, column=0, columnspan=2, pady=(10, 5), sticky=align)



    def _setupValidation(self, widget, validationName):

        """Bind focus-out validation using a named validation function."""

        fn = self._validatorResolver(validationName) if validationName else None

        if not callable(fn):

            return

        def validate(event):

            if fn(widget.get()):

                self.clearInvalid(widget)

            else:

                self.markInvalid(widget)

        widget.bind("<FocusOut>", validate)

    def markInvalid(self, widget):

        """Mark a widget as invalid with red border styling."""

        widget.config(bootstyle="danger")

        self.invalidEntries.add(widget)

    def clearInvalid(self, widget):

        """Clear error styling from a widget."""

        widget.config(bootstyle="none")

        self.invalidEntries.discard(widget)

    def addNextButton(self, parent, row, command, label="Nästa", tooltip=None, column=None, columnspan=None):

        """

        Add a 'Next' button to the given row. Automatically spans the full width

        of horizontal blocks if no column or columnspan is provided.

        Blocks execution if any fields are invalid.

        """        

        def wrapped():

            if self.invalidEntries:

                msg.showerror("Fel", "Alla fält måste vara giltiga innan du kan fortsätta.")

                return

            command()

        # Calculate default columnspan if not explicitly provided

        if column is None or columnspan is None:

            # Find all unique columns used in row=0 (block layout)

            used_columns = set()

            for child in parent.winfo_children():

                grid = child.grid_info()

                if grid.get("row") == 0 and "column" in grid:

                    used_columns.add(int(grid["column"]))

            if not used_columns:

                column = 0

                columnspan = 1

            else:

                column = 0

                columnspan = max(used_columns) + 1

        btn = tb.Button(parent, text=label, command=wrapped, bootstyle="success")

        btn.grid(row=row, column=column, columnspan=columnspan, pady=(10, 5))

        if tooltip:

            self.addTooltip(btn, tooltip)

        return btn



    def getCurrentFont(self):

        """Return the default font currently used by ttkbootstrap widgets."""

        style = tb.Style()

        fontName = style.lookup("TLabel", "font")

        return tkFont.Font(font=fontName)

    def addTooltip(self, widget, text):

        """Attach a styled tooltip to a widget."""

        ToolTip(widget, text, bootstyle=(SECONDARY, INVERSE))

    def removeBlock(self, blockList, wrapper):

        """

        Remove a UI block from view and list. Automatically reindexes

        and repositions remaining blocks.

        """        

        frame = wrapper.meta.get("frame")

        layout = wrapper.meta.get("layout", "horizontal")

        if frame:

            frame.destroy()

        blockList.remove(wrapper)

        for i, block in enumerate(blockList):

            frame = block.meta["frame"]

            new_label = f"Butik {i + 1}"

            # Update visual label

            frame.config(text=new_label)

            # Update meta so future logic knows the new label/index

            block.meta["custom_label"] = new_label

            block.meta["store_id"] = i + 1  # optional if store_id should follow display order

            # Reposition depending on layout

            if layout == "vertical":

                frame.grid_configure(row=i, column=0)

            else:

                frame.grid_configure(row=0, column=i)



    def addLabeledEntry(self, parent, row, label, variable, state="normal", tooltip=None, validation=None, column=0, vertical=False, columnspan=1):

        """

        Add a labeled entry input field. Supports vertical and horizontal layouts,

        optional tooltips and validation.

        """        

        if vertical:

            tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 5))

            entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry)

            entry.grid(row=row + 1, column=column, padx=5, columnspan=columnspan, pady=(0, 10), sticky="ew")

        else:

            tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e")

            entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry)

            entry.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew")

        if tooltip:

            self.addTooltip(entry, tooltip)

        validationFunc = self.resolveValidator(validation) if validation else None

        if callable(validationFunc):

            def onFocusOut(event):

                value = entry.get()

                if validationFunc(value):

                    self.clearInvalid(entry)

                else:

                    self.markInvalid(entry)

            entry.bind("<FocusOut>", onFocusOut)

        return entry

    def addLabeledCombobox(self, parent, row, label, variable, values, tooltip=None, state="readonly", column=0, vertical=False, columnspan=1):

        """Add a labeled combobox dropdown field with options."""

        if vertical:

            tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 0))

            combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo)

            combo.grid(row=row + 1, column=column, columnspan=columnspan, padx=5, pady=(0, 10))

        else:

            tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e")

            combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo)

            combo.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew")

        if tooltip:

            self.addTooltip(combo, tooltip)

        return combo

    def getNextAvailableRow(self, frame: tk.Widget) -> int:

        """

        Return the next available row in a container by checking occupied grid rows.

        """        

        used_rows = [

            child.grid_info().get("row")

            for child in frame.winfo_children()

            if "row" in child.grid_info()

        ]

        return max(used_rows, default=-1) + 1

Variables

ACTIVE
ALL
ANCHOR
ARC
BASELINE
BEVEL
BOTH
BOTTOM
BROWSE
BUTT
CASCADE
CENTER
CHAR
CHECKBUTTON
CHORD
COMMAND
CURRENT
DANGER
DARK
DEFAULT
DEFAULT_THEME
DETERMINATE
DISABLED
DOTBOX
E
END
EW
EXTENDED
FALSE
FIRST
FLAT
FULL
GROOVE
HEADINGS
HIDDEN
HORIZONTAL
INDETERMINATE
INFO
INSERT
INSIDE
INVERSE
LAST
LEFT
LIGHT
LINK
MITER
MOVETO
MULTIPLE
N
NE
NO
NONE
NORMAL
NS
NSEW
NUMERIC
NW
OFF
ON
OUTLINE
OUTSIDE
PAGES
PIESLICE
PRIMARY
PROJECTING
RADIOBUTTON
RAISED
READONLY
RIDGE
RIGHT
ROUND
S
SCROLL
SE
SECONDARY
SEL
SEL_FIRST
SEL_LAST
SEMI
SEPARATOR
SINGLE
SOLID
SQUARE
STRIPED
SUCCESS
SUNKEN
SW
TOGGLE
TOOLBUTTON
TOP
TREE
TREEHEADINGS
TRUE
TTK_ALT
TTK_CLAM
TTK_DEFAULT
UNDERLINE
UNITS
VERTICAL
W
WARNING
WORD
X
Y
YES

Classes

Mimir

class Mimir(
    app
)

Class variables

widthCombo
widthEntry

Methods

addLabeledCombobox

def addLabeledCombobox(
    self,
    parent,
    row,
    label,
    variable,
    values,
    tooltip=None,
    state='readonly',
    column=0,
    vertical=False,
    columnspan=1
)

Add a labeled combobox dropdown field with options.

    def addLabeledCombobox(self, parent, row, label, variable, values, tooltip=None, state="readonly", column=0, vertical=False, columnspan=1):

        """Add a labeled combobox dropdown field with options."""

        if vertical:

            tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 0))

            combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo)

            combo.grid(row=row + 1, column=column, columnspan=columnspan, padx=5, pady=(0, 10))

        else:

            tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e")

            combo = tb.Combobox(parent, textvariable=variable, values=values, state=state, width=self.widthCombo)

            combo.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew")

        if tooltip:

            self.addTooltip(combo, tooltip)

        return combo

addLabeledEntry

def addLabeledEntry(
    self,
    parent,
    row,
    label,
    variable,
    state='normal',
    tooltip=None,
    validation=None,
    column=0,
    vertical=False,
    columnspan=1
)

Add a labeled entry input field. Supports vertical and horizontal layouts,

optional tooltips and validation.

    def addLabeledEntry(self, parent, row, label, variable, state="normal", tooltip=None, validation=None, column=0, vertical=False, columnspan=1):

        """

        Add a labeled entry input field. Supports vertical and horizontal layouts,

        optional tooltips and validation.

        """        

        if vertical:

            tb.Label(parent, text=label).grid(row=row, column=column, columnspan=columnspan, padx=5, pady=(5, 5))

            entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry)

            entry.grid(row=row + 1, column=column, padx=5, columnspan=columnspan, pady=(0, 10), sticky="ew")

        else:

            tb.Label(parent, text=label).grid(row=row, column=column, padx=5, pady=5, sticky="e")

            entry = tb.Entry(parent, textvariable=variable, state=state, width=self.widthEntry)

            entry.grid(row=row, column=column + 1, columnspan=columnspan, padx=5, pady=5, sticky="ew")

        if tooltip:

            self.addTooltip(entry, tooltip)

        validationFunc = self.resolveValidator(validation) if validation else None

        if callable(validationFunc):

            def onFocusOut(event):

                value = entry.get()

                if validationFunc(value):

                    self.clearInvalid(entry)

                else:

                    self.markInvalid(entry)

            entry.bind("<FocusOut>", onFocusOut)

        return entry

addNextButton

def addNextButton(
    self,
    parent,
    row,
    command,
    label='Nästa',
    tooltip=None,
    column=None,
    columnspan=None
)

Add a 'Next' button to the given row. Automatically spans the full width

of horizontal blocks if no column or columnspan is provided. Blocks execution if any fields are invalid.

    def addNextButton(self, parent, row, command, label="Nästa", tooltip=None, column=None, columnspan=None):

        """

        Add a 'Next' button to the given row. Automatically spans the full width

        of horizontal blocks if no column or columnspan is provided.

        Blocks execution if any fields are invalid.

        """        

        def wrapped():

            if self.invalidEntries:

                msg.showerror("Fel", "Alla fält måste vara giltiga innan du kan fortsätta.")

                return

            command()

        # Calculate default columnspan if not explicitly provided

        if column is None or columnspan is None:

            # Find all unique columns used in row=0 (block layout)

            used_columns = set()

            for child in parent.winfo_children():

                grid = child.grid_info()

                if grid.get("row") == 0 and "column" in grid:

                    used_columns.add(int(grid["column"]))

            if not used_columns:

                column = 0

                columnspan = 1

            else:

                column = 0

                columnspan = max(used_columns) + 1

        btn = tb.Button(parent, text=label, command=wrapped, bootstyle="success")

        btn.grid(row=row, column=column, columnspan=columnspan, pady=(10, 5))

        if tooltip:

            self.addTooltip(btn, tooltip)

        return btn

addTooltip

def addTooltip(
    self,
    widget,
    text
)

Attach a styled tooltip to a widget.

    def addTooltip(self, widget, text):

        """Attach a styled tooltip to a widget."""

        ToolTip(widget, text, bootstyle=(SECONDARY, INVERSE))

allowDarkMode

def allowDarkMode(
    self,
    enabled=True
)

Enable or disable theme toggle button in the top right.

    def allowDarkMode(self, enabled=True):

        """Enable or disable theme toggle button in the top right."""

        self._allowThemeToggle = enabled

clearInvalid

def clearInvalid(
    self,
    widget
)

Clear error styling from a widget.

    def clearInvalid(self, widget):

        """Clear error styling from a widget."""

        widget.config(bootstyle="none")

        self.invalidEntries.discard(widget)

getCurrentFont

def getCurrentFont(
    self
)

Return the default font currently used by ttkbootstrap widgets.

    def getCurrentFont(self):

        """Return the default font currently used by ttkbootstrap widgets."""

        style = tb.Style()

        fontName = style.lookup("TLabel", "font")

        return tkFont.Font(font=fontName)

getNextAvailableRow

def getNextAvailableRow(
    self,
    frame: tkinter.Widget
) -> int

Return the next available row in a container by checking occupied grid rows.

    def getNextAvailableRow(self, frame: tk.Widget) -> int:

        """

        Return the next available row in a container by checking occupied grid rows.

        """        

        used_rows = [

            child.grid_info().get("row")

            for child in frame.winfo_children()

            if "row" in child.grid_info()

        ]

        return max(used_rows, default=-1) + 1

markInvalid

def markInvalid(
    self,
    widget
)

Mark a widget as invalid with red border styling.

    def markInvalid(self, widget):

        """Mark a widget as invalid with red border styling."""

        widget.config(bootstyle="danger")

        self.invalidEntries.add(widget)

popupView

def popupView(
    self,
    viewFunc,
    title='Popup',
    size='fit',
    width=500,
    height=400,
    modal=False
)

Open a popup window and embed a view frame into it.

If modal=True, interaction with other windows is blocked.

    def popupView(self, viewFunc, title="Popup", size="fit", width=500, height=400, modal=False):

        """

        Open a popup window and embed a view frame into it.

        If `modal=True`, interaction with other windows is blocked.

        """        

        popup = tk.Toplevel(self.app)

        popup.title(title)

        popup.transient(self.app)

        popup.resizable(False, False)

        if size == "fit":

            popup.update_idletasks()

            popup.geometry("")

        else:

            popup.geometry(f"{width}x{height}")

        popup.grid_rowconfigure(0, weight=1)

        popup.grid_columnconfigure(0, weight=1)

        viewFrame = viewFunc(self, popup)

        viewFrame.grid(row=0, column=0, sticky="nsew")

        if modal:

            popup.grab_set()

            popup.focus_set()

        return popup

registerFieldType

def registerFieldType(
    self,
    name,
    renderer
)

Register a custom field renderer by name.

    def registerFieldType(self, name, renderer):

        """Register a custom field renderer by name."""        

        self.fieldRenderers[name] = renderer

removeBlock

def removeBlock(
    self,
    blockList,
    wrapper
)

Remove a UI block from view and list. Automatically reindexes

and repositions remaining blocks.

    def removeBlock(self, blockList, wrapper):

        """

        Remove a UI block from view and list. Automatically reindexes

        and repositions remaining blocks.

        """        

        frame = wrapper.meta.get("frame")

        layout = wrapper.meta.get("layout", "horizontal")

        if frame:

            frame.destroy()

        blockList.remove(wrapper)

        for i, block in enumerate(blockList):

            frame = block.meta["frame"]

            new_label = f"Butik {i + 1}"

            # Update visual label

            frame.config(text=new_label)

            # Update meta so future logic knows the new label/index

            block.meta["custom_label"] = new_label

            block.meta["store_id"] = i + 1  # optional if store_id should follow display order

            # Reposition depending on layout

            if layout == "vertical":

                frame.grid_configure(row=i, column=0)

            else:

                frame.grid_configure(row=0, column=i)

renderBlockUI

def renderBlockUI(
    self,
    container,
    fields,
    blockList,
    layout='horizontal',
    meta=None,
    label=None,
    noRemove=False
)

Render a labeled input block with specified fields and layout style.

Automatically attaches the block to blockList and handles UI labels and deletion logic.

    def renderBlockUI(self, container, fields, blockList, layout="horizontal", meta=None, label=None, noRemove=False):

        """

        Render a labeled input block with specified fields and layout style.

        Automatically attaches the block to blockList and handles UI labels and deletion logic.

        """        

        index = len(blockList) + 1

        labelText = label or f"Block {index}"

        frame = tb.LabelFrame(container, text=labelText)

        frame.configure(labelanchor="n") 

        if layout == "vertical":

            frame.grid(row=index - 1, column=0, padx=10, pady=10, sticky="w")

        else:

            frame.grid(row=0, column=index - 1, padx=10, pady=10, sticky="n")

        boldFont = self.getCurrentFont()

        boldFont.configure(weight="bold")

        if meta is None:

            meta = {}

        if "custom_label" not in meta and label:

            meta["custom_label"] = label

        blockMeta = {

            "frame": frame,

            "fields": fields,

            "fields_by_key": {f["key"]: f["variable"] for f in fields if "key" in f},

            "layout": layout,

            **meta

        }



        wrapper = DataBlockWrapper(blockMeta)

        blockList.append(wrapper)

        row = 0

        for field in fields:

            ftype = field.get("type", "entry")

            renderer = self.fieldRenderers.get(ftype)

            if renderer:

                renderer(frame, row, field)

                # Adjust row offset based on field type

                if ftype == "fileupload":

                    row += 3

                elif ftype == "heading":

                    row += 1

                else:

                    row += 2

        if not noRemove:

            removeBtn = tb.Button(

                frame,

                text="🗑️ Ta bort",

                bootstyle="danger-outline",

                command=lambda w=wrapper: self.removeBlock(blockList, w)

            )

            removeBtn.grid(row=row, column=0, columnspan=2, pady=(10, 5))

        return wrapper

setValidatorResolver

def setValidatorResolver(
    self,
    resolverFunc
)

Register a function that returns validation callbacks by name.

    def setValidatorResolver(self, resolverFunc):

        """Register a function that returns validation callbacks by name."""

        self._validatorResolver = resolverFunc

switchView

def switchView(
    self,
    newFrameFunc,
    gridOptions=None,
    **kwargs
)

Replace the current view with a new frame built by newFrameFunc.

Automatically configures geometry and grid placement.

    def switchView(self, newFrameFunc, gridOptions=None, **kwargs):

        """

        Replace the current view with a new frame built by newFrameFunc.

        Automatically configures geometry and grid placement.

        """        

        if self.currentView:

            self.currentView.destroy()

        container = tb.Frame(self.app)

        container.grid(row=0, column=0, sticky="nsew")

        container.columnconfigure(0, weight=1)

        topBar = tb.Frame(container)

        topBar.grid(row=0, column=0, sticky="ew")

        if self._allowThemeToggle:

            tb.Button(topBar, text="Theme", command=self._toggleTheme).pack(side="right", padx=10, pady=10)

        newFrame = newFrameFunc(self, **kwargs)

        options = gridOptions or {"row": 1, "column": 0, "sticky": "n", "padx": 50, "pady": 20}

        newFrame.grid(**options)

        self.app.rowconfigure(options.get("row", 0), weight=1)

        self.app.columnconfigure(options.get("column", 0), weight=1)

        self.currentView = newFrame

        self.app.update_idletasks()

        self.app.geometry("")

        return newFrame

License

MIT License © 2025 William Lydahl

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jabs_mimir-0.3.22.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jabs_mimir-0.3.22-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file jabs_mimir-0.3.22.tar.gz.

File metadata

  • Download URL: jabs_mimir-0.3.22.tar.gz
  • Upload date:
  • Size: 18.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for jabs_mimir-0.3.22.tar.gz
Algorithm Hash digest
SHA256 403d8edebebd999f315733c884036b22c4f5e0d939b92dea58855c7d0b59aa5a
MD5 181102f38096c0bab46dd284499d703f
BLAKE2b-256 07d325cce915af71f83eea3d1f8d76b6ec32a40d2046865b2d30caae6a3fac80

See more details on using hashes here.

File details

Details for the file jabs_mimir-0.3.22-py3-none-any.whl.

File metadata

  • Download URL: jabs_mimir-0.3.22-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for jabs_mimir-0.3.22-py3-none-any.whl
Algorithm Hash digest
SHA256 47878063ef3f6b8ae02b1c9d687b5f17e5385054dd30dab1c9162021954cdb54
MD5 ddc8bb7767adb61b7a8e89d7371ca860
BLAKE2b-256 0bf9373fb5ae64af934aaeb9207686c90de2af74b594c08580d68c88a996bb5b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page