diff --git a/docs/dock-active-document.md b/docs/dock-active-document.md index 940fb912a..d5cc34562 100644 --- a/docs/dock-active-document.md +++ b/docs/dock-active-document.md @@ -1,32 +1,47 @@ # Finding the Active Document -Only one document or tool can have focus at a time, even when multiple -`IDocumentDock` instances or floating windows are present. The -`IRootDock` that is currently active exposes its focused dockable via the -`FocusedDockable` property. You can subscribe to the -`IFactory.FocusedDockableChanged` event or query the property directly. +Only one dockable (document or tool) can have focus at a time, even when multiple +`IDocumentDock` instances or floating windows are present. The root dock that owns +the focused dockable stores it in `FocusedDockable`, and the factory reports focus +changes through `IFactory.FocusedDockableChanged`. + +If you already have the root layout for a window, read `FocusedDockable` directly: + +```csharp +var currentDocument = Layout?.FocusedDockable as IDocument; +``` + +To track focus across multiple windows, subscribe to the factory event and cache +the latest dockable: ```csharp -using System.Linq; +IDockable? focusedDockable = null; factory.FocusedDockableChanged += (_, e) => - Console.WriteLine($"Current document: {e.Dockable?.Title}"); +{ + focusedDockable = e.Dockable; + Console.WriteLine($"Focused dockable: {e.Dockable?.Title}"); +}; + +var currentDocument = focusedDockable as IDocument; +``` -var activeRoot = factory - .Find(d => d is IRootDock root && root.IsActive) - .OfType() - .FirstOrDefault(); +If you need the root dock that owns the focused dockable: -var current = activeRoot?.FocusedDockable as IDocument; +```csharp +var focusedRoot = focusedDockable is { } dockable + ? factory.FindRoot(dockable, root => root.IsFocusableRoot) + : null; ``` ```csharp -// Close whichever dockable currently has focus -if (activeRoot?.FocusedDockable is { } dockable) +// Close whichever dockable currently has focus. +if (focusedDockable is { } dockable) { factory.CloseDockable(dockable); } ``` -The focused dockable comes from the active root dock. When no document is focused, -`FocusedDockable` will be `null`. +The focused dockable can be `null` when nothing is focused. In multi-window +layouts, each root keeps its last focused dockable, so use the event to determine +the current focus. diff --git a/docs/dock-adapters.md b/docs/dock-adapters.md index 8d44d710e..7affeff8f 100644 --- a/docs/dock-adapters.md +++ b/docs/dock-adapters.md @@ -1,10 +1,10 @@ # Adapter Classes -Dock exposes a small set of helper classes that adapt the core interfaces to different host environments or provide additional runtime services. These adapters are mainly used by the sample applications but are part of the public API and can be reused in your own projects. +Dock exposes a small set of helper classes that adapt the core interfaces to different host environments or provide additional runtime services. The built-in model base classes (`DockWindow`, `DockBase`, `DockableBase`) wire these adapters in, so most applications interact with them through `IDock`, `IDockable`, and `IDockWindow`. You can use the adapters directly when implementing custom base types or host integrations. ## HostAdapter -`HostAdapter` implements `IHostAdapter` and bridges a dock window (`IDockWindow`) with a platform specific host window. It is responsible for presenting the floating window, updating its size and location and closing it when requested. +`HostAdapter` implements `IHostAdapter` and bridges a dock window (`IDockWindow`) with a platform-specific host window (`IHostWindow`). It resolves the host window via `IFactory.GetHostWindow` when needed, pushes layout, title, size, and position into the host when presenting, and reads the current size and position back when saving. Typical usage looks like the following: @@ -13,11 +13,11 @@ var hostAdapter = new HostAdapter(dockWindow); hostAdapter.Present(isDialog: false); ``` -The adapter fetches the window instance from `Factory.GetHostWindow` if it has not already been assigned. Calling `Save` stores the last known position and dimensions so that they can be restored later. +The adapter fetches the host window instance from `IFactory.GetHostWindow` if it has not already been assigned. Calling `Save` stores the last known position and dimensions so they can be restored later. `Exit` closes the host and clears the reference, and `SetActive` forwards the activation request to the host window. ## NavigateAdapter -`NavigateAdapter` implements `INavigateAdapter` and provides history based navigation for docks. It maintains two stacks for back and forward navigation and exposes methods such as `GoBack`, `GoForward` and `Navigate`. +`NavigateAdapter` implements `INavigateAdapter` and provides history-based navigation for docks. It maintains two stacks for back and forward navigation and exposes methods such as `GoBack`, `GoForward`, and `Navigate`. `Navigate` accepts an `IDockable`, a dockable ID (`string`), or `null` to reset navigation. Create an instance for a given `IDock` and use it to issue navigation commands: @@ -26,13 +26,13 @@ var adapter = new NavigateAdapter(rootDock); adapter.Navigate(document, bSnapshot: true); ``` -The adapter cooperates with the factory to activate dockables and can also show or close floating windows via `ShowWindows` and `ExitWindows`. +The adapter cooperates with the factory to activate dockables and can also show or close floating windows via `ShowWindows` and `ExitWindows`. The built-in `DockBase` types already use this adapter to implement `GoBack`, `GoForward`, `Navigate`, and `Close`. ## TrackingAdapter -`TrackingAdapter` stores bounds and pointer positions used when tools are pinned, visible or displayed as tabs. The docking logic reads these values when calculating drop targets or restoring the layout after a drag operation. +`TrackingAdapter` stores bounds and pointer positions used when dockables are visible, pinned, or displayed as tabs. It tracks both control-local and screen pointer positions. The docking logic reads these values when calculating drop targets or restoring the layout after a drag operation. -A new adapter contains `NaN` values for all coordinates until you call the various `Set*` methods. Most applications interact with `TrackingAdapter` indirectly through the built-in docking controls. +A new adapter contains `NaN` values for all coordinates until you call the various `Set*` methods. `DockableBase` uses it to implement the `IDockable` tracking methods, so most applications interact with `TrackingAdapter` indirectly through the built-in docking controls. ## When to use adapters diff --git a/docs/dock-advanced.md b/docs/dock-advanced.md index 5f8a720b0..8a46cfb01 100644 --- a/docs/dock-advanced.md +++ b/docs/dock-advanced.md @@ -1,11 +1,10 @@ # Dock Advanced Guide -This guide highlights advanced features from the Dock samples. The API is shared across the MVVM, ReactiveUI and XAML versions so the same concepts apply no matter which approach you use. -This guide assumes you are familiar with the basics from the other guides. Interface descriptions are available in the [Dock API Reference](dock-reference.md). It focuses on runtime customization and advanced APIs. +This guide highlights advanced features from the Dock samples. The API is shared across the MVVM, ReactiveUI, and Avalonia/XAML model variants (plus INPC/Prism/Caliburn.Micro), so the same concepts apply no matter which approach you use. This guide assumes you are familiar with the basics from the other guides. Interface descriptions are available in the [Dock API Reference](dock-reference.md). It focuses on runtime customization and advanced APIs. ## Custom factories -All samples derive from `Factory` and override methods to configure the layout. In addition to `CreateLayout`, you can override: +Many samples derive from a `Factory` implementation and override methods to configure the layout. In addition to `CreateLayout`, you can override: - `CreateWindowFrom` to customize new floating windows - `CreateDocumentDock` to provide a custom `IDocumentDock` implementation @@ -38,34 +37,32 @@ public override void InitLayout(IDockable layout) ## Handling events -`FactoryBase` exposes events for virtually every docking action. The samples subscribe to them to trace runtime changes: +`FactoryBase` exposes events for virtually every docking action. The samples subscribe to them to trace runtime changes. Events are useful for hooking into your application's own logging or analytics system. For example you might record when documents are opened or closed so that the next run can restore them. ```csharp +factory.DockableAdded += (_, args) => +{ + Debug.WriteLine($"[DockableAdded] {args.Dockable?.Title}"); +}; factory.ActiveDockableChanged += (_, args) => { Debug.WriteLine($"[ActiveDockableChanged] {args.Dockable?.Title}"); }; -factory.DockableAdded += (_, args) => +factory.FocusedDockableChanged += (_, args) => { - Debug.WriteLine($"[DockableAdded] {args.Dockable?.Title}"); + Debug.WriteLine($"[FocusedDockableChanged] {args.Dockable?.Title}"); }; ``` -// Example: track created and active documents -```csharp -factory.DockableAdded += (_, e) => Console.WriteLine($"Added {e.Dockable?.Id}"); -factory.ActiveDockableChanged += (_, e) => Console.WriteLine($"Active {e.Dockable?.Id}"); -``` - You can react to focus changes, window moves or when dockables are pinned and unpinned. ## Saving and loading layouts -The XAML sample demonstrates persisting layouts with `DockSerializer`: +The XAML sample demonstrates persisting layouts with an `IDockSerializer` implementation: ```csharp await using var stream = await file.OpenReadAsync(); @@ -73,10 +70,21 @@ var layout = _serializer.Load(stream); if (layout is { }) { dock.Layout = layout; + dock.Factory?.InitLayout(layout); _dockState.Restore(layout); } ``` +When saving, call `DockState.Save` before serializing the layout: + +```csharp +if (dock.Layout is { } layout) +{ + _dockState.Save(layout); + _serializer.Save(stream, layout); +} +``` + `DockState` tracks document/tool content (and document templates) so that state can be restored after loading. ## Dynamic documents and tools @@ -98,7 +106,7 @@ activated automatically. ### Modern ItemsSource approach -For more efficient document management, use the ItemsSource property to bind directly to data collections: +For more efficient document management in the Avalonia model, use `DocumentDock.ItemsSource` to bind directly to data collections: ```csharp // In your ViewModel @@ -120,7 +128,7 @@ With XAML binding: ``` -This approach automatically creates and removes documents as the collection changes, provides automatic title mapping from properties like `Title` or `Name`, and integrates seamlessly with MVVM patterns. See the [DocumentDock ItemsSource guide](dock-itemssource.md) for comprehensive details. +This approach automatically creates and removes documents as the collection changes, provides automatic title mapping from properties like `Title` or `Name`, and integrates seamlessly with MVVM patterns. `ItemsSource` requires a `DocumentTemplate`; without one, documents are not generated. See the [DocumentDock ItemsSource guide](dock-itemssource.md) for comprehensive details. Drag-and-drop handlers and file dialogs are used to open and save documents on the fly. diff --git a/docs/dock-api-scenarios.md b/docs/dock-api-scenarios.md index 935828669..e751c47d1 100644 --- a/docs/dock-api-scenarios.md +++ b/docs/dock-api-scenarios.md @@ -1,36 +1,33 @@ # Dock API Scenarios -This short document collects the most common ways the Dock API is used. The examples apply equally to the MVVM, ReactiveUI and XAML approaches. +This short document collects the most common ways the Dock API is used. The examples apply equally to the MVVM, ReactiveUI, and Avalonia/XAML model variants. ## Dock controls -Dock provides several themed controls that host parts of a layout. +Dock provides several themed controls that host parts of a layout. These are Avalonia controls used by the built-in themes and data templates. ### DockControl -The main control placed in your view to display a layout. Set the `Layout` and optionally the `Factory` properties from code or XAML: +The main control placed in your view to display a layout. Set the `Layout` and optionally the `Factory` properties from code or XAML: ```xaml - - - - + InitializeFactory="True" /> ``` ### DocumentDockControl -Displayed inside a `DocumentDock` to render documents. The control automatically binds to the active document and shows the `DocumentControl` template. +Default Avalonia control for `IDocumentDock`. The theme template switches between `DocumentControl` (tabbed) and `MdiDocumentControl` (MDI) based on `LayoutMode`. ### ToolDockControl -Used within a `ToolDock` to host tools or sidebars. This control handles layout grip behavior and interacts with `PinnedDockControl` when tools are pinned or auto-hidden. +Default Avalonia control for `IToolDock`. The theme template hosts `ToolChromeControl` and `ToolControl` to render the active tool and chrome buttons. ## Factories -All runtime operations go through a factory. Pick the implementation that matches your project style: +All runtime operations go through a factory. Pick the implementation that matches your project style: ### `Dock.Model.Avalonia.Factory` @@ -42,7 +39,9 @@ Provides base classes implementing `INotifyPropertyChanged`. Ideal for traditio ### `Dock.Model.ReactiveUI.Factory` -Wraps the same API with ReactiveUI types. Commands become `ReactiveCommand` and properties derive from `ReactiveObject`. +Wraps the same API with ReactiveUI types. Commands become `ReactiveCommand` and properties derive from `ReactiveObject`. + +Other packages expose the same factory pattern, including `Dock.Model.Prism.Factory`, `Dock.Model.CaliburMicro.Factory`, `Dock.Model.Inpc.Factory`, and `Dock.Model.ReactiveProperty.Factory`. A typical initialization sequence looks like: @@ -60,23 +59,28 @@ Use the MVVM, ReactiveUI or XAML samples as references for complete implementati ### Saving and restoring a layout -Layouts can be persisted using `DockSerializer` from the `Dock.Serializer.Newtonsoft` package. -Binary serialization is available via `ProtobufDockSerializer` in -the `Dock.Serializer.Protobuf` package. +Layouts can be persisted using any `IDockSerializer` implementation such as: +`Dock.Serializer.Newtonsoft`, `Dock.Serializer.SystemTextJson`, +`Dock.Serializer.Protobuf`, `Dock.Serializer.Xml`, or `Dock.Serializer.Yaml`. This allows users to keep their preferred window arrangement between sessions. ```csharp await using var stream = File.Create(path); +_dockState.Save(layout); _serializer.Save(stream, layout); ``` -Likewise you can restore a saved layout and reinitialise the factory: +Likewise you can restore a saved layout and reinitialize the factory and dock state: ```csharp await using var stream = File.OpenRead(path); -var layout = _serializer.Load(stream); -factory.InitLayout(layout); -dockControl.Layout = layout; +var layout = _serializer.Load(stream); +if (layout is { }) +{ + dockControl.Layout = layout; + dockControl.Factory?.InitLayout(layout); + _dockState.Restore(layout); +} ``` For an overview of all guides see the [documentation index](README.md). diff --git a/docs/dock-architecture.md b/docs/dock-architecture.md index 6b58b0b1a..bba67eec6 100644 --- a/docs/dock-architecture.md +++ b/docs/dock-architecture.md @@ -13,11 +13,15 @@ the [deep dive](dock-deep-dive.md). property that stops docking tools together when their fixed sizes clash. It also enforces docking group restrictions to control which dockables can be docked together. +- **DockService** – Performs the actual move/split/swap operations on docks and + dockables and calls into the factory to update view models. - **DockControlState** – Tracks pointer interactions and validates potential drop targets using `DockManager`. - **Factories** – Build and initialize dock view models. They expose commands for runtime operations such as adding or closing documents. -- **DockSerializer** – Loads and saves layouts to disk so user changes can +- **DockState** – Captures document/tool content and templates that are not + serialized with the layout so they can be restored after loading. +- **IDockSerializer** – Loads and saves layouts to disk so user changes can be persisted across sessions. ## Interaction overview @@ -27,23 +31,29 @@ flowchart TD UI[DockControl] State[DockControlState] Manager[DockManager] + Service[DockService] Factory[IFactory / FactoryBase] VM[Dock view models] - Serializer[DockSerializer] + Serializer[IDockSerializer] + StateStore[DockState] UI --> State State --> Manager - Manager --> Factory + Manager --> Service + Service --> Factory Factory --> VM UI --> VM Serializer <--> VM + StateStore <--> VM ``` 1. `DockControl` displays the layout created by the factory. 2. Pointer events are processed by `DockControlState` which consults `DockManager` to validate docking actions. -3. `DockManager` modifies the view models by invoking factory methods. -4. `DockSerializer` can load or save the view models using the configured serializer (JSON, XML, YAML, or binary). +3. `DockManager` delegates to `DockService`, which calls factory methods to + update the view models. +4. `IDockSerializer` loads or saves the layout model, while `DockState` captures + non-serialized content and templates so they can be restored after loading. Customising any of these pieces lets you extend Dock with application specific behavior. For an in-depth walkthrough see the [deep dive](dock-deep-dive.md). diff --git a/docs/dock-caliburn-micro.md b/docs/dock-caliburn-micro.md index ed969e517..6faf95098 100644 --- a/docs/dock-caliburn-micro.md +++ b/docs/dock-caliburn-micro.md @@ -17,6 +17,9 @@ Optional packages for serialization: ```bash dotnet add package Dock.Serializer.Newtonsoft # JSON (Newtonsoft.Json) dotnet add package Dock.Serializer.SystemTextJson # JSON (System.Text.Json) +dotnet add package Dock.Serializer.Protobuf # Binary (protobuf-net) +dotnet add package Dock.Serializer.Xml # XML +dotnet add package Dock.Serializer.Yaml # YAML ``` ## View location @@ -156,7 +159,6 @@ public sealed class MainViewModel { Factory = new DockFactory(); Layout = Factory.CreateLayout(); - Factory.InitLayout(Layout); } public IFactory Factory { get; } @@ -171,6 +173,8 @@ public sealed class MainViewModel InitializeLayout="True" /> ``` +If you call `Factory.InitLayout` manually, set `InitializeLayout="False"` to avoid double initialization. + ## Notes - The Caliburn.Micro model uses the same `IFactory` API and dockable interfaces as MVVM and ReactiveUI. diff --git a/docs/dock-code-only.md b/docs/dock-code-only.md index 882970d2c..1e6670ac2 100644 --- a/docs/dock-code-only.md +++ b/docs/dock-code-only.md @@ -1,7 +1,7 @@ # Dock Code-Only Guide This guide shows how to build a minimal Dock layout entirely in C# using -`Dock.Model.Avalonia` and `DockControl`. It mirrors the approach used in +`Dock.Model.Avalonia` and `DockControl`. It is based on `samples/DockCodeOnlySample` and avoids XAML or MVVM helpers. ## Create a new Avalonia project @@ -69,7 +69,7 @@ public class App : Application { Styles.Add(new FluentTheme()); Styles.Add(new DockFluentTheme()); - RequestedThemeVariant = ThemeVariant.Light; + RequestedThemeVariant = ThemeVariant.Dark; if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { @@ -98,7 +98,7 @@ public class App : Application { Id = "Doc1", Title = "Document 1", - Content = new TextBox { Text = "Document 1", AcceptsReturn = true } + // Content = new TextBox { Text = "Document 1", AcceptsReturn = true } }; documentDock.VisibleDockables = factory.CreateList(document); diff --git a/docs/dock-command-bars.md b/docs/dock-command-bars.md index 12032b95a..186c987f1 100644 --- a/docs/dock-command-bars.md +++ b/docs/dock-command-bars.md @@ -5,7 +5,7 @@ Dock can merge command bars from the active dockable into a host control at the ## How it works - `DockControl` templates include a `DockCommandBarHost` named `PART_CommandBarHost`. -- `DockCommandBarManager` listens for active dockable changes and queries the active dockable (or its `Context`) for command bar definitions. +- `DockCommandBarManager` listens for active dockable changes and queries the active dockable (or its `Context`) for command bar definitions. It also refreshes when the provider raises `CommandBarsChanged`. - Definitions are merged with any base bars and rendered using the default adapter into menu bars, tool bars, and ribbon bars. ## Enable command bar merging diff --git a/docs/dock-complex-layouts.md b/docs/dock-complex-layouts.md index 8585cf426..c1460fbc4 100644 --- a/docs/dock-complex-layouts.md +++ b/docs/dock-complex-layouts.md @@ -14,8 +14,7 @@ The default factory can open any dockable in a separate window. This allows you dotnet add package Dock.Avalonia dotnet add package Dock.Model.Mvvm dotnet add package Dock.Serializer.Newtonsoft - # or use the Protobuf variant - dotnet add package Dock.Serializer.Protobuf + # or use Dock.Serializer.SystemTextJson / Dock.Serializer.Protobuf / Dock.Serializer.Xml / Dock.Serializer.Yaml ``` 2. **Define a custom factory** @@ -57,12 +56,14 @@ The default factory can open any dockable in a separate window. This allows you ```csharp await using var write = File.OpenWrite("layout.json"); + _dockState.Save(dockControl.Layout); _serializer.Save(write, dockControl.Layout); await using var read = File.OpenRead("layout.json"); var layout = _serializer.Load(read); if (layout is { }) { + dockControl.Factory?.InitLayout(layout); dockControl.Layout = layout; _dockState.Restore(layout); } @@ -91,7 +92,8 @@ Applications can load additional dockables from plugins at runtime. Plugins typi .Select(t => (IPlugin)Activator.CreateInstance(t)!); foreach (var plugin in plugins) { - factory.AddDockable(rootLayout, plugin.CreateDockable()); + var dockable = plugin.CreateDockable(); + factory.AddDockable(rootLayout, dockable); } ``` diff --git a/docs/dock-content-guide.md b/docs/dock-content-guide.md index 4503ecbd7..1a6e3f3fa 100644 --- a/docs/dock-content-guide.md +++ b/docs/dock-content-guide.md @@ -26,6 +26,8 @@ Make sure you have the required NuGet packages installed: Examples below use `RelayCommand` from `CommunityToolkit.Mvvm.Input`. Use your own `ICommand` implementation if you prefer a different MVVM toolkit. +If you want MVVM-style base classes with `SetProperty`, install the matching model package (for example `Dock.Model.Mvvm`, `Dock.Model.ReactiveUI`, or `Dock.Model.CaliburMicro`) and use those `Document`/`Tool` base types in the examples. + For XAML usage, you need these namespace declarations: ```xaml @@ -44,7 +46,7 @@ The Dock framework supports four main approaches for defining content: ## Method 1: ItemsSource Collection Binding (Recommended) -DocumentDock supports automatic document creation from collections using the `ItemsSource` property, similar to how `ItemsControl` works in Avalonia. This is the recommended approach for most scenarios. +DocumentDock supports automatic document creation from collections using the `ItemsSource` property, similar to how `ItemsControl` works in Avalonia. This is the recommended approach for most scenarios and is available in the Avalonia model (`Dock.Model.Avalonia.Controls.DocumentDock`). ### When to Use This Approach @@ -259,7 +261,7 @@ This approach follows MVVM principles and provides the best flexibility for comp ### Step 1: Create a Document ViewModel ```csharp -using Dock.Model.Avalonia.Controls; +using Dock.Model.Mvvm.Controls; namespace YourApp.ViewModels.Documents; @@ -407,12 +409,12 @@ For simple static content, you can define it directly in XAML: ## Working with Tools -Tools work similarly to documents. Here's an example: +Tools work similarly to documents. Here's an example using MVVM base classes: ### Tool ViewModel ```csharp -using Dock.Model.Avalonia.Controls; +using Dock.Model.Mvvm.Controls; namespace YourApp.ViewModels.Tools; @@ -475,27 +477,33 @@ public class PropertiesToolViewModel : Tool ### Issue: "Unexpected content" Error -**Problem**: Getting `System.ArgumentException: "Unexpected content YourView"` when adding documents. +**Problem**: Getting `System.ArgumentException: "Unexpected content ..."` when adding documents. -**Cause**: Setting a UserControl instance directly to the `Content` property. +**Cause**: Setting `Content` to a view model or other unsupported object without a template. `Content` must be a `Control`, a template/function (`Func`), or template content produced by XAML. -**Solution**: Use one of the supported approaches above (ViewModel pattern or function-based content). +**Solution**: Use one of the supported approaches above (Control/Function/DataTemplate). ```csharp -// ❌ This will cause "Unexpected content" error +// ❌ Unsupported: view model instance without a template var document = new Document { - Content = new MyUserControl() // Don't do this + Content = new MyViewModel() }; -// ✅ Use ViewModel approach instead +// ✅ Use ViewModel + DataTemplate approach instead var document = new MyDocumentViewModel(); -// ✅ Or use function approach +// ✅ Or provide a Control or factory var document = new Document { Content = new Func(_ => new MyUserControl()) }; + +// ✅ Direct Control assignment is valid +var document = new Document +{ + Content = new MyUserControl() +}; ``` ### Issue: Empty/Blank Document Tabs with ItemsSource diff --git a/docs/dock-content-wrapper-pattern.md b/docs/dock-content-wrapper-pattern.md index b294c0354..07bb00b2b 100644 --- a/docs/dock-content-wrapper-pattern.md +++ b/docs/dock-content-wrapper-pattern.md @@ -2,7 +2,7 @@ This guide covers an advanced pattern for wrapping your own domain models with Dock's document system. This approach is useful when you want to maintain separation between your application's document model and Dock's infrastructure. -> **⚠️ Important**: With the introduction of `DocumentDock.ItemsSource`, most scenarios that required this wrapper pattern can now be solved more elegantly. See [Method 1: ItemsSource Collection Binding](dock-content-guide.md#method-1-itemssource-collection-binding-recommended) in the content guide for the recommended approach. +> **⚠️ Important**: With the introduction of `DocumentDock.ItemsSource` in the Avalonia model (`Dock.Model.Avalonia.Controls.DocumentDock`), most scenarios that required this wrapper pattern can now be solved more elegantly. See [Method 1: ItemsSource Collection Binding](dock-content-guide.md#method-1-itemssource-collection-binding-recommended) in the content guide for the recommended approach. > **Note**: This is an advanced pattern. For most use cases, the standard [Document and Tool Content Guide](dock-content-guide.md) approaches are recommended. **Consider ItemsSource first** - it provides the same domain separation with much less code. @@ -15,7 +15,7 @@ Before implementing the wrapper pattern below, consider using `ItemsSource` whic - + @@ -147,7 +147,6 @@ private void AddSceneDocument() }; DocumentsPane.AddDocument(dockDoc); - DocumentsPane.ActiveDockable = dockDoc; } ``` diff --git a/docs/dock-context-locator.md b/docs/dock-context-locator.md index f274f3318..7c7a0440a 100644 --- a/docs/dock-context-locator.md +++ b/docs/dock-context-locator.md @@ -6,10 +6,10 @@ Dock assigns `dockable.Context` when a layout is initialized or loaded if the do ## `ContextLocator` dictionary `ContextLocator` is a `Dictionary>` mapping an identifier to -a factory method. Each entry returns the object that becomes the `DataContext` of -the dockable with the same `Id`. -Populate this dictionary before calling `InitLayout` or loading a layout with -`DockSerializer`. +a factory method. Each entry returns the object that becomes `dockable.Context` +(often used as a view `DataContext`) for the dockable with the same `Id`. +Populate this dictionary before calling `InitLayout`, including when you +initialize a layout loaded with an `IDockSerializer`. ```csharp public override void InitLayout(IDockable layout) @@ -27,7 +27,8 @@ public override void InitLayout(IDockable layout) During `InitDockable` the factory looks up the dockable `Id` in `ContextLocator` and assigns the returned object to `dockable.Context` when it is still `null`. If the id is not found, it falls back to -`DefaultContextLocator`. +`DefaultContextLocator`. Empty ids skip the lookup entirely, so ensure you set +`Id` on dockables you want resolved. ## `DefaultContextLocator` fallback @@ -39,8 +40,9 @@ common fallback or to integrate with a dependency injection container. DefaultContextLocator = () => _services.GetService(); ``` -When `GetContext` cannot resolve a specific id it will call this delegate. If it -returns `null`, the dockable keeps its existing `Context` (often `null`). +When `GetContext` cannot resolve a specific non-empty id it will call this +delegate. If it returns `null`, the dockable keeps its existing `Context` +(often `null`). ## DockControl default context @@ -62,10 +64,11 @@ If you want to configure `DefaultContextLocator` yourself, disable ## Why it matters -Dockable views rely on their `DataContext` to function correctly. When loading a -layout that references custom documents or tools, the factory must be able to -recreate those view models. Register each type in `ContextLocator` and provide a -default via `DefaultContextLocator` so that unknown ids do not break the layout. +Dockable views often rely on `dockable.Context` (directly or via templates) to +function correctly. When loading a layout that references custom documents or +tools, the factory must be able to recreate those view models. Register each +type in `ContextLocator` and provide a default via `DefaultContextLocator` so +that unknown ids do not break the layout. These locators work alongside `DockableLocator` and `HostWindowLocator` which resolve dockable instances and host windows. Ensure all locators are populated diff --git a/docs/dock-context-menus.md b/docs/dock-context-menus.md index fd14aec32..3a680fb4d 100644 --- a/docs/dock-context-menus.md +++ b/docs/dock-context-menus.md @@ -1,6 +1,6 @@ # Context menus and flyouts -Dock defines several built-in context menus and flyouts that are attached to its controls. The menu definitions live in the control theme dictionaries, while the menu text is stored in `Controls/ControlStrings.axaml`. This document lists the available menus and describes how to localize or replace them. +Dock defines several built-in context menus and flyouts that are attached to its controls. The menu definitions live in the theme control dictionaries under `src/Dock.Avalonia.Themes.Fluent/Controls`, and the menu text is stored in `src/Dock.Avalonia.Themes.Fluent/Controls/ControlStrings.axaml`. The Simple theme links to the same control resources. This document lists the available menus and describes how to localize or replace them. ## List of built-in menus @@ -14,11 +14,11 @@ Dock defines several built-in context menus and flyouts that are attached to its The string resources used for menu item headers are defined in `Controls/ControlStrings.axaml`. For example it exposes keys such as `ToolTabStripItemFloatString`, `ToolTabStripItemDockString` and others. -When a dock's `CanCloseLastDockable` property is set to `false` the built-in menus automatically disable commands like **Close** or **Float** if executing them would remove the final item from that dock. +When a dock's `CanCloseLastDockable` property is set to `false` and only one dockable is visible, the built-in menus hide actions such as **Close** or **Float** using `CanRemoveDockableConverter`. ## Customizing menus and themes per control -Each control now exposes properties that allow you to customize the context menus, flyouts, and button themes for individual instances: +Most Dock controls are created by templates, so set these properties via styles or control themes unless you have a direct instance reference. Each control exposes properties that allow you to customize the context menus, flyouts, and button themes for individual instances: ### ToolChromeControl ```csharp @@ -49,6 +49,24 @@ myToolChromeControl.MaximizeButtonTheme = new ControlTheme(typeof(Button)) new Setter(Button.ForegroundProperty, Brushes.White) } }; + +myToolChromeControl.PinButtonTheme = new ControlTheme(typeof(Button)) +{ + Setters = + { + new Setter(Button.BackgroundProperty, Brushes.DarkSlateGray), + new Setter(Button.ForegroundProperty, Brushes.White) + } +}; + +myToolChromeControl.MenuButtonTheme = new ControlTheme(typeof(Button)) +{ + Setters = + { + new Setter(Button.BackgroundProperty, Brushes.DimGray), + new Setter(Button.ForegroundProperty, Brushes.White) + } +}; ``` ### ToolTabStripItem @@ -85,6 +103,16 @@ myMdiDocumentWindow.DocumentContextMenu = new ContextMenu new MenuItem { Header = "Custom Action", Command = myCommand } } }; + +// Optional: theme the close button +myMdiDocumentWindow.CloseButtonTheme = new ControlTheme(typeof(Button)) +{ + Setters = + { + new Setter(Button.BackgroundProperty, Brushes.Orange), + new Setter(Button.ForegroundProperty, Brushes.White) + } +}; ``` ### ToolPinItemControl @@ -172,7 +200,7 @@ The current design provides multiple levels of customization: 4. **Template customization**: The controls use template binding for their menus, making them more efficient and allowing for easier customization. -The new property-based approach provides the most flexibility for runtime customization, while the resource-based approach remains available for global changes. This design allows you to: +The property-based approach provides the most flexibility for runtime customization, while the resource-based approach remains available for global changes. This design allows you to: - Customize menus and button themes for specific controls while keeping the default for others - Add or remove menu items dynamically diff --git a/docs/dock-control-recycling.md b/docs/dock-control-recycling.md index 2d4292265..b470019d4 100644 --- a/docs/dock-control-recycling.md +++ b/docs/dock-control-recycling.md @@ -8,7 +8,7 @@ Include the `Dock.Controls.Recycling` NuGet package alongside your existing Dock ## Enabling recycling in XAML -Define a `ControlRecycling` resource and assign it to `DockControl` using the `ControlRecyclingDataTemplate.ControlRecycling` attached property. +Define a `ControlRecycling` resource and assign it to `DockControl` using the `ControlRecyclingDataTemplate.ControlRecycling` attached property. The attached property is inheritable, so setting it on the `DockControl` makes it available to the internal content presenters that opt into recycling. ```xaml @@ -23,7 +23,7 @@ Define a `ControlRecycling` resource and assign it to `DockControl` using the `C ``` -With this in place the controls generated for each dockable are kept in an internal cache. When the same view model is presented again the cached instance is reused. +With this in place the controls generated for each dockable are kept in an internal cache. When the same dockable data is presented again the cached view control is reused. ## Using IDs for caching @@ -33,7 +33,7 @@ With this in place the controls generated for each dockable are kept in an inter ``` -This allows the same dockable to be recognised even if the instance itself changes, for example after deserializing a layout. +This allows the same dockable to be recognized even if the instance itself changes, for example after deserializing a layout. Ensure `Id` values are stable and unique within a layout. ## Example @@ -56,7 +56,7 @@ public class DocumentViewModel : Dock.Model.Mvvm.Controls.DockableBase InitializeFactory="True" /> ``` -As documents are opened and closed the same `DocumentControl` is reused for a given `DocumentViewModel`. If needed the cache can be cleared manually: +As documents are opened and closed the same view control is reused for a given `DocumentViewModel`. If needed the cache can be cleared manually: ```csharp controlRecycling.Clear(); @@ -64,7 +64,7 @@ controlRecycling.Clear(); ### XAML-only layouts -Control recycling also applies when the layout is declared purely in XAML. Dockables defined in markup can specify an `Id` which the recycling cache uses to match and reuse visuals. This lets tools and documents retain their internal state even when no view models or bindings are present. +Control recycling also applies when the layout is declared purely in XAML. Dockables defined in markup can specify an `Id` which the recycling cache can use (via `TryToUseIdAsKey`) to match and reuse visuals even after a reload. This lets tools and documents retain their internal state even when no view models or bindings are present. ```xaml @@ -85,4 +85,8 @@ Control recycling also applies when the layout is declared purely in XAML. Docka With recycling enabled the `Document` and `Tool` above keep their content each time they are reopened or toggled. Even without bindings the `TextBox` and `Slider` values persist. Control recycling works for both MVVM and ReactiveUI samples as well as the XAML-only approach. Inspect the `App.axaml` files under the `samples` directory for complete working examples. +### Custom templates + +Recycling is enabled by default in the Fluent and Simple theme templates because they include `ControlRecyclingDataTemplate` in the document and tool content presenters. If you build a custom control theme, include `ControlRecyclingDataTemplate` in your content presenter template so the cache can participate. + For an overview of all guides see the [documentation index](README.md). diff --git a/docs/dock-controls-reference.md b/docs/dock-controls-reference.md index 7881f7f6b..bbc2e7a7a 100644 --- a/docs/dock-controls-reference.md +++ b/docs/dock-controls-reference.md @@ -2,7 +2,7 @@ This reference lists Dock-specific properties for Avalonia controls in the `Dock.Avalonia` packages. Base Avalonia properties (for example, `Width`, `IsVisible`, `Background`) are not repeated here. -Unless noted otherwise, the properties listed are Avalonia styled properties and can be set in XAML or code. +Unless noted otherwise, the properties listed are Avalonia styled properties and can be set in XAML or code. Properties labeled "CLR" are regular properties that must be set in code. ## Core host controls @@ -17,10 +17,10 @@ Unless noted otherwise, the properties listed are Avalonia styled properties and | `DefaultContext` | `object?` | Fallback context for the default context locator. | | `AutoCreateDataTemplates` | `bool` | Automatically injects Dock DataTemplates when `true`. | | `IsDraggingDock` | `bool` | Set while a drag operation is active; useful for styling. | -| `DragOffsetCalculator` | `IDragOffsetCalculator` | Controls drag preview positioning. | -| `DockManager` | `IDockManager` | Dock manager service (read-only). | -| `DockControlState` | `IDockControlState` | Internal docking state (read-only). | -| `IsOpen` | `bool` | `true` when the selector overlay is open (read-only). | +| `DragOffsetCalculator` | `IDragOffsetCalculator` | CLR property that controls drag preview positioning. | +| `DockManager` | `IDockManager` | CLR property for the dock manager service (read-only). | +| `DockControlState` | `IDockControlState` | CLR property for internal docking state (read-only). | +| `IsOpen` | `bool` | CLR property; `true` when the selector overlay is open (read-only). | ### DockCommandBarHost @@ -194,9 +194,9 @@ Unless noted otherwise, the properties listed are Avalonia styled properties and | `IsToolWindow` | `bool` | Marks the window as a tool window. | | `ToolChromeControlsWholeWindow` | `bool` | Allows tool chrome to drag the full window. | | `DocumentChromeControlsWholeWindow` | `bool` | Allows document chrome to drag the full window. | -| `HostWindowState` | `IHostWindowState` | Docking state for the host window (read-only). | -| `IsTracked` | `bool` | Indicates that the host window is tracked. | -| `Window` | `IDockWindow?` | Backing dock window model. | +| `HostWindowState` | `IHostWindowState` | CLR property for docking state (read-only). | +| `IsTracked` | `bool` | CLR property that indicates the window is tracked. | +| `Window` | `IDockWindow?` | CLR property for the backing dock window model. | ### HostWindowTitleBar diff --git a/docs/dock-custom-model.md b/docs/dock-custom-model.md index 11c5c3b64..de3e9d7aa 100644 --- a/docs/dock-custom-model.md +++ b/docs/dock-custom-model.md @@ -15,7 +15,7 @@ You can create your own implementation when these do not fit your application or ## Using Dock.Model.Inpc -If you only need basic property change notifications without full MVVM command support, use `Dock.Model.Inpc`: +If you only need basic property change notifications and lightweight `ICommand` implementations without a framework dependency, use `Dock.Model.Inpc`: ```bash dotnet add package Dock.Model.Inpc @@ -88,15 +88,23 @@ This package provides `INotifyPropertyChanged` implementations without the addit 5. **Provide command and property change logic** that matches your MVVM framework. For example, you might expose `ICommand` objects or ReactiveUI commands. -A minimal dockable using `INotifyPropertyChanged` might look like this: +A minimal dockable using `INotifyPropertyChanged` might look like this (implement the `IDockable` members you need, or start from `Dock.Model.Inpc.Core.DockableBase` and adapt it): ```csharp -public class MyDockable : DockableBase, INotifyPropertyChanged +public abstract class MyDockableBase : IDockable, INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; - protected void OnPropertyChanged([CallerMemberName] string? name = null) => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + protected void Set(ref T field, T value, [CallerMemberName] string? name = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + field = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + } + + // Implement IDockable members here or copy from Dock.Model.Inpc.Core.DockableBase. } ``` diff --git a/docs/dock-custom-theme.md b/docs/dock-custom-theme.md index 38e17becc..c354482c9 100644 --- a/docs/dock-custom-theme.md +++ b/docs/dock-custom-theme.md @@ -4,7 +4,9 @@ This guide walks through creating a theme file from scratch. Use it when the bui ## 1. Create an accent dictionary -Define brushes and colors in a new `.axaml` file: +Define brushes and colors in a new `.axaml` file. Start from +`src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml` to see the full set of +resource keys used by the built-in templates: ```xaml + ``` @@ -21,7 +24,10 @@ Define brushes and colors in a new `.axaml` file: ## 2. Merge Dock control styles -Create another `.axaml` file that merges the Dock controls together with your accent dictionary: +Create another `.axaml` file that merges the Dock controls together with your +accent dictionary. The Simple theme reuses the Fluent control templates, so you +can reference them directly via `avares://Dock.Avalonia.Themes.Fluent/Controls/...` +or copy them into your own assembly and include `/Controls/...` resources. ```xaml + - - + + diff --git a/docs/dock-datatemplates.md b/docs/dock-datatemplates.md index fbc6f72cc..40acc84c7 100644 --- a/docs/dock-datatemplates.md +++ b/docs/dock-datatemplates.md @@ -98,10 +98,10 @@ public interface ICustomTabDock : IDock ### 2. Implement the Interface -Create a concrete implementation using your preferred model (Avalonia, MVVM, ReactiveUI, etc.): +Create a concrete implementation using your preferred model (Avalonia, MVVM, ReactiveUI, etc.). Use the `DockBase` type from the model package you picked. ```csharp -public class CustomTabDock : Dock, ICustomTabDock +public class CustomTabDock : DockBase, ICustomTabDock { public string TabStyle { get; set; } = "Default"; public bool ShowTabIcons { get; set; } = true; @@ -154,7 +154,7 @@ When working with custom dock types and DataTemplates: 2. **Use descriptive naming** following the pattern: `IMyDock`, `MyDock`, `MyDockControl` 3. **Add custom templates directly** - they work alongside the default templates automatically 4. **Test your custom types** to ensure DataTemplate coverage -5. **Use the bang operator** (`!`) for property bindings when creating templates programmatically +5. **Use bindings** for template properties when creating templates programmatically, as shown in `DockDataTemplateHelper` 6. **Consider inheritance** - derive from existing dock types when possible 7. **Only disable `AutoCreateDataTemplates`** when you want to provide all templates manually diff --git a/docs/dock-debug-overlay.md b/docs/dock-debug-overlay.md index 93441cd36..42f3e5d09 100644 --- a/docs/dock-debug-overlay.md +++ b/docs/dock-debug-overlay.md @@ -15,10 +15,19 @@ toggled at runtime using F9 by default: ```csharp #if DEBUG -var disposable = this.AttachDockDebugOverlay(); +// TopLevel/Window overload returns IDisposable so you can detach later. +var overlay = this.AttachDockDebugOverlay(); #endif ``` Call the extension method on your `App` or on a specific `Window`/`TopLevel` to -register the hot key. Optionally pass your own `KeyGesture` to change the -shortcut. +register the hot key. The `Application` overload attaches to existing windows +and does not return a disposable: + +```csharp +#if DEBUG +Application.Current?.AttachDockDebugOverlay(); +#endif +``` + +Optionally pass your own `KeyGesture` to change the shortcut. diff --git a/docs/dock-dockable-control.md b/docs/dock-dockable-control.md index be9a16ae0..ec9b742f3 100644 --- a/docs/dock-dockable-control.md +++ b/docs/dock-dockable-control.md @@ -6,9 +6,9 @@ When its `DataContext` is an `IDockable`, `DockableControl`: -- Registers itself in the appropriate factory dictionary (`VisibleDockableControls`, `PinnedDockableControls`, or `TabDockableControls`). +- Registers itself in the appropriate factory dictionary (`VisibleDockableControls`, `PinnedDockableControls`, or `TabDockableControls`) and also records the templated root in `VisibleRootControls`, `PinnedRootControls`, or `TabRootControls`. - Stores bounds via `SetVisibleBounds`, `SetPinnedBounds`, or `SetTabBounds`. -- Updates pointer positions during drag operations. +- Updates pointer positions (including screen coordinates) during pointer presses and moves. The tracking behavior depends on the `TrackingMode` property. diff --git a/docs/dock-dockablelocator.md b/docs/dock-dockablelocator.md index bb32fa92c..197fb7fef 100644 --- a/docs/dock-dockablelocator.md +++ b/docs/dock-dockablelocator.md @@ -4,9 +4,9 @@ ## Why registration is required -Layouts persist the full dockable graph, so `DockSerializer` restores dockable instances directly from the serialized data. `DockableLocator` is for cases where you want to resolve dockables by id at runtime, such as navigation, tool activation, or custom lookup helpers. +Layouts persist the full dockable graph, so an `IDockSerializer` restores dockable instances directly from the serialized data. `DockableLocator` is for cases where you want to resolve dockables by id at runtime, such as navigation, tool activation, or custom lookup helpers. -`FactoryBase` exposes `GetDockable` as a thin wrapper around the dictionary and returns the object typed as `IDockable`. +`FactoryBase` exposes `GetDockable` as a thin wrapper around the dictionary and returns the object typed as `T`. ## Typical setup @@ -36,6 +36,7 @@ var tool = factory.GetDockable("Tool1"); ``` This method simply forwards the call to `DockableLocator` and casts the result. It returns `null` if the id is not registered. +Empty ids also return `null`. ## Best practices @@ -44,6 +45,6 @@ This method simply forwards the call to `DockableLocator` and casts the result. - Keep id strings short but descriptive. They must match the ids you pass to `GetDockable`. - If you use dependency injection, the factory methods can resolve services before creating the dockable. -Following these guidelines guarantees that serialization and dynamic creation work reliably. +Following these guidelines keeps id-based lookup predictable without affecting serialization. For an overview of all documentation topics see the [documentation index](README.md). diff --git a/docs/dock-docking-groups.md b/docs/dock-docking-groups.md index 07e3d5277..041d1c513 100644 --- a/docs/dock-docking-groups.md +++ b/docs/dock-docking-groups.md @@ -55,7 +55,7 @@ var toolB = new Tool DockGroup = "Tools" }; -// Create an unrestricted dock that only accepts other unrestricted dockables +// Create an unrestricted dock that only accepts other unrestricted dockables (local rules) var unrestrictedDock = new ToolDock { Id = "UnrestrictedDock", @@ -63,12 +63,12 @@ var unrestrictedDock = new ToolDock DockGroup = null // Only accepts other null-group dockables }; -// Create unrestricted tools that can only dock in unrestricted areas +// Create unrestricted tools (local docking only; global docking allows ungrouped sources anywhere) var unrestrictedTool = new Tool { Id = "UnrestrictedTool", Title = "Unrestricted Tool", - DockGroup = null // Can only dock with other null-group targets + DockGroup = null // Locally only docks with other null-group targets }; ``` @@ -403,17 +403,11 @@ var searchResults = new Tool ## Attached Property Support -The `DockGroup` is also available as an attached property through `DockProperties.DockGroup`. This provides additional flexibility for setting groups in XAML templates and enables inheritance down the visual tree: - -```xaml - - - - - - -``` +`DockProperties.DockGroup` is an attached property on Avalonia controls. The +built-in templates bind it to the dockable's `DockGroup` so you can style or +inspect groups in the visual tree. It does not override the model's +`DockGroup`—docking validation still uses `IDockable.DockGroup` and owner +inheritance. ### Attached Property Methods @@ -425,7 +419,10 @@ DockProperties.SetDockGroup(myControl, "MyGroup"); string? group = DockProperties.GetDockGroup(myControl); ``` -The attached property supports inheritance, so setting it on a parent container automatically applies the group to all child elements unless they have their own explicit group. +The attached property supports inheritance, so setting it on a parent container +automatically applies the group to all child elements unless they have their own +explicit group. Use it for styling or visual diagnostics, not to drive docking +rules. ## Implementation Details diff --git a/docs/dock-enums.md b/docs/dock-enums.md index 175213e8b..4834a5334 100644 --- a/docs/dock-enums.md +++ b/docs/dock-enums.md @@ -99,7 +99,7 @@ Indicates the current state of an MDI document window. ## GripMode -Determines how the grip element of a splitter behaves. +Determines how the grip element in tool chrome behaves (used by tool docks). | Value | Description | | ----- | ----------- | @@ -119,7 +119,7 @@ Indicates which bounds `TrackingAdapter` is currently storing. ## DragAction -Represents the allowed drag and drop actions when interacting with external data sources. This is a flags enum, so values can be combined. +Represents the drag and drop action Dock should perform when docking. This is a flags enum, but Dock uses single values (`Move`, `Link`, `Copy`) for internal docking gestures. | Value | Description | | ----- | ----------- | diff --git a/docs/dock-events.md b/docs/dock-events.md index dad2163d0..adcd52fbe 100644 --- a/docs/dock-events.md +++ b/docs/dock-events.md @@ -22,9 +22,9 @@ support cancellation via a `Cancel` flag. | `DockableDocked` | Raised after a dock operation completes such as splitting or floating. | | `DockableUndocked` | Raised when a dockable is removed from a dock before being moved or floated. | | `DockablePinned` / `DockableUnpinned` | Signalled when a tool is pinned or unpinned. | -| `WindowOpened` / `WindowClosed` | Fired when a floating window is created or closed. | - -Other specialized events like `WindowMoveDragBegin`, `WindowMoveDrag` and `WindowMoveDragEnd` allow intercepting drag operations. +| `WindowOpened` | Fired when a floating window is created. | +| `WindowClosing` | Fired before a floating window closes and can be cancelled. | +| `WindowClosed` | Fired after a floating window is closed. | ## Additional events @@ -38,6 +38,7 @@ FactoryBase also exposes events for less common scenarios: | `WindowActivated` / `WindowDeactivated` | Fired when a host window gains or loses activation. | | `DockableActivated` / `DockableDeactivated` | Fired when a dockable gains or loses activation. | | `DockableInit` | Raised during `InitDockable` so you can override the resolved `Context`. | +| `WindowMoveDragBegin` / `WindowMoveDrag` / `WindowMoveDragEnd` | Fired during floating window drag operations. | ## Subscribing to events diff --git a/docs/dock-faq.md b/docs/dock-faq.md index c49a4dab5..97db9d9e6 100644 --- a/docs/dock-faq.md +++ b/docs/dock-faq.md @@ -26,10 +26,11 @@ This approach automatically creates/removes documents when you add/remove items **My document tabs are blank/empty (ItemsSource approach)** For `ItemsSource` documents, check: -1. `DocumentTemplate` has `x:DataType="Document"` on the root element -2. Access your model via `{Binding Context.PropertyName}` not `{Binding PropertyName}` -3. Your model implements `INotifyPropertyChanged` -4. Collection items have recognizable property names like `Title`, `Name`, or `CanClose` +1. `DocumentTemplate` is set (no template means no documents are created) +2. Collection items expose `Title`, `Name`, or `DisplayName` (or override `ToString`) for tab headers +3. Access your model via `{Binding Context.PropertyName}` not `{Binding PropertyName}` +4. Your model implements `INotifyPropertyChanged` if you expect updates +5. If you use compiled bindings, set `x:DataType="Document"` on the template root **My document tabs are blank/empty (ViewModel approach)** @@ -72,7 +73,7 @@ Then bind in XAML: - + @@ -100,7 +101,7 @@ Active and focused dockables are serialized with the layout itself, so you do no **Deserialization fails with unknown types** -`DockSerializer` uses type information embedded in the layout. If a type cannot be resolved, ensure the assembly that defines it is loaded and referenced by the application. For dependency injection scenarios, construct the serializer with an `IServiceProvider` so it can resolve types from the container. +`IDockSerializer` implementations (for example `Dock.Serializer.SystemTextJson.DockSerializer`) use type information embedded in the layout. If a type cannot be resolved, ensure the assembly that defines it is loaded and referenced by the application. For dependency injection scenarios, construct the serializer with an `IServiceProvider` so it can resolve types from the container. **What is `DockableLocator` and `ContextLocator`?** diff --git a/docs/dock-fluent-extensions.md b/docs/dock-fluent-extensions.md index 7e0bf3329..2b3edd937 100644 --- a/docs/dock-fluent-extensions.md +++ b/docs/dock-fluent-extensions.md @@ -1,4 +1,4 @@ -### Fluent API for Dock.Model +# Fluent API for Dock.Model This guide shows how to build layouts using the fluent extension methods added to `Dock.Model`. The fluent API wraps factory creation and common configuration into chainable calls. You can: - Create objects and keep them in variables (no `out var` overloads) @@ -240,4 +240,3 @@ var dockControl = new DockControl { Factory = factory, Layout = root }; - The fluent API is additive; existing `IFactory.Create*` APIs remain available. - Prefer the configuration lambda over post-creation property sets when possible for readability. - For Avalonia hosting, assign the `Factory` and the created `IRootDock` to `DockControl`. Set `InitializeFactory`/`InitializeLayout` if you want automatic initialization. - diff --git a/docs/dock-glossary.md b/docs/dock-glossary.md index cbc44cc0a..ce83bb980 100644 --- a/docs/dock-glossary.md +++ b/docs/dock-glossary.md @@ -11,7 +11,7 @@ This page defines common terms used throughout the Dock documentation and sample - **DockWindow / HostWindow** – Floating window created when a dockable is detached from the main layout. - **Layout** – The arrangement of docks and dockables currently shown by `DockControl`. - **Factory** – Class responsible for creating and modifying layouts at runtime. -- **DockSerializer** – Component that saves or loads layouts so user customisations persist across sessions. +- **IDockSerializer** – Interface for saving or loading layouts; `Dock.Serializer.*` provides concrete implementations such as `DockSerializer`. - **DockManager** – Implements the algorithms that move or swap dockables during drag and drop. - **HostAdapter** – Bridges an `IDockWindow` with the platform window used to display it. - **NavigateAdapter** – Provides back and forward history navigation for docks. diff --git a/docs/dock-inpc.md b/docs/dock-inpc.md index b7c04c31a..0fda235f6 100644 --- a/docs/dock-inpc.md +++ b/docs/dock-inpc.md @@ -1,8 +1,8 @@ # Dock INPC Getting Started Guide -This guide explains how to get started with the INPC (INotifyPropertyChanged) implementation of Dock. The INPC model provides basic property change notifications without the overhead of full MVVM command patterns, making it ideal for simpler scenarios or custom MVVM frameworks. +This guide explains how to get started with the INPC (INotifyPropertyChanged) implementation of Dock. The INPC model provides basic property change notifications with lightweight `ICommand` support (via `SimpleCommand`) without requiring a full MVVM framework, making it ideal for simpler scenarios or custom MVVM stacks. -The `Dock.Model.Inpc` package provides `INotifyPropertyChanged` implementations without additional command support. The sample project `DockInpcSample` in the repository demonstrates this approach. For interface details refer to the [Dock API Reference](dock-reference.md). +The `Dock.Model.Inpc` package provides `INotifyPropertyChanged` implementations along with simple command helpers used by the built-in dock models. The sample project `DockInpcSample` in the repository demonstrates this approach. For interface details refer to the [Dock API Reference](dock-reference.md). > **💡 Modern Approach**: For easier document management, consider using [DocumentDock.ItemsSource](dock-itemssource.md) which automatically creates and manages documents from collections. This approach is covered in detail in the [Document and Tool Content Guide](dock-content-guide.md). @@ -322,8 +322,8 @@ The INPC implementation provides: - ✅ Property change notifications via `INotifyPropertyChanged` - ✅ Basic docking functionality - ✅ Serialization support -- ❌ No built-in command support (you need to implement your own) -- ❌ No advanced MVVM patterns like commanding infrastructure +- ✅ Lightweight `SimpleCommand` support for built-in docking commands +- ❌ No advanced MVVM command infrastructure (e.g., ReactiveUI command patterns) This makes it perfect for scenarios where you want the docking functionality without the overhead of a full MVVM framework, or when integrating with custom MVVM implementations. diff --git a/docs/dock-markup-extensions.md b/docs/dock-markup-extensions.md index d1f6a797b..33fe01573 100644 --- a/docs/dock-markup-extensions.md +++ b/docs/dock-markup-extensions.md @@ -25,6 +25,6 @@ The URI is resolved relative to the current `IUriContext`. Any element type can The extension searches the nearest name scope for an element called `OkButton` and returns it. Use it as a binding source when you need to reference another element in the same scope. -Both extensions reside in the `Avalonia.MarkupExtension` namespace and can be imported in XAML using `xmlns:markup="clr-namespace:Avalonia.MarkupExtension"`. +Both extensions reside in the `Avalonia.MarkupExtension` namespace and can be imported in XAML using `xmlns:markup="clr-namespace:Avalonia.MarkupExtension;assembly=Dock.MarkupExtension"`. For an overview of all guides see the [documentation index](README.md). diff --git a/docs/dock-model-controls.md b/docs/dock-model-controls.md index cc59964cc..96ea8de3e 100644 --- a/docs/dock-model-controls.md +++ b/docs/dock-model-controls.md @@ -75,19 +75,19 @@ the `CreateDocument` command. When assigned, this factory is invoked to create a new document which is then added and activated through the `AddDocument` helper. -`IDocumentDockContent` extends this concept by storing a -`DocumentTemplate` object. Calling `CreateDocumentFromTemplate` -should produce a new view model implementing `IDocument`. This is -useful when a document type can be instantiated from a template, -for example when creating a blank file. +`IDocumentDockContent` (implemented by the Avalonia model layer) extends this +concept by storing a `DocumentTemplate` object. `CreateDocumentFromTemplate` +typically creates a new document and sets its `IDocumentContent.Content` from +the template (for example `DocumentTemplate.Content` in the Avalonia +implementation). This is useful for "New" commands or ItemsSource-generated +documents where the view is defined in XAML. ## IDocumentTemplate -Templates are lightweight objects that store any information required -to create a document instance. A factory can inspect the template -and return a fully initialized document. Use this pattern to support -"New" commands that generate a specific kind of document without -hard-coding the creation logic in your views. +`IDocumentTemplate` represents template content used to build document views. In +Avalonia the built-in `DocumentTemplate` stores XAML content (or a +`Func`) that becomes the document `Content` when a new +document is created. ## IProportionalDock and IProportionalDockSplitter diff --git a/docs/dock-mvvm.md b/docs/dock-mvvm.md index 503317bc8..0a8857935 100644 --- a/docs/dock-mvvm.md +++ b/docs/dock-mvvm.md @@ -223,7 +223,7 @@ The following steps walk you through creating a very small application that uses - + @@ -234,7 +234,7 @@ The following steps walk you through creating a very small application that uses > **💡 Tip**: The ItemsSource approach (Option B) provides cleaner separation between your business models and the docking infrastructure, while still working perfectly with MVVM patterns. -4. **Initialize the layout in your main view model** +5. **Initialize the layout in your main view model** ```csharp _factory = new DockFactory(); @@ -242,19 +242,20 @@ The following steps walk you through creating a very small application that uses _factory.InitLayout(Layout); ``` - `InitLayout` configures services such as `ContextLocator` and - `DockableLocator`. Use these to reattach `Context` objects after loading - layouts or to enable id-based lookups via `GetDockable`. Override this - method in your factory if you need to register additional mappings or + `InitLayout` resolves `ContextLocator`/`DockableLocator` entries (if you + configured them) and assigns owners/active dockables. Populate these + dictionaries before calling `InitLayout` if you need to reattach `Context` + objects after loading layouts or enable id-based lookups via `GetDockable`. + Override this method in your factory to register additional mappings or perform custom initialization logic. -5. **Add `DockControl` to `MainWindow.axaml`** +6. **Add `DockControl` to `MainWindow.axaml`** ```xaml ``` -6. **Run the project** +7. **Run the project** ```bash dotnet run diff --git a/docs/dock-properties.md b/docs/dock-properties.md index 160b8c85e..687ef933e 100644 --- a/docs/dock-properties.md +++ b/docs/dock-properties.md @@ -18,7 +18,7 @@ The available properties are: | `ShowDockIndicatorOnly` | `bool` | Hides the dock target visuals and displays only drop indicators. | | `IndicatorDockOperation` | `DockOperation` | Specifies which dock operation a control represents when only indicators are shown. | | `DockAdornerHost` | `Control` | Specifies the element that should display the dock target adorner. | -| `DockGroup` | `string` | Group identifier for restriction-based docking. See [Docking Groups](dock-docking-groups.md). | +| `DockGroup` | `string` | Visual grouping hint for templates (usually bound from `IDockable.DockGroup`). See [Docking Groups](dock-docking-groups.md). | ## Root Dock Settings @@ -30,6 +30,8 @@ In addition to the properties above, `IRootDock` provides settings that control This setting must be configured on your root dock instance and is particularly useful in scenarios where you want to minimize visual clutter and provide a more focused docking experience. +Note that docking rules are enforced by the `IDockable.DockGroup` values in the model; the attached `DockProperties.DockGroup` is used by control templates to surface that value in the visual tree. + ## Using the properties in control themes Every control template that participates in docking should set the appropriate `DockProperties`. The default themes include them on tab strips, pinned panels and window chrome. When creating a custom template copy these setters so dragging continues to work: diff --git a/docs/dock-proportional-stackpanel.md b/docs/dock-proportional-stackpanel.md index e4131b319..15c13605d 100644 --- a/docs/dock-proportional-stackpanel.md +++ b/docs/dock-proportional-stackpanel.md @@ -7,13 +7,14 @@ - **Orientation** – determines whether children are arranged horizontally or vertically. - **ProportionProperty** – attached property that specifies the size ratio for each child. Values are normalized so that all proportions add up to `1`. - **IsCollapsedProperty** – attached property that temporarily collapses a child without removing it from the layout. +- **CollapsedProportionProperty** – attached property that remembers the last non-collapsed proportion so a panel can restore its size. - **ProportionalStackPanelSplitter** – interactive splitter used to adjust proportions at runtime. Insert between two children. ## Basic usage ```xaml + xmlns:psp="clr-namespace:Dock.Controls.ProportionalStackPanel;assembly=Dock.Controls.ProportionalStackPanel"> diff --git a/docs/dock-reactiveui-di.md b/docs/dock-reactiveui-di.md index c6c118d3a..f9330dbe1 100644 --- a/docs/dock-reactiveui-di.md +++ b/docs/dock-reactiveui-di.md @@ -158,61 +158,63 @@ Follow these instructions to create a ReactiveUI application with dependency inj using Dock.Model.ReactiveUI.Controls; using MyDockApp.Models; - namespace MyDockApp.ViewModels.Documents; - - public class DocumentViewModel : Document + namespace MyDockApp.ViewModels.Documents { - private readonly IDataService _dataService; - private readonly CompositeDisposable _disposables = new(); - - public DocumentViewModel(IDataService dataService) + public class DocumentViewModel : Document { - _dataService = dataService; - - var data = _dataService.GetData(); - Content = data.Message; - - // Set up reactive properties and commands as needed - this.WhenAnyValue(x => x.Content) - .Subscribe(content => - { - var updatedData = new DemoData { Message = content }; - _dataService.UpdateData(updatedData); - }) - .DisposeWith(_disposables); - } + private readonly IDataService _dataService; + private readonly CompositeDisposable _disposables = new(); - private string _content = string.Empty; - public string Content - { - get => _content; - set => this.RaiseAndSetIfChanged(ref _content, value); + public DocumentViewModel(IDataService dataService) + { + _dataService = dataService; + + var data = _dataService.GetData(); + Content = data.Message; + + // Set up reactive properties and commands as needed + this.WhenAnyValue(x => x.Content) + .Subscribe(content => + { + var updatedData = new DemoData { Message = content }; + _dataService.UpdateData(updatedData); + }) + .DisposeWith(_disposables); + } + + private string _content = string.Empty; + public string Content + { + get => _content; + set => this.RaiseAndSetIfChanged(ref _content, value); + } } } - namespace MyDockApp.ViewModels.Tools; - - public class ToolViewModel : Tool + namespace MyDockApp.ViewModels.Tools { - private readonly IDataService _dataService; - - public ToolViewModel(IDataService dataService) + public class ToolViewModel : Tool { - _dataService = dataService; - RefreshData(); - } + private readonly IDataService _dataService; - private string _status = string.Empty; - public string Status - { - get => _status; - set => this.RaiseAndSetIfChanged(ref _status, value); - } + public ToolViewModel(IDataService dataService) + { + _dataService = dataService; + RefreshData(); + } - private void RefreshData() - { - var data = _dataService.GetData(); - Status = $"Last updated: {data.LastUpdated:HH:mm:ss}"; + private string _status = string.Empty; + public string Status + { + get => _status; + set => this.RaiseAndSetIfChanged(ref _status, value); + } + + private void RefreshData() + { + var data = _dataService.GetData(); + Status = $"Last updated: {data.LastUpdated:HH:mm:ss}"; + } } } ``` @@ -435,10 +437,13 @@ Follow these instructions to create a ReactiveUI application with dependency inj ```csharp using System; using Avalonia; + using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; + using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; - using ReactiveUI; using Microsoft.Extensions.DependencyInjection; + using MyDockApp.ViewModels; + using ReactiveUI; namespace MyDockApp; @@ -457,27 +462,27 @@ Follow these instructions to create a ReactiveUI application with dependency inj _viewLocator = viewLocator; } - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + DataTemplates.Insert(0, (IDataTemplate)_viewLocator); + } - public override void OnFrameworkInitializationCompleted() + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && ServiceProvider != null) { - // Set the ReactiveUI view locator - Locator.CurrentMutable.RegisterConstant(_viewLocator, typeof(IViewLocator)); - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + var viewModel = ServiceProvider.GetRequiredService(); + var view = ServiceProvider.GetRequiredService>(); + view.ViewModel = viewModel; + if (view is Window window) { - var mainWindow = ServiceProvider?.GetRequiredService(); - if (mainWindow != null) - { - desktop.MainWindow = mainWindow; - } + desktop.MainWindow = window; } - - base.OnFrameworkInitializationCompleted(); } + + base.OnFrameworkInitializationCompleted(); + } } ``` diff --git a/docs/dock-reactiveui-routing.md b/docs/dock-reactiveui-routing.md index c6a58a717..12ebad56e 100644 --- a/docs/dock-reactiveui-routing.md +++ b/docs/dock-reactiveui-routing.md @@ -39,7 +39,7 @@ Follow these instructions to create a ReactiveUI application with routing using 3. **Set up View Locator with Routing Support** - Create a view locator that supports both regular views and routable views: + Create a view locator that resolves views registered with ReactiveUI's `IViewLocator`: ```csharp using System; @@ -47,23 +47,26 @@ Follow these instructions to create a ReactiveUI application with routing using using Avalonia.Controls.Templates; using Dock.Model.Core; using ReactiveUI; - using StaticViewLocator; + using Splat; namespace MyDockApp; - [StaticViewLocator] - public partial class ViewLocator : IDataTemplate + public class ViewLocator : IDataTemplate { public Control? Build(object? data) { if (data is null) + { return null; + } - var type = data.GetType(); - if (s_views.TryGetValue(type, out var func)) - return func.Invoke(); + var viewLocator = Locator.Current.GetService(); + if (viewLocator?.ResolveView(data) is Control control) + { + return control; + } - throw new Exception($"Unable to create view for type: {type}"); + throw new Exception($"Unable to create view for type: {data.GetType()}"); } public bool Match(object? data) @@ -73,12 +76,35 @@ Follow these instructions to create a ReactiveUI application with routing using return false; } - var type = data.GetType(); - return data is IDockable || s_views.ContainsKey(type); + if (data is IDockable) + { + return true; + } + + var viewLocator = Locator.Current.GetService(); + return viewLocator?.ResolveView(data) is not null; } } ``` + Register the view mappings in `App.axaml.cs`: + + ```csharp + using ReactiveUI; + using Splat; + + private void RegisterViews() + { + Locator.CurrentMutable.Register>(() => new DocumentView()); + Locator.CurrentMutable.Register>(() => new ToolView()); + Locator.CurrentMutable.Register>(() => new Page1View()); + Locator.CurrentMutable.Register>(() => new Page2View()); + Locator.CurrentMutable.Register>(() => new InnerView()); + } + ``` + + Call `RegisterViews()` from `Initialize` so the mappings are available before the layout is created. + Register the view locator in `App.axaml`: ```xaml GoToPage1 { get; } - public ReactiveCommand GoToPage2 { get; } - private readonly CompositeDisposable _disposables = new(); - - public DocumentViewModel(IScreen host) : base(host) + public class DocumentViewModel : RoutableDocument { - // Navigate to the default page - Router.Navigate.Execute(new Page1ViewModel(this, "Document Home")); + public ReactiveCommand? GoToPage1 { get; } + public ReactiveCommand? GoToPage2 { get; } + private readonly CompositeDisposable _disposables = new(); + + public DocumentViewModel(IScreen host) : base(host) + { + // Navigate to the default page + Router.Navigate.Execute(new Page1ViewModel(this, "Document Home")); - // Commands to navigate to different pages - GoToPage1 = ReactiveCommand.CreateFromObservable(() => - Router.Navigate.Execute(new Page1ViewModel(this, "Page 1 Content"))); + // Commands to navigate to different pages + GoToPage1 = ReactiveCommand.Create(() => + Router.Navigate.Execute(new Page1ViewModel(this, "Page 1 Content")).Subscribe(_ => { })); - GoToPage2 = ReactiveCommand.CreateFromObservable(() => - Router.Navigate.Execute(new Page2ViewModel(this, "Page 2 Content"))); + GoToPage2 = ReactiveCommand.Create(() => + Router.Navigate.Execute(new Page2ViewModel(this, "Page 2 Content")).Subscribe(_ => { })); - GoToPage1.DisposeWith(_disposables); - GoToPage2.DisposeWith(_disposables); - } + GoToPage1.DisposeWith(_disposables); + GoToPage2.DisposeWith(_disposables); + } - public void InitNavigation( - IRoutableViewModel? document2, - IRoutableViewModel? tool1, - IRoutableViewModel? tool2) - { - // Navigation to other dockables can be set up here - // This is called from the factory after all view models are created + public void InitNavigation( + IRoutableViewModel? document2, + IRoutableViewModel? tool1, + IRoutableViewModel? tool2) + { + // Navigation to other dockables can be set up here + // This is called from the factory after all view models are created + } } } - public class ToolViewModel : RoutableTool + namespace MyDockApp.ViewModels.Tools { - public ReactiveCommand GoToDocument1 { get; set; } - public ReactiveCommand GoToDocument2 { get; set; } - public ReactiveCommand GoToNextTool { get; set; } - - public ToolViewModel(IScreen host) : base(host) - { - Router.Navigate.Execute(new InnerViewModel(this, "Tool Home")); - } - - public void InitNavigation( - IRoutableViewModel? document1, - IRoutableViewModel? document2, - IRoutableViewModel? nextTool) + public class ToolViewModel : RoutableTool { - if (document1 is not null) - { - GoToDocument1 = ReactiveCommand.Create(() => - HostScreen.Router.Navigate.Execute(document1).Subscribe(_ => { })); - } + public ReactiveCommand? GoToDocument1 { get; set; } + public ReactiveCommand? GoToDocument2 { get; set; } + public ReactiveCommand? GoToNextTool { get; set; } - if (document2 is not null) + public ToolViewModel(IScreen host) : base(host) { - GoToDocument2 = ReactiveCommand.Create(() => - HostScreen.Router.Navigate.Execute(document2).Subscribe(_ => { })); + Router.Navigate.Execute(new InnerViewModel(this, "Tool Home")); } - if (nextTool is not null) + public void InitNavigation( + IRoutableViewModel? document1, + IRoutableViewModel? document2, + IRoutableViewModel? nextTool) { - GoToNextTool = ReactiveCommand.Create(() => - HostScreen.Router.Navigate.Execute(nextTool).Subscribe(_ => { })); + if (document1 is not null) + { + GoToDocument1 = ReactiveCommand.Create(() => + HostScreen.Router.Navigate.Execute(document1).Subscribe(_ => { })); + } + + if (document2 is not null) + { + GoToDocument2 = ReactiveCommand.Create(() => + HostScreen.Router.Navigate.Execute(document2).Subscribe(_ => { })); + } + + if (nextTool is not null) + { + GoToNextTool = ReactiveCommand.Create(() => + HostScreen.Router.Navigate.Execute(nextTool).Subscribe(_ => { })); + } } } } diff --git a/docs/dock-reactiveui.md b/docs/dock-reactiveui.md index 375dd4099..b64843c90 100644 --- a/docs/dock-reactiveui.md +++ b/docs/dock-reactiveui.md @@ -217,7 +217,7 @@ Follow these instructions to create a minimal ReactiveUI based application using } ``` -4. **Initialize the layout using Reactive commands** +5. **Initialize the layout in your view model** ```csharp _factory = new DockFactory(); @@ -225,13 +225,13 @@ Follow these instructions to create a minimal ReactiveUI based application using _factory.InitLayout(Layout); ``` -5. **Add `DockControl` to the main view** +6. **Add `DockControl` to the main view** ```xaml ``` -6. **Run the application** +7. **Run the application** ```bash dotnet run diff --git a/docs/dock-settings-controls.md b/docs/dock-settings-controls.md index 62b5a0c6f..591f96bf1 100644 --- a/docs/dock-settings-controls.md +++ b/docs/dock-settings-controls.md @@ -14,8 +14,7 @@ The `DocumentTabStrip` control uses the settings when it decides whether a point ```csharp var delta = currentPoint - _dragStartPoint; -if (!(Math.Abs(delta.X) > DockSettings.MinimumHorizontalDragDistance) - && !(Math.Abs(delta.Y) > DockSettings.MinimumVerticalDragDistance)) +if (!DockSettings.IsMinimumDragDistance(delta)) { return; // Not enough movement yet } @@ -31,6 +30,10 @@ You can apply the same check in your custom `PointerMoved` handlers before calli - `IsDragArea` marks a region where the user can start dragging. - `IsDropArea` marks a region that can receive drops. - `IsDragEnabled` and `IsDropEnabled` allow you to enable or disable interactions globally for any subtree. +- `ShowDockIndicatorOnly` shows only drop indicators, hiding the dock target visuals. +- `IndicatorDockOperation` sets the dock operation for controls that only show indicators. +- `DockAdornerHost` directs the dock adorner to a different host control. +- `DockGroup` restricts docking to matching groups (inherited through the visual tree). These properties are normally set in the default styles but you can place them on your own controls: diff --git a/docs/dock-settings.md b/docs/dock-settings.md index 4eafd2b41..f52129dfb 100644 --- a/docs/dock-settings.md +++ b/docs/dock-settings.md @@ -15,6 +15,12 @@ They are used by the default control templates but you can set them manually: | `IsDropArea` | `bool` | Identifies a region that can accept dropped dockables. | | `IsDragEnabled` | `bool` | Globally enables or disables dragging for child dockables. | | `IsDropEnabled` | `bool` | Globally enables or disables dropping of dockables. | +| `ShowDockIndicatorOnly` | `bool` | Shows only drop indicators, hiding the dock target visuals. | +| `IndicatorDockOperation` | `DockOperation` | Sets the dock operation when showing indicators only. | +| `DockAdornerHost` | `Control` | Hosts the dock target adorner instead of the adorned control. | +| `DockGroup` | `string` | Restricts docking to matching groups (inherited). | + +Use `DockGroup` to restrict docking operations across your layout. For details see [Docking groups](dock-docking-groups.md). Example disabling drag and drop for an entire window: diff --git a/docs/dock-sizing.md b/docs/dock-sizing.md index c67f0ceab..f57984ee1 100644 --- a/docs/dock-sizing.md +++ b/docs/dock-sizing.md @@ -4,7 +4,7 @@ This guide explains how to control the size of dockable items beyond the default ## Proportions vs fixed sizes -`ProportionalDock` arranges its children according to their `Proportion` property. This is ideal when panes should resize together. For pixel perfect layouts assign `MinWidth`, `MaxWidth`, `MinHeight` or `MaxHeight` on the dockable view model. When these limits are present the layout engine honours them before applying proportions. +`ProportionalDock` arranges its children according to their `Proportion` property. This is ideal when panes should resize together. For pixel perfect layouts assign `MinWidth`, `MaxWidth`, `MinHeight` or `MaxHeight` on the dockable view model. The proportional size is then clamped to these limits. Setting both the minimum and maximum to the same value effectively locks the dimension: @@ -45,7 +45,7 @@ The dock splits space proportionally and then clamps the result within the speci ``` This is useful when combining fixed size panes with resizable ones or when you want to lock down a layout entirely. -Setting `ResizePreview="True"` shows only a drag indicator while moving and applies the size when released. The splitter is highlighted during the drag so the preview remains visible against any background. +Setting `ResizePreview="True"` shows a drag preview while moving and applies the size when released. ## Common scenarios diff --git a/docs/dock-styling.md b/docs/dock-styling.md index 837c12f0c..138ed9c39 100644 --- a/docs/dock-styling.md +++ b/docs/dock-styling.md @@ -30,13 +30,13 @@ You can override brushes and other resources to match your application colors: Any `SolidColorBrush` referenced by the theme can be replaced this way. Controls automatically pick up the new values. -`ToolChromeControl` defines an extra brush for its icons and drag grip: +`ToolChromeControl` defines an extra brush for its icons: ```xaml ``` -Include replacements for these in your accent dictionary to change the grip or button colors. +Include replacements for these in your accent dictionary to change icon or button colors. Several icons are also exposed as `StreamGeometry` resources so you can swap them with your own glyphs: @@ -115,14 +115,14 @@ continue to work. For example `ToolChromeControl` defines `PART_Grip`, Controls also toggle pseudo classes to reflect their current state. These can be targeted in selectors to customize the appearance: -- `DocumentControl` and `DocumentTabStripItem` use `:active`. +- `DocumentControl`, `DocumentTabStrip`, and `DocumentTabStripItem` use `:active`. - `DocumentTabStrip` and `ToolTabStrip` apply `:create` when new items can be added. - `ToolChromeControl` sets `:active`, `:pinned`, `:floating` and `:maximized`. - `HostWindow` toggles `:toolwindow`, `:dragging`, `:toolchromecontrolswindow` and `:documentchromecontrolswindow`. - `ProportionalStackPanelSplitter` uses `:horizontal` or `:vertical` depending on - orientation. + orientation, and `:preview` while preview resizing is active. These pseudo classes are driven by properties such as `IsActive` and `CanCreateItem` on the respective controls. diff --git a/docs/dock-targets.md b/docs/dock-targets.md index 15991fff2..5657c1091 100644 --- a/docs/dock-targets.md +++ b/docs/dock-targets.md @@ -4,7 +4,7 @@ Dock shows drop indicators using two controls: `DockTarget` (local docking) and ## How targets are created -When a drag starts, `DockControlState` locates a control marked with `DockProperties.IsDockTarget` and creates a target adorner for it. The adorner is hosted either in the visual tree or inside a floating window, depending on `DockSettings.UseFloatingDockAdorner`. +When a drag starts, `DockControlState` locates a control marked with `DockProperties.IsDropArea`. If that control is also marked with `DockProperties.IsDockTarget`, the docking pipeline creates a target adorner for it. The adorner is hosted either in the visual tree or inside a floating window, depending on `DockSettings.UseFloatingDockAdorner`. Local targets are attached to the drop control (or `DockProperties.DockAdornerHost` if set). Global targets are attached to the nearest `DockControl` and display global docking options when they are available. @@ -17,6 +17,7 @@ These `DockProperties` determine how targets behave: | Property | Purpose | | --- | --- | | `IsDockTarget` | Marks the control as a docking surface. | +| `IsDropArea` | Marks the control as a valid drop surface. | | `ShowDockIndicatorOnly` | Hide the selector icons and show indicators only. | | `IndicatorDockOperation` | Assigns a `DockOperation` (Top/Bottom/Left/Right/Fill) to an indicator part. | | `DockAdornerHost` | Redirects the adorner to a specific host control. | diff --git a/docs/dock-user-viewmodel.md b/docs/dock-user-viewmodel.md index 968a6899d..8fe32e5cb 100644 --- a/docs/dock-user-viewmodel.md +++ b/docs/dock-user-viewmodel.md @@ -56,7 +56,7 @@ Applications often have their own view models that expose domain specific proper var factory = new DockFactory(); var layout = factory.CreateLayout(); factory.InitLayout(layout); - Dock.Layout = layout; + dockControl.Layout = layout; ``` Your custom view models now behave like any other Dock documents or tools while still exposing your application specific state. diff --git a/docs/dock-window-drag.md b/docs/dock-window-drag.md index 831d5bf97..43eea6a80 100644 --- a/docs/dock-window-drag.md +++ b/docs/dock-window-drag.md @@ -19,10 +19,10 @@ var documents = new DocumentDock The same property is available on the Avalonia control `DocumentDock` and can also be set in XAML: ```xml - + ``` -With the property enabled, `DocumentTabStrip` listens for pointer events on its background and calls `BeginMoveDrag` on the surrounding `HostWindow`. The user can grab the tab area and drag the entire window. +With the property enabled, `DocumentTabStrip` listens for pointer events on its background and initiates a window drag on the surrounding `HostWindow` (or window). The user can grab the tab area and drag the entire window. ## Scenarios diff --git a/docs/dock-xaml.md b/docs/dock-xaml.md index 98fd7f140..2bb277108 100644 --- a/docs/dock-xaml.md +++ b/docs/dock-xaml.md @@ -33,9 +33,9 @@ These steps outline how to set up a small Dock application that defines its layo dotnet add package Dock.Serializer.Yaml # YAML ``` -3. **Set up View Locator (Required)** +3. **Set up View Templates (Optional)** - Even for XAML-only layouts, you need a view locator for document and tool content. Choose one of the following approaches: + If you rely on view models via `Context`, register a view locator or data templates for your document and tool views. If you define `Document.Content` or use `DocumentTemplate` in XAML, you can skip this step. Choose one of the following approaches: **Option A: Static View Locator with Source Generators (Recommended)** @@ -107,7 +107,7 @@ These steps outline how to set up a small Dock application that defines its layo } ``` - Register the view locator in `App.axaml`: + If you use a view locator, register it in `App.axaml`: ```xaml **💡 Tip**: The ItemsSource approach (Option B) provides automatic document management and is recommended for most applications. For details, see the [DocumentDock ItemsSource guide](dock-itemssource.md). -4. **Save and load layouts** +5. **Save and load layouts** Use `DockSerializer` from code-behind to persist or restore the layout. @@ -216,7 +216,7 @@ When declaring layouts this way you typically still provide a small models and hook into runtime events while keeping the layout defined in markup. -5. **Run the application** +6. **Run the application** ```bash dotnet run diff --git a/docs/quick-start.md b/docs/quick-start.md index 6e006e1d5..34675eeea 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -42,9 +42,9 @@ This short guide shows how to set up Dock in a new Avalonia application. You wil dotnet add package Dock.Model.Avalonia ``` -4. **Set up View Locator (Required)** +3. **Set up View Templates (Optional)** - Dock requires a view locator to map view models to their corresponding views. Choose one of the following approaches: + If you rely on view models via `Context`, register a view locator or data templates for your document and tool views. If you define `Document.Content` or use `DocumentTemplate` in XAML, you can skip this step. **Option A: Static View Locator with Source Generators (Recommended)** @@ -58,7 +58,6 @@ This short guide shows how to set up Dock in a new Avalonia application. You wil using System; using Avalonia.Controls; using Avalonia.Controls.Templates; - using CommunityToolkit.Mvvm.ComponentModel; using Dock.Model.Core; using StaticViewLocator; @@ -136,9 +135,9 @@ This short guide shows how to set up Dock in a new Avalonia application. You wil } ``` -5. **Add Dock styles and View Locator** +4. **Add Dock styles (and View Locator if used)** - Reference the theme and register the view locator in `App.axaml`: + Reference the theme and register the view locator in `App.axaml` if you use one: ```xaml ``` -6. **Add a simple layout** +5. **Add a simple layout** **Option A: Traditional MVVM Approach** @@ -253,15 +252,20 @@ This short guide shows how to set up Dock in a new Avalonia application. You wil xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dock="https://github.com/avaloniaui"> - + + + + + - + - + + ``` @@ -305,7 +309,7 @@ This short guide shows how to set up Dock in a new Avalonia application. You wil For instructions on mapping documents and tools to views see the [Views guide](dock-views.md). -7. **Run the application** +6. **Run the application** ```bash dotnet run