Доработан Docking
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
using Lattice.Core.Docking.Engine;
|
||||
using Lattice.Core.Docking.Models;
|
||||
using Lattice.UI.Docking.Abstractions;
|
||||
using Lattice.UI.Docking.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
@@ -12,9 +11,22 @@ using System.Runtime.CompilerServices;
|
||||
namespace Lattice.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Визуальный контрол для отображения группы разделения (сплиттера).
|
||||
/// Реализует интерфейс <see cref="IDockGroupControl"/> для интеграции с системой докинга.
|
||||
/// Визуальный контрол для отображения группы разделения (сплиттера) в системе докинга.
|
||||
/// Реализует интерфейс <see cref="IDockGroupControl"/> для интеграции с системой докинга
|
||||
/// и обеспечивает отображение двух дочерних элементов с разделителем между ними.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Контрол <see cref="LatticeDockGroup"/> отвечает за визуальное представление узла
|
||||
/// дерева компоновки, который разделяет доступное пространство между двумя дочерними
|
||||
/// элементами. Поддерживает горизонтальное и вертикальное разделение с возможностью
|
||||
/// изменения соотношения сторон через перетаскивание разделителя.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Контрол автоматически обновляет свое представление при изменении свойств модели
|
||||
/// и обеспечивает двустороннюю привязку данных с объектом <see cref="DockGroup"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
{
|
||||
private readonly PropertyChangedEventHandler _modelPropertyChangedHandler;
|
||||
@@ -24,18 +36,20 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
private ContentControl? _firstChildControl;
|
||||
private ContentControl? _secondChildControl;
|
||||
private LayoutManager? _layoutManager;
|
||||
private DockDragDropService? _dragDropService;
|
||||
private IDockContextManager? _contextManager;
|
||||
private bool _isSelected;
|
||||
private bool _isActive;
|
||||
private bool _canDrag = true;
|
||||
private bool _canDrop = true;
|
||||
private double _splitRatio = 0.5;
|
||||
private double _splitterSize = 4.0;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="LatticeDockGroup"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Конструктор устанавливает ключ стиля по умолчанию, инициализирует обработчик
|
||||
/// изменений модели и подписывается на событие изменения контекста данных.
|
||||
/// Созданный контрол готов к использованию после применения шаблона.
|
||||
/// </remarks>
|
||||
public LatticeDockGroup()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(LatticeDockGroup);
|
||||
@@ -43,7 +57,18 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
this.DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает модель данных, связанную с этим контролом.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Экземпляр <see cref="DockGroup"/>, представляющий узел разделения в дереве компоновки.
|
||||
/// Может быть null, если контрол не связан с моделью.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// При установке новой модели контрол автоматически подписывается на события
|
||||
/// изменения свойств модели и обновляет свое визуальное представление.
|
||||
/// При удалении модели происходит отписка от событий и очистка ресурсов.
|
||||
/// </remarks>
|
||||
public IDockElement? Model
|
||||
{
|
||||
get => _model;
|
||||
@@ -57,7 +82,18 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает менеджер макета, к которому принадлежит этот контрол.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Экземпляр <see cref="LayoutManager"/>, управляющий структурой док-системы.
|
||||
/// Может быть null, если контрол не связан с менеджером макета.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Менеджер макета используется для выполнения операций с деревом компоновки,
|
||||
/// таких как перемещение элементов, создание плавающих окон и управление
|
||||
/// автоскрываемыми панелями.
|
||||
/// </remarks>
|
||||
public LayoutManager? LayoutManager
|
||||
{
|
||||
get => _layoutManager;
|
||||
@@ -69,19 +105,17 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDockDragDropService? DragDropService
|
||||
{
|
||||
get => _dragDropService;
|
||||
set
|
||||
{
|
||||
if (_dragDropService == value) return;
|
||||
_dragDropService = value;
|
||||
OnPropertyChanged(nameof(DragDropService));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает контекстный менеджер для этого контрола.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Экземпляр <see cref="IDockContextManager"/> или null, если менеджер не установлен.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Контекстный менеджер используется для отображения контекстных меню при щелчке
|
||||
/// правой кнопкой мыши по контролу. Меню содержит команды, доступные для данного
|
||||
/// элемента в текущем контексте.
|
||||
/// </remarks>
|
||||
public IDockContextManager? ContextManager
|
||||
{
|
||||
get => _contextManager;
|
||||
@@ -93,7 +127,18 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает признак того, что контрол выбран.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// true, если контрол выбран; в противном случае false.
|
||||
/// Значение по умолчанию: false.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Выделенный контрол обычно визуально отличается от других (например, имеет
|
||||
/// выделенную границу или фон). В каждый момент времени может быть выделен
|
||||
/// только один контрол в пределах контейнера.
|
||||
/// </remarks>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
@@ -105,7 +150,17 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает признак того, что контрол активен.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// true, если контрол активен; в противном случае false.
|
||||
/// Значение по умолчанию: false.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Активный контрол получает фокус ввода и может обрабатывать команды клавиатуры.
|
||||
/// Обычно соответствует последнему взаимодействию пользователя с элементом.
|
||||
/// </remarks>
|
||||
public bool IsActive
|
||||
{
|
||||
get => _isActive;
|
||||
@@ -117,31 +172,20 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanDrag
|
||||
{
|
||||
get => _canDrag;
|
||||
set
|
||||
{
|
||||
if (_canDrag == value) return;
|
||||
_canDrag = value;
|
||||
OnPropertyChanged(nameof(CanDrag));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanDrop
|
||||
{
|
||||
get => _canDrop;
|
||||
set
|
||||
{
|
||||
if (_canDrop == value) return;
|
||||
_canDrop = value;
|
||||
OnPropertyChanged(nameof(CanDrop));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает ориентацию разделения группы.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Направление разделения (горизонтальное или вертикальное).
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Ориентация определяет, как расположены дочерние элементы относительно друг друга:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="SplitDirection.Horizontal"/> - элементы расположены слева и справа</item>
|
||||
/// <item><see cref="SplitDirection.Vertical"/> - элементы расположены сверху и снизу</item>
|
||||
/// </list>
|
||||
/// Изменение ориентации приводит к перестройке внутреннего макета контрола.
|
||||
/// </remarks>
|
||||
public SplitDirection Orientation
|
||||
{
|
||||
get => _model?.Orientation ?? SplitDirection.Horizontal;
|
||||
@@ -155,7 +199,18 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает соотношение разделения между первым и вторым элементами.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Значение от 0.0 до 1.0, где 0.5 означает равное разделение пространства.
|
||||
/// Значение 0.0 отдает все пространство второму элементу, 1.0 - первому элементу.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Соотношение разделения определяет пропорции, в которых доступное пространство
|
||||
/// распределяется между дочерними элементами. Изменение этого свойства приводит
|
||||
/// к перестройке внутреннего макета и генерации события <see cref="SplitRatioChanged"/>.
|
||||
/// </remarks>
|
||||
public double SplitRatio
|
||||
{
|
||||
get => _splitRatio;
|
||||
@@ -173,7 +228,17 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает или задает размер разделителя в пикселях.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Ширина разделителя в пикселях. Значение по умолчанию: 4.0.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Размер разделителя определяет область, доступную для перетаскивания пользователем
|
||||
/// для изменения соотношения разделения. Увеличение размера облегчает взаимодействие,
|
||||
/// но уменьшает полезное пространство для содержимого.
|
||||
/// </remarks>
|
||||
public double SplitterSize
|
||||
{
|
||||
get => _splitterSize;
|
||||
@@ -187,19 +252,57 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает контрол для первого дочернего элемента.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Контрол, отображающий первый дочерний элемент, или null, если элемент не установлен.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Первый дочерний элемент занимает левую область при горизонтальной ориентации
|
||||
/// или верхнюю область при вертикальной ориентации.
|
||||
/// </remarks>
|
||||
public IDockControl? FirstChild => _firstChildControl?.Content as IDockControl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Получает контрол для второго дочернего элемента.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Контрол, отображающий второй дочерний элемент, или null, если элемент не установлен.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Второй дочерний элемент занимает правую область при горизонтальной ориентации
|
||||
/// или нижнюю область при вертикальной ориентации.
|
||||
/// </remarks>
|
||||
public IDockControl? SecondChild => _secondChildControl?.Content as IDockControl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Происходит при изменении соотношения разделения между дочерними элементами.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Событие генерируется при изменении свойства <see cref="SplitRatio"/>,
|
||||
/// независимо от источника изменения (пользователь, программа или восстановление состояния).
|
||||
/// Содержит информацию о новом соотношении и источнике изменения.
|
||||
/// </remarks>
|
||||
public event EventHandler<SplitRatioChangedEventArgs>? SplitRatioChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Происходит при изменении значения свойства.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Событие реализует интерфейс <see cref="INotifyPropertyChanged"/> и используется
|
||||
/// для уведомления системы привязки данных об изменениях свойств контрола.
|
||||
/// </remarks>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Вызывается при применении шаблона контрола.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Метод получает ссылки на именованные части шаблона и инициализирует
|
||||
/// внутренние структуры контрола. Вызывает обновление макета для корректного
|
||||
/// отображения дочерних элементов.
|
||||
/// </remarks>
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
@@ -211,11 +314,28 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
UpdateLayoutDefinitions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменение контекста данных контрола.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события (контрол).</param>
|
||||
/// <param name="args">Данные о изменении контекста.</param>
|
||||
/// <remarks>
|
||||
/// Метод автоматически устанавливает модель контрола на основе нового контекста данных,
|
||||
/// если он является экземпляром <see cref="DockGroup"/>. Это позволяет использовать
|
||||
/// привязку данных XAML для установки модели контрола.
|
||||
/// </remarks>
|
||||
private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
Model = args.NewValue as DockGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Присоединяет модель к контролу.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Подписывается на события изменения свойств модели, устанавливает контекст данных
|
||||
/// и инициализирует свойства контрола значениями из модели. Вызывает обновление макета.
|
||||
/// </remarks>
|
||||
private void AttachModel()
|
||||
{
|
||||
if (_model != null)
|
||||
@@ -229,6 +349,13 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отсоединяет модель от контрола.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Отписывается от событий изменения свойств модели, очищает контекст данных
|
||||
/// и освобождает ресурсы, связанные с предыдущей моделью.
|
||||
/// </remarks>
|
||||
private void DetachModel()
|
||||
{
|
||||
if (_model != null)
|
||||
@@ -238,6 +365,16 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменения свойств модели.
|
||||
/// </summary>
|
||||
/// <param name="sender">Источник события (модель).</param>
|
||||
/// <param name="e">Данные об изменении свойства.</param>
|
||||
/// <remarks>
|
||||
/// Реагирует на изменения ключевых свойств модели (Orientation, SplitRatio)
|
||||
/// и обновляет соответствующие свойства и визуальное представление контрола.
|
||||
/// Также уведомляет систему привязки данных об изменении свойств контрола.
|
||||
/// </remarks>
|
||||
private void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
@@ -258,6 +395,14 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет определения макета сетки на основе текущей ориентации и соотношения разделения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Метод перестраивает структуру строк и столбцов сетки в зависимости от ориентации
|
||||
/// разделения и текущего соотношения между дочерними элементами. Обеспечивает
|
||||
/// корректное позиционирование разделителя и дочерних контролов.
|
||||
/// </remarks>
|
||||
private void UpdateLayoutDefinitions()
|
||||
{
|
||||
if (_rootGrid == null || _model == null) return;
|
||||
@@ -313,7 +458,15 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Устанавливает дочерние контролы для отображения.
|
||||
/// </summary>
|
||||
/// <param name="firstChild">Контрол для первого элемента.</param>
|
||||
/// <param name="secondChild">Контрол для второго элемента.</param>
|
||||
/// <remarks>
|
||||
/// Метод назначает контролы для визуального представления дочерних элементов группы.
|
||||
/// После установки контролов обновляет макет для корректного отображения.
|
||||
/// </remarks>
|
||||
public void SetChildren(IDockControl? firstChild, IDockControl? secondChild)
|
||||
{
|
||||
if (_firstChildControl != null)
|
||||
@@ -325,13 +478,27 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
UpdateLayoutDefinitions();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Обновляет внешний вид контрола в соответствии с текущим состоянием модели.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Вызывает перестройку макета сетки для синхронизации визуального представления
|
||||
/// с текущими значениями свойств модели (ориентация, соотношение разделения).
|
||||
/// </remarks>
|
||||
public void Refresh()
|
||||
{
|
||||
UpdateLayoutDefinitions();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Применяет указанную тему к контролу.
|
||||
/// </summary>
|
||||
/// <param name="theme">Тема для применения.</param>
|
||||
/// <remarks>
|
||||
/// Обновляет стили и параметры отображения контрола в соответствии с заданной темой.
|
||||
/// В текущей реализации метод является заглушкой и должен быть расширен для
|
||||
/// поддержки динамического изменения тем оформления.
|
||||
/// </remarks>
|
||||
public void ApplyTheme(IDockTheme theme)
|
||||
{
|
||||
// Применение темы к контролу
|
||||
@@ -341,7 +508,14 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Вызывается при изменении состояния модели для обновления UI.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Имя изменившегося свойства модели.</param>
|
||||
/// <remarks>
|
||||
/// Перенаправляет вызов в обработчик изменений модели, обеспечивая уведомление
|
||||
/// контрола о конкретных изменениях в связанной модели данных.
|
||||
/// </remarks>
|
||||
public void OnModelPropertyChanged(string propertyName)
|
||||
{
|
||||
if (_model != null)
|
||||
@@ -350,12 +524,27 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывает событие изменения свойства.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Имя изменившегося свойства.</param>
|
||||
/// <remarks>
|
||||
/// Используется для уведомления системы привязки данных об изменениях свойств
|
||||
/// контрола. Если имя свойства не указано, автоматически определяется по имени
|
||||
/// вызывающего члена.
|
||||
/// </remarks>
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Освобождает ресурсы, используемые этим экземпляром контрола.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Выполняет отписку от событий модели, очистку ссылок и освобождение ресурсов.
|
||||
/// После вызова этого метода контрол не должен использоваться.
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Lattice.Core.Docking.Abstractions;
|
||||
using Lattice.Core.Docking.Engine;
|
||||
using Lattice.Core.Docking.Models;
|
||||
using Lattice.UI.Docking;
|
||||
using Lattice.UI.Docking.Abstractions;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -39,7 +40,6 @@ public sealed class LatticeDockHost : Control, IDockHost, IDisposable
|
||||
private bool _disposed;
|
||||
private IDockElement? _model;
|
||||
private LayoutManager? _layoutManager;
|
||||
private IDockDragDropService? _dragDropService;
|
||||
private IDockContextManager? _contextManager;
|
||||
private bool _isSelected;
|
||||
private bool _isActive;
|
||||
@@ -109,27 +109,6 @@ public sealed class LatticeDockHost : Control, IDockHost, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает сервис перетаскивания, используемый этим контролом.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Реализация <see cref="IDockDragDropService"/> для обработки операций перетаскивания.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// Сервис перетаскивания обеспечивает взаимодействие с системой drag-and-drop,
|
||||
/// включая визуальную обратную связь и обработку событий.
|
||||
/// </remarks>
|
||||
public IDockDragDropService? DragDropService
|
||||
{
|
||||
get => _dragDropService;
|
||||
set
|
||||
{
|
||||
if (_dragDropService == value) return;
|
||||
_dragDropService = value;
|
||||
OnPropertyChanged(nameof(DragDropService));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает или задает контекстный менеджер для этого контрола.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Lattice.Core.Docking.Abstractions;
|
||||
using Lattice.Core.Docking.Engine;
|
||||
using Lattice.Core.Docking.Models;
|
||||
using Lattice.UI.Docking.Abstractions;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
@@ -14,9 +14,20 @@ using System.Runtime.CompilerServices;
|
||||
namespace Lattice.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Кастомный контрол вкладок с поддержкой всех позиций размещения панели вкладок.
|
||||
/// Представляет кастомный контрол вкладок с поддержкой всех позиций размещения панели вкладок.
|
||||
/// Реализует интерфейс <see cref="IDockLeafControl"/> для интеграции с системой докинга.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Контрол обеспечивает отображение коллекции вкладок с возможностью навигации между ними,
|
||||
/// закрытия вкладок и изменения порядка. Поддерживает все четыре позиции размещения панели
|
||||
/// вкладок: сверху, снизу, слева и справа.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Контрол автоматически синхронизирует свое состояние с моделью данных <see cref="DockLeaf"/>
|
||||
/// и обеспечивает двустороннюю привязку данных через механизм INotifyPropertyChanged.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
{
|
||||
private readonly PropertyChangedEventHandler _modelPropertyChangedHandler;
|
||||
@@ -26,12 +37,10 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
private ListBox? _tabHeaderList;
|
||||
private ContentControl? _contentControl;
|
||||
private LayoutManager? _layoutManager;
|
||||
private IDockDragDropService? _dragDropService;
|
||||
private IDockContextManager? _contextManager;
|
||||
private bool _isSelected;
|
||||
private bool _isActive;
|
||||
private bool _canDrag = true;
|
||||
private bool _canDrop = true;
|
||||
private TabPlacement _tabPlacement = TabPlacement.Top;
|
||||
private bool _showCloseButtons = true;
|
||||
private bool _canReorderTabs = true;
|
||||
|
||||
@@ -43,11 +52,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
this.DefaultStyleKey = typeof(LatticeTabControl);
|
||||
_modelPropertyChangedHandler = OnModelPropertyChanged;
|
||||
this.DataContextChanged += OnDataContextChanged;
|
||||
|
||||
// Подписываемся на события
|
||||
this.PointerPressed += OnPointerPressed;
|
||||
this.PointerMoved += OnPointerMoved;
|
||||
this.PointerReleased += OnPointerReleased;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -76,18 +80,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDockDragDropService? DragDropService
|
||||
{
|
||||
get => _dragDropService;
|
||||
set
|
||||
{
|
||||
if (_dragDropService == value) return;
|
||||
_dragDropService = value;
|
||||
OnPropertyChanged(nameof(DragDropService));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDockContextManager? ContextManager
|
||||
{
|
||||
@@ -124,40 +116,17 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanDrag
|
||||
{
|
||||
get => _canDrag;
|
||||
set
|
||||
{
|
||||
if (_canDrag == value) return;
|
||||
_canDrag = value;
|
||||
OnPropertyChanged(nameof(CanDrag));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanDrop
|
||||
{
|
||||
get => _canDrop;
|
||||
set
|
||||
{
|
||||
if (_canDrop == value) return;
|
||||
_canDrop = value;
|
||||
OnPropertyChanged(nameof(CanDrop));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TabPlacement TabPlacement
|
||||
{
|
||||
get => _model?.TabPlacement ?? TabPlacement.Top;
|
||||
get => _tabPlacement;
|
||||
set
|
||||
{
|
||||
if (_model != null && _model.TabPlacement != value)
|
||||
if (_tabPlacement != value)
|
||||
{
|
||||
_model.TabPlacement = value;
|
||||
_tabPlacement = value;
|
||||
UpdateTabPlacement();
|
||||
OnPropertyChanged(nameof(TabPlacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,10 +137,12 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
get => _showCloseButtons;
|
||||
set
|
||||
{
|
||||
if (_showCloseButtons == value) return;
|
||||
_showCloseButtons = value;
|
||||
OnPropertyChanged(nameof(ShowCloseButtons));
|
||||
UpdateTabHeaders();
|
||||
if (_showCloseButtons != value)
|
||||
{
|
||||
_showCloseButtons = value;
|
||||
OnPropertyChanged(nameof(ShowCloseButtons));
|
||||
UpdateTabHeaders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +152,11 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
get => _canReorderTabs;
|
||||
set
|
||||
{
|
||||
if (_canReorderTabs == value) return;
|
||||
_canReorderTabs = value;
|
||||
OnPropertyChanged(nameof(CanReorderTabs));
|
||||
if (_canReorderTabs != value)
|
||||
{
|
||||
_canReorderTabs = value;
|
||||
OnPropertyChanged(nameof(CanReorderTabs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,30 +203,38 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
UpdateTabHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменение контекста данных контрола.
|
||||
/// </summary>
|
||||
private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
|
||||
{
|
||||
Model = args.NewValue as DockLeaf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Присоединяет модель данных к контролу.
|
||||
/// </summary>
|
||||
private void AttachModel()
|
||||
{
|
||||
if (_model != null)
|
||||
{
|
||||
_model.PropertyChanged += _modelPropertyChangedHandler;
|
||||
|
||||
// Подписываемся на изменения коллекции
|
||||
if (_model.Children is INotifyCollectionChanged notifyCollection)
|
||||
{
|
||||
notifyCollection.CollectionChanged += OnChildrenCollectionChanged;
|
||||
}
|
||||
|
||||
// Устанавливаем DataContext для привязки в XAML
|
||||
this.DataContext = _model;
|
||||
_tabPlacement = _model.TabPlacement;
|
||||
UpdateTabHeaders();
|
||||
UpdateTabPlacement();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отсоединяет модель данных от контрола.
|
||||
/// </summary>
|
||||
private void DetachModel()
|
||||
{
|
||||
if (_model != null)
|
||||
@@ -269,11 +250,15 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменения свойств модели данных.
|
||||
/// </summary>
|
||||
private void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(DockLeaf.TabPlacement):
|
||||
_tabPlacement = _model?.TabPlacement ?? TabPlacement.Top;
|
||||
OnPropertyChanged(nameof(TabPlacement));
|
||||
UpdateTabPlacement();
|
||||
break;
|
||||
@@ -289,20 +274,24 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменения коллекции вкладок.
|
||||
/// </summary>
|
||||
private void OnChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateTabHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет положение панели вкладок.
|
||||
/// </summary>
|
||||
private void UpdateTabPlacement()
|
||||
{
|
||||
if (_rootGrid == null || _model == null) return;
|
||||
|
||||
// Очищаем все определения
|
||||
_rootGrid.RowDefinitions.Clear();
|
||||
_rootGrid.ColumnDefinitions.Clear();
|
||||
|
||||
// Настраиваем Grid в зависимости от позиции вкладок
|
||||
switch (_model.TabPlacement)
|
||||
{
|
||||
case TabPlacement.Top:
|
||||
@@ -330,21 +319,24 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
break;
|
||||
}
|
||||
|
||||
// Обновляем позиции элементов
|
||||
UpdateElementPositions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет ориентацию списка заголовков вкладок.
|
||||
/// </summary>
|
||||
/// <param name="orientation">Новая ориентация списка.</param>
|
||||
private void UpdateHeaderListOrientation(Orientation orientation)
|
||||
{
|
||||
if (_tabHeaderList != null && _tabHeaderList.ItemsPanelRoot is ItemsPanelTemplate panelTemplate)
|
||||
if (_tabHeaderList?.ItemsPanelRoot is StackPanel stackPanel)
|
||||
{
|
||||
if (panelTemplate.LoadContent() is StackPanel stackPanel)
|
||||
{
|
||||
stackPanel.Orientation = orientation;
|
||||
}
|
||||
stackPanel.Orientation = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет позиции элементов в сетке.
|
||||
/// </summary>
|
||||
private void UpdateElementPositions()
|
||||
{
|
||||
if (_rootGrid == null || _tabHeaderList == null || _contentControl == null) return;
|
||||
@@ -381,14 +373,15 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет заголовки вкладок.
|
||||
/// </summary>
|
||||
private void UpdateTabHeaders()
|
||||
{
|
||||
if (_tabHeaderList == null || _model == null) return;
|
||||
|
||||
// Очищаем текущие элементы
|
||||
_tabHeaderList.Items.Clear();
|
||||
|
||||
// Добавляем новые элементы
|
||||
foreach (var content in _model.Children)
|
||||
{
|
||||
var item = CreateTabHeaderItem(content);
|
||||
@@ -398,6 +391,11 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
UpdateSelectedTab();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает элемент заголовка вкладки.
|
||||
/// </summary>
|
||||
/// <param name="content">Содержимое вкладки.</param>
|
||||
/// <returns>Созданный элемент заголовка.</returns>
|
||||
private ListBoxItem CreateTabHeaderItem(IDockContent content)
|
||||
{
|
||||
var item = new ListBoxItem
|
||||
@@ -408,7 +406,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
VerticalContentAlignment = VerticalAlignment.Stretch
|
||||
};
|
||||
|
||||
// Обработка клика для выбора вкладки
|
||||
item.PointerPressed += (sender, e) =>
|
||||
{
|
||||
if (e.GetCurrentPoint(item).Properties.IsLeftButtonPressed)
|
||||
@@ -418,26 +415,20 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
};
|
||||
|
||||
// Обработка клика на кнопке закрытия
|
||||
if (_showCloseButtons && content.CanClose)
|
||||
{
|
||||
// Добавляем контекстное меню
|
||||
item.ContextRequested += (sender, e) =>
|
||||
{
|
||||
ShowTabContextMenu(item, content, e);
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создает содержимое заголовка вкладки.
|
||||
/// </summary>
|
||||
/// <param name="content">Содержимое вкладки.</param>
|
||||
/// <returns>Созданное содержимое заголовка.</returns>
|
||||
private UIElement CreateTabHeaderContent(IDockContent content)
|
||||
{
|
||||
var grid = new Grid();
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
// Заголовок вкладки
|
||||
var textBlock = new TextBlock
|
||||
{
|
||||
Text = content.Title,
|
||||
@@ -447,7 +438,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
Grid.SetColumn(textBlock, 0);
|
||||
grid.Children.Add(textBlock);
|
||||
|
||||
// Кнопка закрытия (если разрешено)
|
||||
if (_showCloseButtons && content.CanClose)
|
||||
{
|
||||
var closeButton = new Button
|
||||
@@ -475,11 +465,13 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
return grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет выбранную вкладку.
|
||||
/// </summary>
|
||||
private void UpdateSelectedTab()
|
||||
{
|
||||
if (_tabHeaderList == null || _model == null) return;
|
||||
|
||||
// Находим элемент, соответствующий активному контенту
|
||||
foreach (var item in _tabHeaderList.Items)
|
||||
{
|
||||
if (item is ListBoxItem listBoxItem && listBoxItem.Tag is IDockContent content)
|
||||
@@ -488,13 +480,15 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем контент
|
||||
if (_contentControl != null)
|
||||
{
|
||||
_contentControl.Content = _model.ActiveContent?.View;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает изменение выбора вкладки.
|
||||
/// </summary>
|
||||
private void OnTabSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_tabHeaderList?.SelectedItem is ListBoxItem selectedItem &&
|
||||
@@ -511,66 +505,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Drag-and-Drop для переупорядочивания вкладок
|
||||
private ListBoxItem? _draggedItem;
|
||||
private Point _dragStartPoint;
|
||||
|
||||
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (!_canReorderTabs) return;
|
||||
|
||||
var pointerPoint = e.GetCurrentPoint(this);
|
||||
_dragStartPoint = new Point(pointerPoint.Position.X, pointerPoint.Position.Y);
|
||||
|
||||
// Находим элемент под курсором
|
||||
var element = VisualTreeHelper.FindElementsInHostCoordinates(
|
||||
pointerPoint.Position, this).FirstOrDefault();
|
||||
|
||||
if (element is ListBoxItem listBoxItem)
|
||||
{
|
||||
_draggedItem = listBoxItem;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (_draggedItem == null || !_canReorderTabs) return;
|
||||
|
||||
var pointerPoint = e.GetCurrentPoint(this);
|
||||
var currentPoint = new Point(pointerPoint.Position.X, pointerPoint.Position.Y);
|
||||
|
||||
// Проверяем, достаточно ли переместили для начала перетаскивания
|
||||
var distance = Math.Sqrt(
|
||||
Math.Pow(currentPoint.X - _dragStartPoint.X, 2) +
|
||||
Math.Pow(currentPoint.Y - _dragStartPoint.Y, 2));
|
||||
|
||||
if (distance > 10 && _draggedItem.Tag is IDockContent content)
|
||||
{
|
||||
// Начинаем операцию перетаскивания
|
||||
StartTabDrag(_draggedItem, content);
|
||||
_draggedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_draggedItem = null;
|
||||
}
|
||||
|
||||
private void StartTabDrag(ListBoxItem item, IDockContent content)
|
||||
{
|
||||
// TODO: Реализовать перетаскивание вкладок
|
||||
// Для этого нужно использовать IDockDragDropService
|
||||
}
|
||||
|
||||
private void ShowTabContextMenu(ListBoxItem item, IDockContent content, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (_contextManager == null) return;
|
||||
|
||||
var position = e.GetPosition(this);
|
||||
_contextManager.ShowContextMenu(this, position.X, position.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddContent(IDockContent content)
|
||||
{
|
||||
@@ -643,10 +577,9 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
/// <inheritdoc/>
|
||||
public void ApplyTheme(IDockTheme theme)
|
||||
{
|
||||
// Применение темы к элементу
|
||||
if (theme != null)
|
||||
{
|
||||
// TODO: Применить тему к стилям контрола
|
||||
// TODO: Реализовать применение темы к стилям контрола
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +592,9 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывает событие изменения свойства.
|
||||
/// </summary>
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
@@ -676,11 +612,6 @@ public sealed class LatticeTabControl : Control, IDockLeafControl, IDisposable
|
||||
_tabHeaderList.SelectionChanged -= OnTabSelectionChanged;
|
||||
}
|
||||
|
||||
// Отписываемся от событий указателя
|
||||
this.PointerPressed -= OnPointerPressed;
|
||||
this.PointerMoved -= OnPointerMoved;
|
||||
this.PointerReleased -= OnPointerReleased;
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@ namespace Lattice.UI.Docking.WinUI.Factories;
|
||||
/// Фабрика контролов для платформы WinUI.
|
||||
/// Создает UI-элементы для отображения компонентов системы докинга.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Фабрика реализует паттерн "Абстрактная фабрика", предоставляя единый интерфейс
|
||||
/// для создания всех типов контролов док-системы. Это позволяет абстрагировать
|
||||
/// конкретную UI-платформу (WinUI) от бизнес-логики системы.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Все создаваемые контролы автоматически настраиваются: устанавливаются связи
|
||||
/// с менеджером макета, контекстным менеджером и применяется текущая тема оформления.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockControlFactory
|
||||
{
|
||||
private readonly IDockTheme _theme;
|
||||
@@ -18,13 +29,33 @@ public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockContr
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр фабрики WinUI.
|
||||
/// </summary>
|
||||
/// <param name="theme">Тема оформления.</param>
|
||||
/// <param name="theme">Тема оформления для применения к создаваемым контролам.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="theme"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Конструктор создает фабрику с заданной темой оформления. Все контролы,
|
||||
/// созданные этой фабрикой, будут автоматически применять указанную тему.
|
||||
/// </remarks>
|
||||
public WinUIDockControlFactory(IDockTheme theme)
|
||||
{
|
||||
_theme = theme ?? throw new ArgumentNullException(nameof(theme));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает контрол для группы разделения.
|
||||
/// </summary>
|
||||
/// <param name="group">Модель группы разделения.</param>
|
||||
/// <returns>
|
||||
/// Созданный контрол группы.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="group"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает экземпляр <see cref="LatticeDockGroup"/>, настраивает его связи
|
||||
/// с моделью и другими сервисами, применяет текущую тему оформления.
|
||||
/// </remarks>
|
||||
public override IDockGroupControl CreateGroupControl(DockGroup group)
|
||||
{
|
||||
var control = new LatticeDockGroup();
|
||||
@@ -33,7 +64,21 @@ public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockContr
|
||||
return control;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает контрол для контейнера вкладок.
|
||||
/// </summary>
|
||||
/// <param name="leaf">Модель контейнера вкладок.</param>
|
||||
/// <returns>
|
||||
/// Созданный контрол листа.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="leaf"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает экземпляр <see cref="LatticeTabControl"/>, настраивает его связи
|
||||
/// с моделью и другими сервисами, применяет текущую тему оформления.
|
||||
/// Контрол поддерживает все положения панели вкладок и операции с вкладками.
|
||||
/// </remarks>
|
||||
public override IDockLeafControl CreateLeafControl(DockLeaf leaf)
|
||||
{
|
||||
var control = new LatticeTabControl();
|
||||
@@ -42,21 +87,58 @@ public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockContr
|
||||
return control;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает контрол для плавающего окна.
|
||||
/// </summary>
|
||||
/// <param name="window">Модель плавающего окна.</param>
|
||||
/// <returns>
|
||||
/// Созданный контрол окна.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="window"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// В текущей реализации метод не реализован. Плавающие окна требуют
|
||||
/// дополнительной интеграции с оконной системой платформы.
|
||||
/// </remarks>
|
||||
public override IFloatingWindowControl CreateFloatingWindowControl(DockWindow window)
|
||||
{
|
||||
// TODO: Реализовать создание плавающего окна
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает контрол для автоскрываемой панели.
|
||||
/// </summary>
|
||||
/// <param name="panel">Модель автоскрываемой панели.</param>
|
||||
/// <returns>
|
||||
/// Созданный контрол панели.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="panel"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// В текущей реализации метод не реализован. Автоскрываемые панели требуют
|
||||
/// сложной логики анимации и взаимодействия с краями окна.
|
||||
/// </remarks>
|
||||
public override IAutoHidePanelControl CreateAutoHidePanelControl(AutoHidePanel panel)
|
||||
{
|
||||
// TODO: Реализовать создание автоскрываемой панели
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает контрол для разделителя.
|
||||
/// </summary>
|
||||
/// <param name="orientation">Ориентация разделителя.</param>
|
||||
/// <returns>
|
||||
/// Созданный контрол разделителя.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Создает экземпляр <see cref="LatticeSplitter"/>, настраивает его ориентацию
|
||||
/// и применяет текущую тему оформления. Разделитель поддерживает перетаскивание
|
||||
/// для изменения соотношения размеров между соседними областями.
|
||||
/// </remarks>
|
||||
public override IDockSplitterControl CreateSplitterControl(SplitDirection orientation)
|
||||
{
|
||||
var control = new LatticeSplitter
|
||||
@@ -71,6 +153,14 @@ public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockContr
|
||||
/// <summary>
|
||||
/// Создает хост для размещения системы докинга.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Созданный док-хост.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Создает корневой контейнер для всей системы докинга - экземпляр <see cref="LatticeDockHost"/>.
|
||||
/// Хост управляет всем макетом приложения, включая основное дерево компоновки,
|
||||
/// плавающие окна и автоскрываемые панели.
|
||||
/// </remarks>
|
||||
public IDockHost CreateDockHost()
|
||||
{
|
||||
var host = new LatticeDockHost();
|
||||
@@ -79,13 +169,22 @@ public sealed class WinUIDockControlFactory : DockControlFactoryBase, IDockContr
|
||||
return host;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настраивает созданный контрол.
|
||||
/// </summary>
|
||||
/// <param name="control">Контрол для настройки.</param>
|
||||
/// <param name="model">Модель данных для контрола (опционально).</param>
|
||||
/// <remarks>
|
||||
/// Устанавливает основные связи контрола: модель данных, менеджер макета,
|
||||
/// контекстный менеджер. Также настраивает привязку данных через DataContext.
|
||||
/// Этот метод вызывается для всех создаваемых контролов.
|
||||
/// </remarks>
|
||||
private void ConfigureControl(IDockControl control, IDockElement? model = null)
|
||||
{
|
||||
if (control == null) return;
|
||||
|
||||
control.Model = model;
|
||||
control.LayoutManager = LatticeUIFramework.LayoutManager;
|
||||
control.DragDropService = LatticeUIFramework.DragDropService;
|
||||
control.ContextManager = LatticeUIFramework.ContextManager;
|
||||
|
||||
if (control is FrameworkElement frameworkElement && model != null)
|
||||
|
||||
@@ -8,11 +8,43 @@ using System.Threading.Tasks;
|
||||
namespace Lattice.UI.Docking.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация UI-сервиса для WinUI.
|
||||
/// Реализация UI-сервиса для платформы WinUI.
|
||||
/// Инкапсулирует платформенно-зависимые операции, такие как создание окон,
|
||||
/// показ диалогов и синхронизация с UI-потоком.
|
||||
/// </summary>
|
||||
public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="WinUIDockUIService"/> предоставляет конкретные реализации методов
|
||||
/// <see cref="IDockUIService"/> для платформы WinUI. Это позволяет основной
|
||||
/// бизнес-логике док-системы оставаться независимой от конкретной UI-платформы.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Сервис использует API WinUI для создания окон, показа ContentDialog и
|
||||
/// управления диспетчером потока пользовательского интерфейса.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class WinUIDockUIService : DockUIServiceBase, IDockUIService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Создает главное окно приложения для размещения док-хоста.
|
||||
/// </summary>
|
||||
/// <param name="host">
|
||||
/// Экземпляр <see cref="IDockHost"/>, который будет содержаться в окне.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Объект окна WinUI, который можно отобразить и управлять им.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="host"/> равен null.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Выбрасывается, если <paramref name="host"/> не является элементом WinUI.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает окно WinUI с заголовком "Lattice IDE", устанавливает указанный хост
|
||||
/// в качестве содержимого и регистрирует окно в системе отслеживания окон.
|
||||
/// Окно создается с настройками по умолчанию для IDE-подобных приложений.
|
||||
/// </remarks>
|
||||
public override object CreateMainWindow(IDockHost host)
|
||||
{
|
||||
if (host is not FrameworkElement hostElement)
|
||||
@@ -28,7 +60,25 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
return window;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Отображает модальное диалоговое окно с указанным содержимым.
|
||||
/// </summary>
|
||||
/// <param name="title">Заголовок диалогового окна.</param>
|
||||
/// <param name="content">Содержимое диалогового окна.</param>
|
||||
/// <returns>
|
||||
/// Nullable boolean значение, указывающее результат диалога:
|
||||
/// true - пользователь подтвердил действие,
|
||||
/// false - пользователь отменил действие,
|
||||
/// null - диалог был закрыт без выбора.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="title"/> или <paramref name="content"/> равны null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает и показывает ContentDialog с кнопками OK и Cancel.
|
||||
/// Блокирует взаимодействие с родительским окном до закрытия диалога.
|
||||
/// Использует XamlRoot активного окна для корректного отображения.
|
||||
/// </remarks>
|
||||
public override bool? ShowDialog(string title, object content)
|
||||
{
|
||||
if (content is not FrameworkElement contentElement)
|
||||
@@ -53,7 +103,19 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Отображает информационное сообщение с кнопкой OK.
|
||||
/// </summary>
|
||||
/// <param name="message">Текст сообщения.</param>
|
||||
/// <param name="caption">Заголовок окна сообщения.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="message"/> или <paramref name="caption"/> равны null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает ContentDialog с текстом сообщения и одной кнопкой OK.
|
||||
/// Используется для информирования пользователя о результате операции
|
||||
/// или отображения некритичных ошибок.
|
||||
/// </remarks>
|
||||
public override void ShowMessage(string message, string caption)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
@@ -67,7 +129,22 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
dialog.ShowAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Отображает диалог подтверждения с кнопками Yes/No.
|
||||
/// </summary>
|
||||
/// <param name="message">Текст вопроса.</param>
|
||||
/// <param name="caption">Заголовок окна подтверждения.</param>
|
||||
/// <returns>
|
||||
/// true, если пользователь выбрал "Yes"; false, если пользователь выбрал "No".
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="message"/> или <paramref name="caption"/> равны null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает ContentDialog с кнопками Yes и No. Используется для получения
|
||||
/// подтверждения пользователя перед выполнением критических операций,
|
||||
/// таких как закрытие вкладок с несохраненными данными или сброс настроек.
|
||||
/// </remarks>
|
||||
public override bool Confirm(string message, string caption)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
@@ -83,7 +160,22 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
return result == ContentDialogResult.Primary;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Отображает диалог ввода текста.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Текст подсказки для пользователя.</param>
|
||||
/// <param name="defaultValue">Значение по умолчанию для поля ввода.</param>
|
||||
/// <returns>
|
||||
/// Введенный пользователем текст или null, если диалог был отменен.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="prompt"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Создает ContentDialog с однострочным полем ввода TextBox.
|
||||
/// Используется для получения текстового ввода от пользователя, такого как
|
||||
/// имена файлов, названия документов или параметры конфигурации.
|
||||
/// </remarks>
|
||||
public override string? Prompt(string prompt, string? defaultValue = null)
|
||||
{
|
||||
var textBox = new TextBox
|
||||
@@ -106,7 +198,19 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
return result == ContentDialogResult.Primary ? textBox.Text : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Выполняет указанное действие в UI-потоке.
|
||||
/// </summary>
|
||||
/// <param name="action">Действие для выполнения.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="action"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Гарантирует, что действие будет выполнено в потоке, связанном с
|
||||
/// пользовательским интерфейсом. Если текущий поток уже является UI-потоком,
|
||||
/// действие выполняется немедленно. В противном случае действие ставится
|
||||
/// в очередь диспетчера WinUI.
|
||||
/// </remarks>
|
||||
public override void InvokeOnUIThread(Action action)
|
||||
{
|
||||
if (action == null) return;
|
||||
@@ -122,7 +226,21 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Выполняет указанную асинхронную функцию в UI-потоке.
|
||||
/// </summary>
|
||||
/// <param name="action">Асинхронная функция для выполнения.</param>
|
||||
/// <returns>
|
||||
/// Задача, представляющая асинхронную операцию.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Выбрасывается, если <paramref name="action"/> равен null.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// Гарантирует, что асинхронная функция будет выполнена в UI-потоке.
|
||||
/// Используется для операций, которые требуют доступа к UI-элементам
|
||||
/// или выполняют асинхронные вызовы с обновлением интерфейса.
|
||||
/// </remarks>
|
||||
public override async Task InvokeOnUIThreadAsync(Func<Task> action)
|
||||
{
|
||||
if (action == null) return;
|
||||
@@ -151,6 +269,17 @@ public sealed class WinUIDockUIService : DockUIServiceBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает XamlRoot активного окна приложения.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// XamlRoot активного окна или null, если нет активных окон.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Используется для корректного отображения диалоговых окон в контексте
|
||||
/// текущего окна приложения. Перебирает все зарегистрированные окна
|
||||
/// и возвращает XamlRoot первого найденного.
|
||||
/// </remarks>
|
||||
private XamlRoot? GetActiveXamlRoot()
|
||||
{
|
||||
// Получаем XamlRoot из активного окна
|
||||
|
||||
Reference in New Issue
Block a user