Widgets#
Widgets extend Tkinter with compound components. Import from VIStk.Widgets.
TabBar#
TabBar(Frame) — A row of clickable tabs displayed at the top of a TabManager. Each tab
has a label button and a close button (✕). A thin vertical separator divides adjacent tabs. Tabs
can be reordered by dragging, detached into their own window, or merged into another TabBar.
TabBar is created automatically by TabManager.__init__ and exposed as
host.TabManager.tab_bar. You do not normally need to interact with it directly.
Interaction model#
Action |
Behaviour |
|---|---|
Click |
Focuses the tab. |
Close button (✕) |
Closes the tab. |
Right-click |
Context menu with Open in new window, Force refresh, and Close. |
Drag (≥ 8 px) |
Shows a semi-transparent ghost window following the cursor; a thin blue insertion indicator appears in the hovered bar showing where the tab will land. |
Release over the same bar |
Reorders the tab to the indicated position. |
Release over another bar |
Merges the tab into that bar. |
Release outside all bars |
Detaches the tab into a new |
Attributes#
Attribute |
Type |
Description |
|---|---|---|
|
|
Name of the currently focused tab. |
|
|
The |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Methods#
Method |
Returns |
Description |
|---|---|---|
|
|
Add a tab. Does nothing if already open. Returns |
|
|
Remove the tab. Returns |
|
|
Set |
|
|
Return whether a tab with |
|
|
Return the 0-based position, or |
|
— |
Show the blue insertion indicator at position |
|
— |
Hide the insertion indicator. |
|
— |
Deregisters from |
Styling (0.6.2)#
The tab bar’s colours and shape come from a named style (see
VIStk.Styles), which the end user picks in Settings > Appearance > Tab
style. Four looks ship: classic (the historical grey bar), underline
(flat tabs, accent bar under the active one), topline (fill cue + accent
bar on top), and pill (a rounded accent capsule). Styling is process-wide
class state — the methods below are classmethods that apply live to every
open bar and become the style for new ones. Configure them once at startup from
Screens/styles.py (imported by the Host before the first window opens), or
call them at runtime.
Method |
Description |
|---|---|
|
Switch to a built-in style by name ( |
|
Recolour the chrome — shorthand for building a palette and passing it
to |
|
Curate which style names the Settings dropdown offers, and set the fallback default. |
|
Register a custom |
setPalette colour roles: bar (the tab-strip background), tab (an
unselected tab), selected (the selected tab), text (label + ✕ colour on
every tab), close (the ✕ close-button highlight), selected_text
(label + ✕ on the selected tab only). For anything past these six — surfaces,
body text, fields, buttons — build a Palette instead (see
Palettes (0.6.5)).
The strip does not paint bar directly — it paints one of four states
(focused, unfocused, empty, empty_hover), so those are shaded
from the bar colour whenever it is set. A pane that is unfocused or holds
no tabs therefore keeps the app’s bar colour instead of reverting to the
scheme grey. The same derivation applies to a TabStyle whose palette
overrides bar_bg; naming any of the four states explicitly wins over the
derived value.
# Screens/styles.py — runs once at startup, before the first window opens
from VIStk.Widgets import TabBar
TabBar.setStyle("pill")
TabBar.setPalette(bar="#dddddd", tab="#f6f6f6", selected="#5d9edc",
text="#0000cd", close="#cd0000")
Palettes (0.6.5)#
A palette is an open name -> colour mapping. The names are the app’s
own, and widgets refer to them where a colour is expected:
v_label = Label(f_cc, text="Part", bg="page", fg="muted")
There is no fixed schema to map onto: name the colours you care about, and
anything you leave out falls through to a base palette (the shipped
light unless told otherwise). The chrome is not special — the tab bar
reads bar_bg / tab_active / tab_fg out of the same mapping, so
restyling it is just naming those colours. A tab style is the shape; a
palette is the colour, and they compose.
The end user picks in Settings > Appearance > Colour palette.
Palettes belong to the project, which also owns the Settings the user’s
pick is stored in. Every method below is a Project classmethod, so
Screens/styles.py calls them without instantiating (and re-parsing
project.json); Project().registerPalette(...) on a held instance is
identical.
Method |
Description |
|---|---|
|
Register (or replace) a |
|
Curate the Settings dropdown, and set the app default. |
|
The curated list / every registered name. |
|
The registered |
|
Declare the app’s look — a registered name, or a |
|
Repaint the chrome with name now. |
|
The resolved |
|
The pair |
TabBar keeps setDefaultPalette / setActivePalette /
activePalette as the repaint mechanism the Project methods delegate to.
Precedence. The app’s default is what a user who has never chosen sees; a
stored appearance.color_scheme pick overrides it, and clearing the pick
returns to the default. Only a genuine override counts as a pick, so an
untouched install wears the app’s colours.
Names the chrome reads (VIStk.Styles.CHROME_NAMES): bar_bg,
tab_active, tab_inactive, tab_hover, close_hover,
separator, accent, empty, empty_hover, focused,
unfocused, active_unfocused, tab_drag, tab_fg,
tab_active_fg, info_bg, info_fg. The shipped palettes also carry a
general-UI set (surface, surface_alt, border, text,
text_muted, field, button, selection …) which is what
ttk styling reads. Nothing is enforced — these are just the names VIStk
itself looks up, and an app’s own names sit beside them.
Naming bar_bg also shades the strip’s own focused / unfocused /
empty / empty_hover states from it, so an emptied or unfocused pane
keeps the app’s colour. A "$name" value copies another colour of the
result.
# Screens/styles.py
from VIStk.Structures._Project import Project
Project.registerPalette("bmi Light", {
"page": wColors.Lowlight.Button,
"card": wColors.White,
"ink": wColors.Black,
"muted": wColors.Grey.Light,
"bar_bg": wColors.Lowlight.Button.Highlight, # the chrome, same mapping
})
Project.registerPalette("bmi Dark", {
"page": wColors.Grey.Dark,
"card": wColors.Grey,
"ink": wColors.White.Dark.Light,
"muted": wColors.White.Dark.Dark,
"bar_bg": wColors.Grey,
}, base="dark")
Project.offerPalettes(["bmi Light", "bmi Dark"], default="bmi Light")
Project.setSystemPalettes("bmi Light", "bmi Dark") # what "system" means
"system" reads the real OS light/dark preference (Windows
AppsUseLightTheme, macOS AppleInterfaceStyle, Linux gsettings) and
is polled while the app runs, so an OS theme change repaints live. The poll is
armed only while the active pick is "system".
Widget theming (0.6.5)#
Any widget may name a palette colour where a colour is expected, and VIStk resolves the name as the widget is built:
v_label = Label(f_cc, text="Part", bg="page", fg="muted")
VIStk wraps tkinter.BaseWidget.__init__ — the single seam every classic
Tk widget passes through — and swaps the name for the colour before Tk
creates the widget, so the widget is built once, with real colours, exactly as
if they had been written literally. Nothing is configured afterwards.
The rule is simply what the value is:
a
#rrggbbliteral (or a non-string such as awColor) is a colour — passed straight through, never tracked, never repainted;any other string is a name — resolved through the palette;
a name the palette doesn’t define is left for Tk, which accepts its own colour names (
"red") and rejects the rest with a normal error;options that don’t take colours are never looked at, so
text="page"stays the word “page”.
What each option was named is kept on the widget:
v_label._vcolors.bg # -> "page"
v_label._vcolors.as_dict() # -> {"bg": "page", "fg": "muted"}
which is the whole switching mechanism: changing palette re-resolves those names. There is nothing to compare and nothing to do per frame — a widget that never named a colour is never touched.
Method |
Description |
|---|---|
|
Leave one widget out of repainting — for a widget whose colours something else owns. |
|
Choose the |
|
Every |
ttk is a different animal. ttk widgets accept no bg/fg at all; they
are styled through ttk.Style from the same palette, and how much lands
depends on the base theme — which is the developer’s choice, defaulting to
the platform’s own:
vista/xpnative(the Windows default) —ttk.Frameandttk.Labelfollow the palette, so layout surfaces stay on-theme, butButton,Entry,Combobox,Notebooktabs,TreeviewandScrollbarare drawn by the Windows theme engine and ignore colour options. That is below Tk; interception cannot reach it.clam/alt/default— Tk-drawn throughout, so every ttk control takes the palette, at the cost of the native appearance.
Classic tk widgets name their colours under any theme. The shipped
light palette carries the Windows/Tk defaults (SystemButtonFace,
SystemWindow, black text, the shell selection blue) under the general-UI
names, so a widget naming surface or field looks like an unstyled Tk
widget.
Registry#
All live TabBar instances are tracked in VIStk.Widgets._TabBar._TABBAR_REGISTRY. This
list is used during drag motion to detect cross-bar merges.
SplitView#
SplitView(Frame) — A tree-of-panes container that allows the Host (or DetachedWindow) content
area to be divided into multiple panes, each with its own TabManager and TabBar. Panes are
separated by draggable sashes.
Each SplitView instance holds a root widget that is either a single TabManager (no split)
or a _SplitNode wrapping a ttk.PanedWindow with two child slots. Each slot is either a
TabManager (leaf) or another _SplitNode (branch), forming an arbitrary binary tree.
Import: from VIStk.Widgets._SplitView import SplitView
Key methods#
Method |
Description |
|---|---|
|
Split pane into two side-by-side panes. direction is |
|
Collapse pane out of the tree, promoting the surviving sibling. If the root becomes a
single |
|
Walk the tree and return all leaf |
|
Aggregate |
|
Locate which |
|
Store a callback dict and apply to all current and future |
Focus tracking#
focused_pane(property) — theTabManagerthe user last interacted with.Clicking anywhere inside a pane (including child widgets like buttons) sets that pane as focused via a toplevel-level
<Button-1>binding._global_focused_pane(class attribute) — tracks the last-focused pane across all windows (Host and DetachedWindows). Used byHost._open_tab()to open new tabs in the correct pane.When a window loses OS focus, all pane focus indicators dim. They restore on
<FocusIn>.
Drag-to-split#
Dragging a tab into the outer 25% of any pane’s content area shows a translucent blue overlay (
Toplevelwithalpha=0.22) indicating the split direction.Dragging to the center shows a full-pane overlay; dropping there adds the tab to that pane.
detect_drop_zone(x_root, y_root)— returns(pane, direction)orNone.detect_any_drop_zone(x_root, y_root)— class method that checks all registered SplitViews, respecting window z-order viawm stackorder.lift_window_at(x_root, y_root)— class method that lifts the target window to the front when the cursor enters its non-overlapping area during a drag.
Cross-window support#
All live SplitView instances are tracked in SplitView._registry (class-level list).
This enables cross-window drag-to-split: a tab dragged from one window can be dropped into a
split zone in another window.
When windows overlap, only the frontmost window at the cursor position shows drop zones.
The stacking order is determined by Tk’s wm stackorder command.
InfoRow#
InfoRow(Frame) — A slim status bar packed at the bottom of the Host window. Created
automatically by Host.__init__ and exposed as host.InfoRow.
Zone |
Content |
|---|---|
Left |
Active screen name and version, updated on tab focus change. |
Centre |
Project copyright string (static, set at startup). |
Right |
App version and live FPS counter, e.g. |
The copyright string is normalised at construction: if it does not already contain ©, the
current year and © are automatically prepended.
Methods#
Method |
Description |
|---|---|
|
Update the screen label. Pass empty strings to clear. |
|
Update the FPS counter. Called by |
InfoRow is managed entirely by Host — you do not need to call its methods directly.
ScrollableFrame#
ScrollableFrame(ttk.Frame) — A frame with a vertical scrollbar. Content is placed inside
scrollable_frame. Mouse wheel scrolling activates when the cursor enters the frame and
deactivates when it leaves.
from VIStk.Widgets import ScrollableFrame
sf = ScrollableFrame(parent)
sf.pack(fill=BOTH, expand=True)
# Place content inside scrollable_frame, not sf directly
Label(sf.scrollable_frame, text="Item 1").pack()
Label(sf.scrollable_frame, text="Item 2").pack()
Attributes#
Attribute |
Type |
Description |
|---|---|---|
|
|
The underlying canvas that enables scrolling. |
|
|
The vertical scrollbar. |
|
|
The inner frame — place all content here. |
Note
All child widgets must be placed inside sf.scrollable_frame, not inside sf itself.
QuestionWindow#
QuestionWindow(SubRoot) — A configurable dialog window with a question and one or more
response buttons. Centers on the parent window.
from VIStk.Widgets import QuestionWindow
dlg = QuestionWindow(
question="Save changes before closing?",
answer="yn",
parent=root,
ycommand=save_and_close
)
Constructor#
Parameter |
Type |
Description |
|---|---|---|
|
|
Text to display. A list creates one label per item. |
|
|
A string of character codes defining the buttons (see below). |
|
|
The window to center on. |
|
|
Function called when an affirmative button is clicked. The window is destroyed first. |
|
|
Values for a dropdown ( |
Answer codes#
Code |
Button Text |
Action |
|---|---|---|
|
Yes |
Destroys window, calls |
|
No |
Destroys window. |
|
Return |
Destroys window. |
|
Continue |
Destroys window, calls |
|
Back |
Destroys window. |
|
Close |
Destroys window. |
|
Confirm |
Destroys window, calls |
|
(dropdown) |
|
Examples#
# Yes / No
QuestionWindow("Delete this record?", "yn", root, ycommand=delete_record)
# Confirm / Back
QuestionWindow(["Are you sure?", "This cannot be undone."], "cb", root, ycommand=proceed)
# Multi-line with dropdown
QuestionWindow("Select output format:", "dx", root, droplist=["PDF", "CSV", "JSON"])
WarningWindow#
WarningWindow(QuestionWindow) — A modal warning dialog with a single “Continue” button.
from VIStk.Widgets import WarningWindow
WarningWindow("File not found.", parent=root)
The window is automatically made modal (modalize()), blocking input to the parent until
dismissed. Use for non-recoverable error messages where the user must acknowledge before
continuing.
Tooltip (0.5.0)#
Hover tooltip bound to any widget. Tkinter has no native tooltip.
from VIStk.Widgets import Tooltip
Tooltip(my_button, text="Save the current document")
text may be a string or a zero-arg callable for state-dependent
tooltips (re-evaluated each show). Cleans up its after callback on
widget destroy.
Keyword args: delay_ms=500, wraplength=240, background,
foreground, borderwidth.
CollapsibleFrame (0.5.0)#
Frame whose body is hidden under a header button. Pack children into
cf.body (NOT directly into the frame).
from VIStk.Widgets import CollapsibleFrame
cf = CollapsibleFrame(parent, text="Advanced", expanded=False)
cf.pack(fill="x")
ttk.Entry(cf.body).pack()
cf.expanded_var is a BooleanVar callers can bind to share state
or persist it. Methods: expand(), collapse(), toggle(),
set_expanded(bool), set_text(str).
AutocompleteEntry (0.5.0)#
ttk.Entry with a filtered dropdown Listbox of suggestions.
from VIStk.Widgets import AutocompleteEntry
AutocompleteEntry(parent, values=["Boston", "Chicago", ...]).pack()
values may be an iterable or a callable (text) -> iterable
(use the callable form for dynamic lookups).
Keyword args: max_results=8, case_sensitive=False,
match="prefix" (or "contains").
Keyboard: Up/Down move, Return accepts, Tab accepts the
first match, Escape closes the popup.
DateEntry (0.5.0)#
Date input with format validation and a calendar-picker popup. No third-party dependencies.
from VIStk.Widgets import DateEntry
de = DateEntry(parent, date_format="%Y-%m-%d")
de.pack()
de.get() returns date | None. de.set(d) sets
programmatically. Invalid manual input reverts to the last valid value
on focus-out. Keyword args include initial: date | None,
on_change: callable, entry_width: int.
Since 0.6.2 the calendar popup flips to open above the entry when opening below would spill past the bottom of the containing window (or screen) — e.g. a date field near the bottom of a form — falling back to below when there is no room above either.
confirm / confirm_discard (0.5.0)#
Drop-in modal helpers so screens stop reimplementing
tkinter.messagebox.
from VIStk.Widgets import confirm, confirm_discard
if confirm(parent, title="Delete?", message="Really delete?"):
...
choice = confirm_discard(parent, name="Work Order #12345")
if choice == "cancel":
return False # veto on_quit
if choice == "save":
_save()
return True
Both dialogs centre on the parent via WindowGeometry.center_on
(no flicker), are modal/transient, and return plain values
(bool for confirm; "save" | "discard" | "cancel" for
confirm_discard). Closing the window or pressing Escape returns
the negative outcome.
SettingsWindow / SettingsTab (0.6.0)#
The built-in application-settings surface: a ttk.Notebook whose first tab
(“General”) edits the framework’s window / host / appearance / notification
preferences, backed by Project.Settings. Both classes share one builder
(_SettingsUI) and render the identical surface — they differ only in where it
lives.
Class |
Role |
|---|---|
|
The surface as a tab module — duck-typed like a screen ( |
|
The original modal |
Neither is normally constructed by application code — Host opens the surface from
its framework-provided menu entry.
Behaviour#
Controls are seeded from
ProjectSettings.effective(), so each shows its current effective value (default or override).On Save, a control whose value equals the framework default is written as a reset rather than a redundant override, so
settings.jsonstays minimal; the rest are stored and flushed with a singleProjectSettings.save().Blank numeric fields mean “unset”; non-numeric or negative input is rejected silently in favour of the stored value rather than corrupting it.
SettingsTab.setupmay run more than once for one logical tab (VIStk re-runs it when a tab is dragged between panes), so it rebuilds and re-seeds from scratch each time — unsaved edits are dropped on a move, the same “closing discards” contract as the modal window’s Cancel.Appearance settings are persisted but applied on next launch.
Application panels#
Apps contribute their own tabs with host.register_settings_panel(name, setup_fn).
Each setup_fn receives the tab frame and builds into it, mirroring a screen’s
setup. A panel that raises shows an inline error in its own tab rather than a blank
one.
setup_fn may take one argument (the frame — the panel manages its own
persistence) or two (frame, ui), where ui is the settings surface itself. The
two-argument form lets a panel build fields that ride the window’s own Save / Restore
Defaults:
def build_panel(frame, ui):
ui.add_check(frame, 0, "Enable fast mode", "myapp.fast_mode")
ui.add_int(frame, 1, "Row height", "myapp.row_height", "px")
ui.add_combo(frame, 2, "Units", "myapp.units", ["mm", "in"])
host.register_settings_panel("My App", build_panel)
Method |
Description |
|---|---|
|
Checkbox bound to boolean setting key. |
|
Spinbox bound to integer setting key (blank = unset/default). hint is grey trailing text, e.g. a unit. |
|
Combobox bound to string setting key. |
Fields built through these helpers are registered in the surface’s read-back registry,
so they are saved, restored and defaulted exactly like the General tab — no per-panel
Save button, and no apply-on-change surprise. The ProjectSettings storage API and
Host.register_settings_panel are covered in Structures and Objects.
v-prefixed widgets (0.6.0)#
A family of widgets that subclass the classic tk widgets rather than ttk, so
per-instance bg / fg / font actually work, and that add two things on top:
Parent-property inheritance — each widget declares which visual options it inherits; any the caller omits are filled in from the parent at construction. Explicitly-passed options always win. A
vLabeldropped into a white frame is white, not default grey.Optional rounded corners — opt in with
radius. This consolidates the hand-rolled Canvas-polygon “pill / chip / card” pattern that screens otherwise reimplement one at a time.
At radius=0 (the default) every one of them is a plain native widget with no extra
machinery — they are drop-in replacements.
Hierarchy#
vWidget is a pure mixin (it subclasses object, never tk.Widget) combined with
a native widget through multiple inheritance, so a vLabel genuinely is both a
vWidget and a tk.Label. vWidget.__init__ runs first in the MRO — it computes
inheritance and pops the rounded kwargs — then defers to the native base through
cooperative super().__init__(), so the Tcl widget is created exactly once.
vLabel(RoundedLeaf, vWidget, Label)
vButton(RoundedLeaf, vWidget, Button)
vCheckbutton(vWidget, Checkbutton)
vImage(vWidget, Label)
vFrame(RoundedContainer, vWidget, LayoutFrame)
vLabelFrame(RoundedContainer, vWidget, LabelFrame)
RoundedLeaf and RoundedContainer are internal mixins holding the two rounding
strategies. They are mixed in before vWidget so their render hooks win the MRO.
Leaves round by painting the fill into the widget’s single image slot (or, when the
caller needs that slot for their own image=, by overlaying corner tiles); containers
round with a lowered background label plus a child inset. You do not import them
directly — subclass vWidget if you need a further v-widget of your own.
Radius semantics#
The radius is resolved by effective_radius(radius, style, w, h) from the live
size inside every render path, not once at construction — so a percentage radius stays
correct through resizes, and the result is always clamped to half the short side. A
radius can therefore never exceed what the widget can actually show.
Common behaviour#
The parent’s background is followed live. Recolour a parent and its v-children re-inherit on their own — a rounded child re-blends its corners too — cascading to any depth. The parent does not have to be a v-widget: a plain
Frame,LayoutFrameorLabelpropagates just the same, since the child is what does the inheriting. Options you set explicitly are never touched, so an explicitbgboth holds its own colour and stops the cascade for its subtree.Every other inherited option is a snapshot taken at construction. Call
refresh()after the parent’s font or foreground changes to re-pull them.Runtime recolouring works —
configure(bg=...)repaints the rounded corners live;vButtonalso repaints onstate.The resize repaint is clobber-proof. It lives on a dedicated bindtag rather than an instance binding, so your own
widget.bind("<Configure>", fn)withoutadd="+"cannot silently replace it and leave the widget rendering square.Parents are read via
cgetfor classic widgets andStyle().lookupfor ttk ones, falling back toSystemButtonFace.help(vLabel)lists every native tk option too: the native option block is lifted from tkinter’s own__init__docstring at class-creation time, and editors see the same set throughUnpack[TypedDict]hints.
Note
Corner blending assumes a solid-colour parent — the area outside the arc is painted with the parent’s background so it blends in. On a gradient or image background the corners will not disappear.
Rounded-image helpers#
Exported for direct use when you need the artwork without a v-widget.
Function |
Description |
|---|---|
|
Anti-aliased rounded rectangle as a raw |
|
Same arguments; wraps the result in a |
|
Resolve a |
|
Return img with anti-aliased transparent rounded corners and an optional stroked outline. The mask is multiplied into the image’s own alpha, so existing PNG transparency survives. |
vLabel (0.6.0)#
vLabel(RoundedLeaf, vWidget, Label) — a tk.Label that inherits background,
foreground and font from its parent and can be rounded.
from tkinter import Frame
from VIStk.Widgets import vLabel
pane = Frame(root, bg="white")
vLabel(pane, text="Hello").pack() # bg/fg/font inherited
vLabel(pane, text="Pill", bg="#2f78d3", fg="white",
radius=100, radius_style="percent").pack() # full pill
vLabel(pane, text="Item", image=icon,
compound="left", radius=8).pack() # icon laid out natively
Constructor: vLabel(master=None, *, radius=0, radius_style="pixels", outline=None,
outline_width=1, corner_bg=None, **label_options).
A text-only label paints the rounded fill into its image slot and draws the text over
it, so the text is never covered at any radius — circles included. Passing your own
image= switches to corner-tile rounding, which leaves the native image slot free so
image / compound / anchor behave exactly as on a native Label. The tiles
are opaque, so keep the radius modest in that mode.
vFrame (0.6.0)#
vFrame(RoundedContainer, vWidget, LayoutFrame) — a LayoutFrame (so it keeps the
.Layout helper; see Objects) that inherits background only — frames have
no fg/font — and can be rounded.
from VIStk.Widgets import vFrame, vLabel
card = vFrame(root, bg="white", radius=14) # white card on a grey parent
card.place(relx=.1, rely=.1, relwidth=.8, relheight=.8)
card.Layout.colSize([1.0]); card.Layout.rowSize([1.0])
vLabel(card, text="Inside").place(card.Layout.cell(1, 1))
Constructor adds inset to the shared set:
Argument |
Type |
Description |
|---|---|---|
|
|
Content inset ( |
The frame’s bg is the fill. A Tk child is an opaque rectangle with no per-widget
transparency, so a child reaching a rounded corner would square it off; that is why every
child is inset — under place, pack, grid and .Layout.cell alike, with
the caller’s own padding preserved. The inset is invisible when the child shares the
frame’s fill, which is the inherited default.
Children added at runtime, after the frame has been sized, are picked up on the next
resize; call refresh() to re-inset them immediately.
vLabelFrame (0.6.0)#
vLabelFrame(RoundedContainer, vWidget, LabelFrame) — a drop-in tk.LabelFrame
(text, labelanchor, labelwidget, relief, bd … all work) that inherits
background, foreground and font, carries a .Layout like vFrame, and
takes the same inset argument.
from VIStk.Widgets import vLabelFrame, vLabel
box = vLabelFrame(root, text="Tooling", radius=12, outline="#c8ccd2")
box.place(x=20, y=20, width=260, height=180)
box.Layout.colSize([1.0]); box.Layout.rowSize([1.0])
vLabel(box, text="Inside").place(box.Layout.cell(1, 1))
A rounded box almost always wants an outline (or a fill that contrasts the parent) —
the native rectangular border is flattened in rounded mode, so without one the only thing
drawn is the title.
The title needs special handling: a LabelFrame lays its children out in a content
area below the title band, so a background image placed there would miss the top and
bottom border. In rounded mode the background is floated across the whole frame
instead, and the title is routed through a labelwidget — an internal Label
mirroring your text / fg / font, or the labelwidget you supply — which Tk
still positions per labelanchor and which is lifted above the background, so the
title breaks the rounded border exactly like a native one. configure(text=...),
cget("text") and box["text"] (plus fg / foreground / font)
transparently proxy to that title. None of this applies at radius=0.
vImage (0.6.0)#
vImage(vWidget, Label) — an image-only widget, the mirror of vLabel’s “text
with an optional image”. Path resolution and loading are delegated to VIMG, so
Project().p_images lookup, the glob fallback and absolute_path behave exactly as
everywhere else; vImage owns only the Tk rendering. It inherits background only.
from VIStk.Widgets import vImage
# Contain a logo in the widget, letterboxed with the inherited bg
vImage(pane, "logo").place(relwidth=1, relheight=1)
# A fixed-size rounded thumbnail
vImage(pane, "avatar.png", size=(64, 64), radius=12).pack()
Constructor: vImage(master=None, path=None, *, image=None, absolute_path=False,
size=None, fit=True, resample=Resampling.BICUBIC, radius=0, radius_style="pixels",
outline=None, outline_width=1, **label_options).
Argument |
Type |
Description |
|---|---|---|
|
|
Image path resolved by |
|
|
An in-memory image to display instead of loading from disk. Takes precedence over path. |
|
|
Treat path as a literal filesystem path (no |
|
|
Fixed pixel box to fit into. Omit to fit the live widget size, re-fitting on every resize. |
|
|
|
|
|
PIL resampling filter, default |
Methods: set_path(path, *, absolute_path=False) swaps the source from disk and
repaints; set_image(pil_image) swaps to an in-memory image with no disk load. The
loaded VIMG (when there is one) is exposed as .VIMG.
Rounding here is done image-side rather than with the shared machinery: an anti-aliased
mask makes the corners genuinely transparent, so the widget’s inherited bg shows
through and re-blends on a bg change with no re-render. A percentage radius resolves
against the rendered image, so it tracks the picture as it is re-fitted.