Доработан 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user