# Joufflu WPF Components - full documentation > Every documentation page of Joufflu concatenated in one file. Index with one-line summaries: https://raw.githubusercontent.com/ndegheselle/Joufflu/main/docs/llms.txt --- # Joufflu WPF Components **A modern WPF component library that makes your desktop apps look good by default.** WPF inputs and reusable components for .NET, built on a design system of themed brushes, dimensions and layout helpers. Every control reads its colours through `DynamicResource`, so the whole UI re-themes live between Light and Dark β€” no restart, no flicker. These pages mirror the controls in the **`Joufflu.Samples`** gallery app β€” same titles, descriptions and code snippets. Each control page lists example markup you can paste into your views. {: .note } > **Using an LLM agent?** Point it at the machine-readable > [`llms.txt`](https://raw.githubusercontent.com/ndegheselle/Joufflu/main/docs/llms.txt) > index β€” a single file linking every page's raw Markdown source, or > [`llms-full.txt`](https://raw.githubusercontent.com/ndegheselle/Joufflu/main/docs/llms-full.txt) > for every page in one file. Use those raw URLs rather > than the rendered site, which can return `403` to automated fetchers. ## Highlights - πŸŒ— **Live Light / Dark theming** β€” flip the theme at runtime and every control follows instantly. - 🎨 **A real design system, built to be customized** β€” semantic colours, dimensions, sizing and spacing exposed as override-able resource keys; the gallery's **Customize theme** window (palette button in its title bar) edits them live and generates the dictionary. - 🧩 **Ready-to-use inputs** β€” numeric, decimal and timespan pickers, searchable and tag combo boxes, file and colour pickers, inline-editable text, dropdowns. - 🧭 **Navigation & overlays** β€” a navigation menu, a view-model-first page container, awaitable modal dialogs and a paging selector. - πŸ—‚οΈ **A file explorer** β€” breadcrumb, folder tree and file list sharing a source, with shell-backed file operations and nodes of your own. - 🧾 **JSON data trees** β€” fill a value in against a JSON Schema, or build one from scratch. - πŸͺŸ **Custom-chrome window & natives** β€” a themed application shell plus restyled built-in WPF controls that match out of the box. - πŸ“¦ **Modular packages** β€” take just the core styles, or add inputs and navigation only where you need them. {: .note } > The gallery's **Native controls** group β€” WPF's built-in controls restyled to match the > design system (text boxes, combo boxes, data grid, …) β€” is mostly undocumented > here; only natives with named style variants are (see > [Native controls](natives/index.md)). Otherwise these pages cover the > custom controls the library adds. ## Sections | Section | What's inside | |---|---| | [Themes](themes/index.md) | The application shell (`ThemedWindow`), `ThemeManager`, the design tokens and the live theme customizer | | [Native controls](natives/index.md) | Text sizes and muted text, `FontIcon` and `Chevron`, the `Card` styles, buttons and toggle buttons (solid, soft & outline variants) | | [Toolkit](toolkit/index.md) | Sizing, spacing, derived dimensions, tooltips, animation, drag and drop, shared width, creating a custom control | | [Joufflu controls](controls/index.md) | The optional packages: [Feedback](feedback/index.md) (`Badge`, `Spinner`, toasts), [Inputs](inputs/index.md) (numeric, search, combo boxes, pickers, `Dropdown`…), [Navigation](navigation/index.md) (`NavigationMenu`, overlays, `Paging`), [Data](data/index.md) (`DataFill`, `DataEdit`, `DataDisplay`) and [File explorer](file-explorer/index.md) | ## Getting started 1. Add the packages you need. `Joufflu` is the core (styles & theming); `Joufflu.Data`, `Joufflu.Feedback`, `Joufflu.FileExplorer`, `Joufflu.Inputs` and `Joufflu.Navigation` are optional and all build on it: ```sh dotnet add package Joufflu dotnet add package Joufflu.Data # optional: JSON data trees (needs Joufflu.Inputs) dotnet add package Joufflu.Feedback # optional: badges, spinner, toasts dotnet add package Joufflu.FileExplorer # optional: file explorer (needs Joufflu.Feedback) dotnet add package Joufflu.Inputs # optional: input controls dotnet add package Joufflu.Navigation # optional: navigation & overlays ``` 2. Merge the control styles in `App.xaml` so the themed styles and the `joufflu:Dimensions` keys are available (the `joufflu:Brushes` come with the theme, step 3): ```xml ``` 3. Initialize the theme manager once at startup, before the first window shows: ```csharp // App.xaml.cs β€” OnStartup ThemeManager.Instance.Initialize(); ``` 4. Use the controls as shown in the pages above. New to the library? The [Tutorial](tutorial.md) builds a full navigable app shell β€” side menu, pages, modals and toasts β€” from scratch, and the [Recipes](recipes.md) give copy-paste screens. Choosing between UI libraries? See [Joufflu vs other WPF UI libraries](comparison.md). To explore everything interactively, run the **`Joufflu.Samples`** project. --- # Tutorial: a navigable app shell Builds a small MVVM app on `Joufflu.Navigation`: a collapsible side menu that switches pages, a shared shell view model owning the navigation services, an example page, and a modal overlay plus a toast raised from it. The result matches the wiring the **`Joufflu.Samples`** gallery uses. {: .note } > Assumes the packages are installed and `App.xaml` merges the Joufflu resource > dictionaries, with `ThemeManager.Instance.Initialize()` called at startup. See > [Getting started](index.md#getting-started) first if not. The snippets use these namespaces: ```xml xmlns:controls="clr-namespace:Joufflu.Controls;assembly=Joufflu" xmlns:feedback="clr-namespace:Joufflu.Feedback.Controls;assembly=Joufflu.Feedback" xmlns:fonts="clr-namespace:Joufflu.Assets.Fonts;assembly=Joufflu" xmlns:nav="clr-namespace:Joufflu.Navigation.Controls;assembly=Joufflu.Navigation" xmlns:toolkit="clr-namespace:Joufflu.Toolkit;assembly=Joufflu" ``` ## How it fits together Three services drive everything, shared between the shell window and the pages: | Service | Role | |---|---| | `Navigator` | Holds the current page (a view model) and switches between pages. Built with a resolver turning a page type into the page instance. | | `Overlayer` | Shows modal overlays on top of the current page. | | `ToastService` | Shows stacking, auto-dismissing notifications. | Navigation is **view-model-first**: navigate to a *view model* and WPF resolves the matching *view* through an implicit `DataTemplate`. A plain `ContentControl` bound to `Navigator.CurrentPage` renders the current page; `NavigationMenu` drives the same `Navigator` from the side; `OverlayContainer` and `ToastContainer` wrap the whole app and host the overlay and toast stacks above it. ## Step 1 β€” The shared shell view model The shell view model owns the three services so the window and every page share the same instances. It also keeps a registry of pages keyed by their own type, which is what the menu items target. It starts empty; the first page is added in Step 4. ```csharp using CommunityToolkit.Mvvm.ComponentModel; using Joufflu.Feedback; using Joufflu.Navigation; public class ShellViewModel : ObservableObject { // Shared with the shell window's page container, NavigationMenu and the // overlay/toast containers, and injected into the pages that need them. public Navigator Navigator { get; } public Overlayer Overlays { get; } = new(); public ToastService Toasts { get; } = new(); // Pages keyed by their own type, which is what the menu's NavigationItems // target. Filled in as we add pages (Step 4). private readonly Dictionary _pages = new(); public ShellViewModel() { // The navigator turns an item's target type into the page to display. Navigator = new Navigator(target => _pages.GetValueOrDefault(target)); } } ``` ## Step 2 β€” Map view models to views An implicit `DataTemplate` (a `DataType` with no `x:Key`) maps each view model to its view. Add one per page, typically in `App.xaml`. The `HomeViewModel` / `HomeView` pair comes in Step 4; here is the mapping it needs: ```xml ``` The same mechanism resolves overlay content β€” add a `DataTemplate` for every overlay view model too (Step 5). ## Step 3 β€” The shell window A `ThemedWindow` whose content is a `ToastContainer` around an `OverlayContainer`, wrapping the side `NavigationMenu` and the page `ContentControl`. Bind everything to the shell view model's services so they stay in sync: selecting a menu item navigates the page, and the overlays/toasts use the shared services. Wrapping the *whole* app rather than the page area only is what lets a full screen overlay cover the entire window, side menu included. Toasts wrap the overlays in turn, so they stay on top of them. The `d:DataContext` line gives the XAML designer the runtime view-model type, so bindings like `{Binding Navigator}` get IntelliSense and design-time validation. No runtime effect. ```xml Home ``` Its code-behind (`ShellWindow.xaml.cs`) must derive from `ThemedWindow` too (`public partial class ShellWindow : ThemedWindow`), or the build fails on the mismatched base class. Create the window in code to pass the shell view model as its `DataContext`, e.g. in `App.xaml.cs` (`using Joufflu.Themes;` for `ThemeManager`): ```csharp protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ThemeManager.Instance.Initialize(); var shell = new ShellViewModel(); new ShellWindow { DataContext = shell }.Show(); } ``` {: .note } > Remove `StartupUri="MainWindow.xaml"` from `App.xaml`, or WPF opens that window > too and you end up with two. {: .note } > Each item's `TargetType` is passed to the navigator's resolver, which returns > the page. Add more items with the Step 4 recipe. A `NavigationGroup` expands to > reveal children instead of navigating; a `NavigationTitle` is a section label. > See [Navigation menu](navigation/navigation-menu.md) for the full > markup. ## Step 4 β€” An example page A page is a view model plus a matching view. The view model takes the shared services to open overlays and raise toasts: ```csharp using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Joufflu.Feedback; using Joufflu.Navigation; public class HomeViewModel : ObservableObject { private readonly IOverlayer _overlays; private readonly IToastService _toasts; public IRelayCommand SayHelloCommand { get; } public IAsyncRelayCommand DeleteCommand { get; } public HomeViewModel(IOverlayer overlays, IToastService toasts) { _overlays = overlays; _toasts = toasts; SayHelloCommand = new RelayCommand(SayHello); DeleteCommand = new AsyncRelayCommand(DeleteAsync); } // ... commands implemented in Step 5 ... } ``` Its view: ```xml ``` ## Chevron `Chevron` is the `FontIcon` of a header that opens content below it: its glyph follows `IsExpanded`. `Placement="Leading"` (the default, before the header as in a tree) points right, then down once open; `Placement="Trailing"` (after the header, as in an expander) points down, then up. The glyph switches without an animation. The tree view, expander, navigation group and data display all use it. `ChevronToggleButton` is the expander of a tree row built on it: a bare, square, non-focusable toggle in the row's foreground. ```xml ``` --- # Card `Card` and `CardSecondary` are named `Border` styles that provide the app's padded, rounded surfaces. Apply them through the `Style` property of a plain `` and put any content inside. ## Card The primary surface for grouping content, a display: a `DisplayBase0` fill, a `DisplayBorder0` border, `DisplayRadius` corners and `DisplayPadding` as padding. ```xml ``` ## CardSecondary A subtly inset panel meant to sit **inside** a `Card`. It uses the `DisplayBorder0` fill, the part of a display that sits on its base, so it reads as a distinct area of the card, and the `DisplayBorder1` border to stand out from it. ```xml ``` {: .note } > Both styles read the display tokens (see [Design tokens](../themes/tokens.md)), so > they follow Light / Dark and any custom theme automatically, and a theme restyles every > card by overriding the `Display*` colours or dimensions. --- # Buttons A plain ` ``` ## Soft A tinted background with the semantic hue as text β€” lower emphasis than solid, for secondary actions. The tint is the semantic colour at low opacity (β‰ˆ14 %, β‰ˆ24 % on hover), not a dedicated palette entry, so it derives automatically in both Light and Dark and follows any custom theme. Secondary's colour is a pale fill, not a hue, so its soft and outline variants draw with its foreground instead, at a lighter tint (β‰ˆ8 %, β‰ˆ14 % on hover). ```xml ``` {: .note } > Since the soft text is the semantic hue itself, the brighter semantics (Success, > Warning) sit at lower text-contrast on the pale tint in the Light theme. Reserve > soft buttons for short labels / secondary actions, or use solid when contrast > matters. ## Outline A coloured border and text over a transparent fill; hovering fills it with the soft tint. Secondary draws its border and text with its foreground. ```xml ``` ## Icon buttons `IsSquare` makes a button as wide as it is tall, for a single icon. Composes with any variant. ```xml ``` ## Sizes The inherited `Sizing.Size` attached property (`xs` / `sm` / `md` / `lg`) scales height, font size and padding. ```xml ``` A button inside an input (clear, open, step…) uses `EmbeddedButton`: see [Create a custom control](../toolkit/custom-control.md). ## Toggle buttons A plain `` is themed like a button and filled with the primary colour when checked. The ghost, solid, soft and outline styles each have a toggle twin, the `Button` suffix replaced by `ToggleButton`. Checked, the ghost toggle turns primary, a solid one takes its hover shade, and soft and outline ones turn solid. ```xml Default Primary Ghost Success Danger ``` `IconToggleButton` is a ready-made square toggle for a single icon, transparent until hovered or checked. `IsSquare` composes with any other variant. ```xml ``` The chevron toggle of a tree row, `ChevronToggleButton`, is on the [Font icon](icons.md#chevron) page. --- # Toolkit Design-system helpers that shape controls and layouts rather than being controls themselves. They all live in the `Joufflu.Toolkit` namespace of the core `Joufflu` package β€” `Sizing`, `Spacing`, `Derive`, `Tooltip`, `PopupPlacement`, `Animate`, `DropTarget`, `DropData`, `DragSource`, `SharedWidth` and `DataGridEditing` (which the `DataGrid` style uses to theme the editors of its built-in columns). The theme manager, tokens and window have their own section, see [Themes](../themes/index.md). - **Sizing** β€” the `Sizing.Size` and `IsSquare` attached properties. - **Spacing** β€” the `Spacing.Gap` attached property for gaps between children. - **Derived dimensions** β€” the `Derive.BorderThickness`, `Derive.CornerRadius` and `Derive.Margin` attached properties, with their factors scaling the value side by side, for thicknesses, radii and margins that follow the theme live. - **Tooltip & popup placement** β€” every `ToolTip` themed and placed on a side of its element; `Tooltip.Placement` picks the side, `Tooltip.Alignment` how it lines up (centred by default) and `Tooltip.MaxWidth` where its text wraps. The `PopupPlacement.Side`, `Alignment` and `Gap` attached properties place any `Popup` the same way, flipped to the opposite side at a screen edge. - **Animate** β€” the `Animate.Bounce` attached property for a looping vertical hop on any element, with `BounceHeight` and `BounceDuration` shaping it, and `Animate.Spin` for an endless turn around its centre, with `SpinDuration` setting its speed. - **Drag and drop** β€” the `DropTarget.Command`, `IsDragOver` and `Effect` attached properties for turning any element into a drop target, with the `DropData` telling what was dropped and where, and `DragSource.Data`, `AllowedEffects` and `IsDragging` for turning any element into a drag source. - **Shared width** β€” the `SharedWidth` decorator and its `IsScope` attached property, which give every input of a group the width of the narrowest one so they line up across the depths of a tree. - **Create a custom control** β€” the category a control of your own belongs to (display, input, action), the tokens and states of each, then an input built step by step: background and border, height, sizes and padding, states, the validation error style and embedded buttons. Snippets use the `toolkit` XML namespace for those helpers, `joufflu` for the design-system keys and `fonts` for the icons, plus `feedback` where an example targets one of its controls: ```xml xmlns:joufflu="clr-namespace:Joufflu;assembly=Joufflu" xmlns:toolkit="clr-namespace:Joufflu.Toolkit;assembly=Joufflu" xmlns:fonts="clr-namespace:Joufflu.Assets.Fonts;assembly=Joufflu" xmlns:feedback="clr-namespace:Joufflu.Feedback.Controls;assembly=Joufflu.Feedback" ``` --- # Sizing ## Sizing.Size The inherited `Size` attached property (`xs` / `sm` / `md` / `lg`) scales height, font size and padding. `xl` exists too, for text and icons: a `TextBlock` takes the `FontSizeXl` size and a `FontIcon` grows, while controls fall back to `md`. Since it inherits, setting it on a panel sizes every child. ```xml ``` Other controls honor the same attached property β€” `TreeView` scales its expander, indentation and text together: ```xml ``` ## Sizing.IsSquare `IsSquare` forces equal width and height, for single-icon buttons. ```xml ``` --- # Spacing ## Spacing.Gap `Gap` adds even spacing between a panel's children β€” no per-child margins. A `StackPanel` spaces along its orientation; `WrapPanel` and `Grid` space on both axes. `Gap` is a `Thickness`: a single value is a uniform gap on both axes, a `"horizontal,vertical"` pair sets each axis independently (`Left` drives horizontal, `Top` vertical). ```xml ... ... ``` {: .note } > Spacing is implemented through child margins, so any margin set directly on a > child is overwritten while spacing is active. A `WrapPanel` doesn't know where its > lines break, so each child gets the gap after it: the panel keeps one trailing gap > on its right and bottom edges. --- # Derived dimensions A `Thickness` or a `CornerRadius` declared in a `ResourceDictionary` is baked at parse time. Its `Left`/`Top`/`Right`/`Bottom` (or `TopLeft`/`TopRight`/…) are plain CLR properties, not dependency properties, so they can only be fed with `StaticResource` and never follow a later change of the scalar they were built from: ```xml 1 ``` `Derive.BorderThickness`, `Derive.CornerRadius` and `Derive.Margin` build the value on the element instead, from a real `DynamicResource`. A scalar edited at runtime β€” by the [theme customizer](../themes/customize-theme.html), for instance β€” flows straight through, and no derived resource key has to be declared or re-pushed by hand. Point it at the key of the category the element belongs to (`DisplayThickness`, `DisplayRadius`, `InputRadius`, `ActionRadius`…, see [Design tokens](../themes/tokens.html)), so the part follows the rest of the control when a theme reshapes that category. The global `Thickness` and `Radius` are for an app's own layout. ## Factors Each of the three properties has a matching factor, a `Thickness` (or a `CornerRadius`) whose components multiply the derived value side by side: | Factor component | Result | | --- | --- | | `0` | The side or corner is dropped | | `1` | It is kept as is | | anything else | It is scaled β€” `2` for a doubled margin, `0.5` for a halved radius | The default is `1,1,1,1`, so the derived value is applied whole. ## Derive.BorderThickness Point `Derive.BorderThickness` at a resource key and pick the sides with `Derive.BorderThicknessFactor`, in the usual `Left,Top,Right,Bottom` order. ```xml ``` Both properties work in a `Style` setter, so a style can derive its border without a keyed `Thickness`: ```xml ``` > **In a style, the fed property stops being overridable.** The derived value is > pushed with `SetCurrentValue`, which outranks a style setter β€” and even a local > value; only an animation or a template trigger still takes over. So do not also > set the property it feeds (here `BorderThickness`) in the same style, move any > `Style.Triggers` that change it onto the `Derive` property, and expect that a > consumer can no longer override it. `Derive` fits a value the control owns β€” a > `Border` inside a template. When the fed property has to stay overridable β€” a > text input's `Padding`, say β€” give it a plain keyed default > (`Dimensions.InputPadding*`) instead. ## Derive.CornerRadius Same shape, with `Derive.CornerRadiusFactor` in the `TopLeft,TopRight,BottomRight,BottomLeft` order. ```xml ``` ## Derive.Margin Same shape again, on any `FrameworkElement`, with `Derive.MarginFactor`. ```xml ``` ## Notes - The source resource may be a `Thickness` / `CornerRadius` (every Joufflu shape key is one), whose own sides are then scaled, or a `double`, spread over every side or corner before scaling. - Pass the resource **key** (`{x:Static …}`): `Derive` follows the resource itself. A `Thickness`, `CornerRadius` or `double` value is taken as is instead, so inside a template `toolkit:Derive.Margin="{TemplateBinding Padding}"` scales the control's own padding. - `Derive.BorderThickness` applies to `Border` and to any `Control`; `Derive.CornerRadius` applies to `Border`; `Derive.Margin` to any `FrameworkElement`. Anything else throws. --- # Tooltip & popup placement Set `ToolTip` the usual way, as a string or arbitrary XAML: every tooltip is themed. Its style places it with a custom placement rather than at the mouse, centred on a side of the element and kept a small gap clear of it, and WPF flips it to the opposite side when it would run off a screen edge. The show delay is the native one. Any other `Popup` is placed the same way with [`PopupPlacement`](#popupplacement). ```xml ``` ## Tooltip.Placement `Tooltip.Placement`, set on the element, chooses which side its tooltip sits on β€” `Top`, `Bottom` (default), `Left` or `Right`. ```xml ``` ## Binding the bounce `Bounce` is a plain boolean, so bind it to a view-model flag to start and stop the loop at runtime. ```xml 3 ``` {: .note } > `BounceHeight` and `BounceDuration` are baked into the animation's key frames, > so changing either while the element is bouncing restarts the hop from the top > of its cycle. ## Animate.Spin `Spin` turns an element endlessly, clockwise and at constant speed β€” a loading glyph, a refresh icon while it works. `SpinDuration` sets the time of one full turn (default `0:0:1`). The `Spinner` of `Joufflu.Feedback` is built on it. The element turns around its own centre whatever its `RenderTransformOrigin`, and keeps doing so when it is resized. Like the bounce it runs on the `RenderTransform`, so it never affects layout; a transform already on the element is kept, and the element turns as that transform draws it. ```xml ``` `Spin` and `Bounce` can be set on the same element: it hops while it turns, and each stops on its own. ## Hidden elements and lifetime Every animation pauses itself whenever the element is not visible β€” `Collapsed` or `Hidden`, under a collapsed ancestor, or out of the visual tree β€” parks at rest, and starts again from the top of its cycle when the element comes back. An animation left on a hidden element therefore costs no frames. Nothing has to be unhooked for the element to be collected: the helper keeps no reference back to it, and an element dropped with `Bounce` or `Spin` still `True` is collected exactly like one that was stopped first β€” WPF retires the clock once its target goes away. --- # Drag and drop ## DropTarget.Command `DropTarget.Command` makes **any** element a drop target: `AllowDrop` and the four drag events (`DragEnter`, `DragOver`, `DragLeave`, `Drop`) are handled for you, and the command is executed with the dropped `IDataObject` as parameter. The command's `CanExecute` is what decides the accepted data: what it refuses can't be dropped β€” the cursor shows a no-drop sign β€” and never lights the zone up. ```xml ``` ```csharp // The command's CanExecute is the whole filter: what it refuses can't be dropped public IRelayCommand DropFilesCommand { get; } = new RelayCommand(DropFiles, CanDropFiles); private static bool CanDropFiles(IDataObject? data) { string[]? files = data?.GetData(DataFormats.FileDrop) as string[]; return files?.Length > 0 && files.All(f => Path.GetExtension(f).Equals(".pdf", StringComparison.OrdinalIgnoreCase)); } // Same IDataObject, once the drop landed private static void DropFiles(IDataObject? data) { ... } ``` {: .note } > `CanExecute` is called on every mouse move of the drag, so keep it cheap and side > effect free β€” look at the paths, not at the files. ## Where the drop landed The parameter is a `DropData`: the dragged data β€” which it is an `IDataObject` of, so a command only interested in what was dropped keeps taking one and reads it as above β€” and where the pointer is on the target, for the drops that land *somewhere* rather than just on something: a canvas placing what it receives under the cursor, a list inserting it at an index. ```csharp // Position is relative to Target, the element holding DropTarget.Command private void DropNode(IDataObject? data) { if (data is not DropData drop) return; Point onCanvas = drop.Target.TranslatePoint(drop.Position, Canvas); ... } ``` {: .note } > `Position` is where the drop landed for the command itself, and where the pointer is for > `CanExecute`, which is called all along the drag: a target can accept a drag over one of > its areas and refuse it over another. ## DropTarget.IsDragOver `IsDragOver` is `true` while **accepted** data hovers the element, which is all a trigger needs to highlight a valid drop. It inherits, so template and content children see it too. ```xml ``` ## DropTarget.Effect `Effect` is the drop effect reported to the drag source for an accepted drop, which drives the mouse cursor β€” `Copy` by default. Data the source doesn't allow this effect for is rejected, so a `Move` target only accepts drags that can be moved. ```xml ``` ## DragSource.Data `DragSource.Data` is the counterpart of `DropTarget.Command`: it makes **any** element a drag source, carrying that data to the drop targets. The mouse events are handled for you, and the drag only starts once the pointer moved past the system drag threshold, so clicks keep working β€” a draggable button is still clickable. The data is given to the targets as is when it already is an `IDataObject`, and wrapped in a `DataObject` otherwise: a `string` arrives as text, a view model under its own type β€” and under every base class and interface of it, so a target can ask for what the data **is** instead of having to know the exact kind it is given. ```xml ``` ```csharp // The target reads what the source carried, wrapped in a DataObject private static string? GetTag(IDataObject? data) => data?.GetData(DataFormats.UnicodeText) as string; // Whatever kind of node was dragged, the target only asks for the base it can handle private static BaseNode? GetNode(IDataObject? data) => data?.GetData(typeof(BaseNode)) as BaseNode; ``` ## DragSource.AllowedEffects `AllowedEffects` is what the source lets the targets answer with β€” `Copy` by default. A target asking for anything else is refused, so `AllowedEffects` and `DropTarget.Effect` must agree for the drop to happen. ```xml ``` ## DragSource.IsDragging `IsDragging` is `true` for the whole duration of the drag, which is all a trigger needs to fade the original out while it travels. It inherits, so template and content children see it too. ```xml ``` {: .note } > The drag is a blocking call: nothing else happens on the element until the drop > lands or the drag is cancelled. --- # Shared width ## SharedWidth `SharedWidth` lines up inputs that each get a different amount of room, such as the rows of a tree at different depths. Wrap each input in a `SharedWidth`: it stretches over its cell and gives the input the width of the narrowest member of its group, against its right edge. Their right edges already line up, so their left edges do too. Each group is the closest ancestor marked `SharedWidth.IsScope`: ```xml ``` `DataFill` and `DataEdit` use it for their editors. - Only visible members count: the rows of a collapsed node are ignored until it expands. - A member's room is the width its parent arranges it at, so give it a stretching cell (`*` column, stretched panel). The shared width follows when a member is added or removed or the scope is resized. - The shared width only changes how the input is laid out, never the size the member asks for. A scope sized to its content (a centered dialog, an `Auto` column, a horizontal scroll) stays stable instead of bouncing between two widths. --- # Create a custom control A control of your own sits with the Joufflu ones when it reads the tokens of its category, so a theme restyling that category restyles it too. Pick the category the control belongs to, then follow its rules. A control derived from a native one (`TextBox`, `Button`, `ListBox`…) gets them all by basing its style on the native one. | Category | What it is | Tokens | States | |---|---|---|---| | **Display** | Shows content without being interacted with: a card, a popup, a tooltip, a panel, the frame of a list | `DisplayBase0/Border0/Border1/InteractionBrush`, `DisplayRadius`, `DisplayThickness`, `DisplayPadding` (`DisplayPaddingSm` when compact) | None of its own (`DisplayInteractionBrush` is there for an informative hover). Its items (rows, entries) are actions without a fill at rest | | **Input** | Takes a value: a text field, a picker, a rating, a check | `InputBase0/Border0/Border1/InteractionBrush`, `InputRadius`, `InputThickness`, `InputPaddingXs`…`Lg`, `HeightXs`…`Lg` | No hover; on focus the border turns `InputBorder1Brush` (and the background `InputInteractionBrush` when no text is typed in it) | | **Action** | Runs something: a button, a toggle, an item of a display | `ActionBase0/Border0/Border1/InteractionBrush`, `ActionRadius`, `ActionThickness`, `ActionPaddingXs`…`Lg`, `HeightXs`…`Lg` | On hover the border turns `ActionBorder1Brush` and the background `ActionInteractionBrush`; moved down when pressed, no focus border | In every category, disabled is `DisabledOpacity`, selected and checked are the primary, and sizes follow `Sizing.Size`. [Design tokens](../themes/tokens.html) lists each key with its role and fallback. ## Example: an input The gallery's **Create a custom control** page builds a star `RatingInput` from a bare `Control` step by step, following every rule of the **Input** category by hand; a display or an action follows the same steps with the keys of its own category. The rules: | | Rule | Tokens | |---|---|---| | **Required** | Background | `Brushes.InputBase0Brush` (`InputBorder0Brush` for a part that sits on it: a tag, a track) | | **Required** | Border colour, thickness and radius | `Brushes.InputBorder0Brush`, `Dimensions.InputThickness`, `Dimensions.InputRadius` (clamped to half the height with `MaxCornerRadiusConverter`) | | **Required** | Height, for a one-line input | `Dimensions.HeightMd` | | Optional | Sizes and padding | `HeightXs`…`HeightLg`, `FontSizeXs`…`FontSizeLg`, `InputPaddingXs`…`InputPaddingLg` | | Optional | Focus and disabled states (an input has no hover) | `InputBorder1Brush` (focus), `Colors.DisabledOpacity` | | Optional | Validation error style | `DangerBrush`, `ValidationErrorTemplate` | | Optional | Buttons inside the input | `EmbeddedButton` | Always reference the tokens with `DynamicResource`: a `StaticResource` is read once and misses every later theme switch. ## Derive from a native control first When the input is a native control underneath (a `TextBox` with extras, a `ComboBox` variant…), derive from it and base the style on the native one. It inherits every rule on this page at once, and only the template needs writing: ```xml ``` The template must keep reading `Background`, `BorderBrush`, `BorderThickness` and `Padding` through `TemplateBinding`, or the inherited triggers have nothing to act on. The rest of this page is for an input built from a bare `Control`. ## The minimum Set the background, the border and the height from the tokens in the style, and let the template read them from the control: ```xml ``` Setting them in the style rather than in the template keeps them overridable, and lets the triggers below change them. A multi-line input (a list, a text area) leaves the height alone. `MaxCornerRadiusConverter`, and `HorizontalThicknessConverter` below, are in `Joufflu.Converters`: declare them in the dictionary holding the style (see [Embedded buttons](#embedded-buttons) for why). ```xml ``` ## Sizes and padding `Sizing.Size` is inherited: a panel set to `lg` expects every input in it to grow. Set the `md` values by default, and one trigger per other size: | Size | Height | Font size | Input padding | |---|---|---|---| | `xs` | `HeightXs` (24) | `FontSizeXs` (11) | `InputPaddingXs` (3,2) | | `sm` | `HeightSm` (28) | `FontSizeSm` (12) | `InputPaddingSm` (4.5,3) | | `md` | `HeightMd` (32) | `FontSizeMd` (13) | `InputPaddingMd` (6,4) | | `lg` | `HeightLg` (40) | `FontSizeLg` (16) | `InputPaddingLg` (9,6) | ```xml ``` Inputs use the `InputPadding` keys, tighter than the `ActionPadding` keys of the buttons. The height of a one-line input comes from its size, so, like the native inputs, the template lays the content over the border inside the **horizontal** part of `Padding` only, centred vertically: it lines up with the text of the native inputs, and a large vertical padding never crushes it. `HorizontalThicknessConverter` drops the vertical part: ```xml ``` ## States An input has no hover state: on focus the native inputs turn their border to `InputBorder1Brush`, and they fade when disabled. One you type text into keeps its background, as a `TextBox` does: a darker fill would lower the contrast of the text. One you only pick from (a combo box, a check box) also turns its background to `InputInteractionBrush`. ```xml ``` ## Validation error style Binding errors show in two layers, as on every Joufflu input: the input turns its own border red, and `ValidationErrorTemplate` adds a badge on the corner whose tooltip lists the errors. The template draws no border of its own, so the trigger is what makes the border red: ```xml ``` A user of the input drops the badge with `Validation.ErrorTemplate="{x:Null}"`; the red border stays. ## Embedded buttons A button inside the input (clear, open, step…) uses the `EmbeddedButton` style: a transparent square of `HeightEmbedded` (20). Centred in the content row, which takes no vertical padding, it fits every size, down to the 24 of `xs`. ```xml ``` `EmbeddedButton` is a named style of `Button.named.xaml`. A `StaticResource` in a `ResourceDictionary` only sees that dictionary and the ones it merges, so a dictionary holding the input's style merges it: ```xml ``` ## Checking the result Put the input next to a `TextBox` and compare them: - in each size, with `Sizing.Size` on a parent panel; - after switching between Light and Dark, and to a custom theme; - focused and disabled; - with a binding error, with and without `Validation.ErrorTemplate="{x:Null}"`. --- # Joufflu controls The controls Joufflu adds on top of the restyled natives, one optional package each. They all build on the core `Joufflu` package and read the same design tokens, so they follow the theme like the natives do. | Section | Package | What's inside | |---|---|---| | [Feedback](../feedback/index.md) | `Joufflu.Feedback` | `Badge`, `Spinner`, toasts | | [Inputs](../inputs/index.md) | `Joufflu.Inputs` | `NumericUpDown`, `DecimalUpDown`, `TimeSpanPicker`, `FormatTextBox`, `Search`, `ComboBoxSearch`, `ComboBoxTags`, `TextEditable`, `FilePicker`, `ColorPicker`, `Dropdown` | | [Navigation](../navigation/index.md) | `Joufflu.Navigation` | `NavigationMenu`, overlays (modal dialogs), `Paging` | | [Data](../data/index.md) | `Joufflu.Data` | `DataFill`, `DataEdit`, `DataDisplay` | | [File explorer](../file-explorer/index.md) | `Joufflu.FileExplorer` | `ExplorerList`, `ExplorerTree`, `Explorer` and their sources | --- # Feedback Feedback controls from the optional [`Joufflu.Feedback`](https://www.nuget.org/packages/Joufflu.Feedback) package, built on the design system. - **Badge** β€” a themed pill in the semantic colours. - **Spinner** β€” an indeterminate loading indicator. - **Toasts** β€” transient notifications shown from a service, stacked in any corner by a `ToastContainer`. Add the package (`dotnet add package Joufflu.Feedback`). Only the core `Resources.xaml` needs merging: the controls' styles load from the package's `Themes/Generic.xaml`. The controls live in the `Joufflu.Feedback.Controls` namespace: ```xml xmlns:feedback="clr-namespace:Joufflu.Feedback.Controls;assembly=Joufflu.Feedback" xmlns:joufflu="clr-namespace:Joufflu;assembly=Joufflu" xmlns:toolkit="clr-namespace:Joufflu.Toolkit;assembly=Joufflu" ``` --- # Badge ## Variants A pill themed from the semantic brushes. Pick a variant with the `Variant` property (`Default` when unset). ```xml Default Primary Secondary Success Info Warning Danger ``` ## Sizes Sized through the inherited `Sizing.Size` attached property. ```xml xs sm md lg ``` --- # Spinner ## Loading indicator An indeterminate loading indicator. Colour comes from `Foreground` (the primary brush by default), diameter from `Sizing.Size`. ```xml ``` A collapsed or hidden spinner stops turning, so it can stay in the view and be shown through `Visibility` while work runs. --- # Toasts Transient notifications stacked in a corner of the window, above page content and overlays, auto-dismissed after 5 seconds by default (`ToastOptions.Duration`) unless sticky. ## Hosting the toasts A `ToastContainer` bound to the same `ToastService` renders them. It wraps the content it sits above, so wrapping the whole window keeps the toasts on top of everything: ```xml ``` The container creates a `ToastService` of its own when none is bound; bind one from your shell view model to share it with the pages, behind `IToastService`. When the app also uses an [`OverlayContainer`](../navigation/overlays.md#hosting-the-overlays), wrap it *inside* the `ToastContainer` so toasts stay above the modal overlays: ```xml ``` ## Toast types Show them from an injected `IToastService` (in `Joufflu.Feedback`): ```csharp toasts.Info("A neutral message.", "Heads up"); toasts.Success("Saved."); toasts.Warning("Careful."); toasts.Error("Failed."); toasts.Show(new ToastOptions { Message = "Sticky", Duration = TimeSpan.Zero }); ``` A `Duration` of `TimeSpan.Zero` makes a toast sticky: it stays until the user closes it. ## Loading toast A loading toast shows a spinner and stays until updated. Change its type, title, message or duration from the returned instance once the work progresses or ends: ```csharp ToastInstance toast = toasts.Loading("Preparing…", "Uploading files"); toast.Message = "File 1 of 3"; // once done toast.Type = ToastType.Success; toast.Title = "Upload complete"; toast.Message = "3 files uploaded."; toast.Duration = TimeSpan.FromSeconds(5); // starts the auto-dismiss ``` Every `ToastInstance` can be updated this way, not only loading ones. Changing `Duration` restarts the countdown from that moment. `toasts.Close(toast)` removes a toast early. ## Position The `ToastContainer`'s `Position` picks the corner the stack sits in: `TopRight` (the default), `TopLeft`, `BottomRight` or `BottomLeft`. The newest toast always sits closest to that corner, so the stack grows away from it. Bind it to change corners at runtime β€” the gallery's **Toasts** page does exactly that. ```xml ``` --- # Inputs Inputs from the `Joufflu.Inputs` library. Two-way bindable like the built-in WPF inputs. - **Numeric & format inputs** β€” `NumericUpDown`, `DecimalUpDown`, `TimeSpanPicker` and the underlying `FormatTextBox`. - **Search** β€” a debounced `Search` box. - **Search combo box** β€” the searchable `ComboBoxSearch`. - **Combo box tags** β€” multi-selection built on `ComboBoxSearch`. - **Text editable** β€” a value that becomes editable on demand. - **File picker** β€” pick a file through the system dialog. - **Color picker** β€” pick a colour with a saturation square, hue and alpha sliders. - **Dropdown** β€” attached properties opening a popup of any content off a `ToggleButton`. All snippets assume these XML namespaces: ```xml xmlns:inputs="clr-namespace:Joufflu.Inputs.Controls;assembly=Joufflu.Inputs" xmlns:format="clr-namespace:Joufflu.Inputs.Controls.Format;assembly=Joufflu.Inputs" xmlns:toolkit="clr-namespace:Joufflu.Toolkit;assembly=Joufflu" xmlns:fonts="clr-namespace:Joufflu.Assets.Fonts;assembly=Joufflu" ``` --- # Numeric & format inputs ## NumericUpDown Selects a whole number (`long?`). Built on `FormatTextBox` with a single `IntegerGroup`, plus clear and increment/decrement buttons. ```xml ``` ## DecimalUpDown Selects a `decimal?` value, through a single `DecimalGroup`. ```xml ``` ## TimeSpanPicker Selects a `TimeSpan?` through a days/hours/minutes/seconds format. ```xml ``` ## Bounds and format To bound or format the number of a `NumericUpDown` or a `DecimalUpDown`, write its group yourself: a group given as content replaces the default one (which is nullable and keeps its own caret, so set those back when you want them). ```xml ``` ## FormatTextBox The base input: groups the user types into, with literal text between them, each group read into its own value and navigated with Tab / arrows. ```xml ``` `IntegerGroup` counts in `long` and `DecimalGroup` in `decimal`. Both take: | Property | Default | Meaning | |---|---|---| | `Min`, `Max` | the type's own | Bounds of what can be typed or spun to. `Max` also sets the group's width: once the number is as long as it, typing moves on to the next group. | | `Step` | `1` / `0.1` | What ↑ / ↓ and the mouse wheel add or take away. | | `StringFormat` | none | A .NET numeric format string: `00` pads to two digits, `N0` separates thousands. | | `IsNullable` | `false` | Whether the group can hold no number rather than zero. | | `PromptChar` | `-` | Shown once per character of a group holding no number. | | `SelectsWhole` | `true` | Selected, typed over and emptied as one thing; when `false` the group keeps its own caret, like a plain text box. | `Values` holds one value per group, raising `ValuesChanged`. `ShowDeleteButton="False"` and `ShowIncrementsButtons="False"` hide the clear and increment/decrement buttons, on every format input. ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Bring each value in range to clear it: a rating from 1 to 10, a ratio from 0 to 1, at most one hour. The last input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # Search ## Search A text box that waits for typing to settle (300 ms) before reporting the search, to limit calls to an API or database. Escape clears it. ```xml ``` ```csharp // code-behind: search.SearchChanged += text => Filter(text); ``` --- # Search combo box ## ComboBoxSearch An editable combo box that filters its items as you type, on the text `DisplayMemberPath` shows for each item. The chevron on the right opens the list, as do Up and Down. Opening it that way shows every choice, even once an item is selected: the text left behind by a selection is not a search, so it does not filter the list down to the item already picked. Escape closes the list and clears the selection, and text matching no item is cleared when the focus leaves. ```xml ``` ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Pick a country to clear it. The second input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # Combo box tags ## ComboBoxTags Builds on `ComboBoxSearch` to select several items, shown as removable tags. Selected items leave the list. Set `AllowAdd="True"` to let users add values not in the source list. ```xml ``` Enter adds the typed text when `AllowAdd` is set, and Backspace on an empty text removes the last tag. With `AllowAdd` and no `ItemsSource`, it is a free list of tags: no chevron, nothing drops down. Bind `SelectedItems` to a mutable `IList` (the gallery uses an `ObservableCollection`; with `AllowAdd` its element type must accept a `string`). The control edits that list in place. Changes made to its contents from the view model are not picked up: assign a new collection instead. ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Pick a second country to clear it. The second input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # Text editable ## TextEditable Displays a value that turns into an editable text box on demand, for use outside a form. A double click or the pencil button starts the edit; Enter or the check commits it, Escape or the cross cancels it. ```xml ``` `Text` only changes on commit. The `TextChanged` event then reports the new `Text` and the `OldText`. ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Shorten the title to 10 characters to clear it. The second input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # File picker ## FilePicker Selects a file through the system dialog and exposes the chosen path, with a clear button. The path can also be typed. ```xml ``` ## File types `Options` takes a `FilePickerOptions`: `Filter` lists the file types the dialog offers, in `OpenFileDialog.Filter` syntax, and `DefaultExtension` is the extension it appends to a typed name. ```xml ``` ```csharp public FilePickerOptions ImageOptions { get; } = new() { Filter = "Images (*.png;*.jpg)|*.png;*.jpg|All files (*.*)|*.*", DefaultExtension = ".png" }; ``` ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Choose a file to clear it. The second input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # Color picker ## ColorPicker Edits a colour through an editable hex value and a popup with a saturation square, hue and alpha sliders. Two-way bindable through `Color` (`System.Windows.Media.Color`). ```xml ``` The popup provides: - a **saturation / brightness** square you can drag, - a **hue** slider, - an **alpha** slider (checkerboard behind a transparent β†’ opaque ramp), - an editable **hex** field and an **opacity** percentage. A `#RRGGBB` hex (or a colour name such as `Teal`) keeps the opacity, a pasted `#AARRGGBB` sets it too. Text that spells no colour reverts to the current one. ## Validation Binding errors (here from an ObservableValidator) turn the input's border red, and `ValidationErrorTemplate` adds a corner badge whose tooltip lists them. Pick an opaque colour to clear it. The second input sets `Validation.ErrorTemplate` to `{x:Null}`: no badge, only the red border. ```xml ``` --- # Dropdown Attached properties that hang a themed popup off any `ToggleButton` β€” a menu of commands, a filter panel, a colour list. The popup is open while the button is checked, and closes when you click outside it. The button is not wrapped in a control: it stays a plain `ToggleButton` you own, so its `Style`, its content, its triggers and every attached property (`Sizing.IsSquare`, `Sizing.Size`, `Derive.*`, …) apply directly to it. ## Dropdown `Dropdown.Popup` hangs the popup off the button, open while it is checked. `Dropdown.CloseOnClick` dismisses it when a button inside it is clicked, the way picking a command from a menu does (see [Closing on click](#closing-on-click)). ```xml