Доработан winui

This commit is contained in:
2026-02-01 09:26:13 +03:00
parent 584df249f6
commit e8b4cb9881
26 changed files with 1842 additions and 2373 deletions

View File

@@ -10,23 +10,6 @@ using System.Runtime.CompilerServices;
namespace Lattice.UI;
/// <summary>
/// Визуальный контрол для отображения группы разделения (сплиттера) в системе докинга.
/// Реализует интерфейс <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;
@@ -42,14 +25,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
private double _splitRatio = 0.5;
private double _splitterSize = 4.0;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="LatticeDockGroup"/>.
/// </summary>
/// <remarks>
/// Конструктор устанавливает ключ стиля по умолчанию, инициализирует обработчик
/// изменений модели и подписывается на событие изменения контекста данных.
/// Созданный контрол готов к использованию после применения шаблона.
/// </remarks>
public LatticeDockGroup()
{
this.DefaultStyleKey = typeof(LatticeDockGroup);
@@ -57,18 +32,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
this.DataContextChanged += OnDataContextChanged;
}
/// <summary>
/// Получает или задает модель данных, связанную с этим контролом.
/// </summary>
/// <value>
/// Экземпляр <see cref="DockGroup"/>, представляющий узел разделения в дереве компоновки.
/// Может быть null, если контрол не связан с моделью.
/// </value>
/// <remarks>
/// При установке новой модели контрол автоматически подписывается на события
/// изменения свойств модели и обновляет свое визуальное представление.
/// При удалении модели происходит отписка от событий и очистка ресурсов.
/// </remarks>
public IDockElement? Model
{
get => _model;
@@ -82,18 +45,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает менеджер макета, к которому принадлежит этот контрол.
/// </summary>
/// <value>
/// Экземпляр <see cref="LayoutManager"/>, управляющий структурой док-системы.
/// Может быть null, если контрол не связан с менеджером макета.
/// </value>
/// <remarks>
/// Менеджер макета используется для выполнения операций с деревом компоновки,
/// таких как перемещение элементов, создание плавающих окон и управление
/// автоскрываемыми панелями.
/// </remarks>
public LayoutManager? LayoutManager
{
get => _layoutManager;
@@ -105,17 +56,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает контекстный менеджер для этого контрола.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockContextManager"/> или null, если менеджер не установлен.
/// </value>
/// <remarks>
/// Контекстный менеджер используется для отображения контекстных меню при щелчке
/// правой кнопкой мыши по контролу. Меню содержит команды, доступные для данного
/// элемента в текущем контексте.
/// </remarks>
public IDockContextManager? ContextManager
{
get => _contextManager;
@@ -127,18 +67,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает признак того, что контрол выбран.
/// </summary>
/// <value>
/// true, если контрол выбран; в противном случае false.
/// Значение по умолчанию: false.
/// </value>
/// <remarks>
/// Выделенный контрол обычно визуально отличается от других (например, имеет
/// выделенную границу или фон). В каждый момент времени может быть выделен
/// только один контрол в пределах контейнера.
/// </remarks>
public bool IsSelected
{
get => _isSelected;
@@ -150,17 +78,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает признак того, что контрол активен.
/// </summary>
/// <value>
/// true, если контрол активен; в противном случае false.
/// Значение по умолчанию: false.
/// </value>
/// <remarks>
/// Активный контрол получает фокус ввода и может обрабатывать команды клавиатуры.
/// Обычно соответствует последнему взаимодействию пользователя с элементом.
/// </remarks>
public bool IsActive
{
get => _isActive;
@@ -172,20 +89,9 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает ориентацию разделения группы.
/// </summary>
/// <value>
/// Направление разделения (горизонтальное или вертикальное).
/// </value>
/// <remarks>
/// Ориентация определяет, как расположены дочерние элементы относительно друг друга:
/// <list type="bullet">
/// <item><see cref="SplitDirection.Horizontal"/> - элементы расположены слева и справа</item>
/// <item><see cref="SplitDirection.Vertical"/> - элементы расположены сверху и снизу</item>
/// </list>
/// Изменение ориентации приводит к перестройке внутреннего макета контрола.
/// </remarks>
public bool CanDrag => true;
public bool CanDrop => true;
public SplitDirection Orientation
{
get => _model?.Orientation ?? SplitDirection.Horizontal;
@@ -199,18 +105,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает или задает соотношение разделения между первым и вторым элементами.
/// </summary>
/// <value>
/// Значение от 0.0 до 1.0, где 0.5 означает равное разделение пространства.
/// Значение 0.0 отдает все пространство второму элементу, 1.0 - первому элементу.
/// </value>
/// <remarks>
/// Соотношение разделения определяет пропорции, в которых доступное пространство
/// распределяется между дочерними элементами. Изменение этого свойства приводит
/// к перестройке внутреннего макета и генерации события <see cref="SplitRatioChanged"/>.
/// </remarks>
public double SplitRatio
{
get => _splitRatio;
@@ -221,24 +115,12 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
_splitRatio = value;
UpdateLayoutDefinitions();
OnPropertyChanged(nameof(SplitRatio));
SplitRatioChanged?.Invoke(this,
new SplitRatioChangedEventArgs(value, SplitRatioChangeSource.Programmatic));
}
}
}
/// <summary>
/// Получает или задает размер разделителя в пикселях.
/// </summary>
/// <value>
/// Ширина разделителя в пикселях. Значение по умолчанию: 4.0.
/// </value>
/// <remarks>
/// Размер разделителя определяет область, доступную для перетаскивания пользователем
/// для изменения соотношения разделения. Увеличение размера облегчает взаимодействие,
/// но уменьшает полезное пространство для содержимого.
/// </remarks>
public double SplitterSize
{
get => _splitterSize;
@@ -252,57 +134,12 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Получает контрол для первого дочернего элемента.
/// </summary>
/// <value>
/// Контрол, отображающий первый дочерний элемент, или null, если элемент не установлен.
/// </value>
/// <remarks>
/// Первый дочерний элемент занимает левую область при горизонтальной ориентации
/// или верхнюю область при вертикальной ориентации.
/// </remarks>
public IDockControl? FirstChild => _firstChildControl?.Content as IDockControl;
/// <summary>
/// Получает контрол для второго дочернего элемента.
/// </summary>
/// <value>
/// Контрол, отображающий второй дочерний элемент, или null, если элемент не установлен.
/// </value>
/// <remarks>
/// Второй дочерний элемент занимает правую область при горизонтальной ориентации
/// или нижнюю область при вертикальной ориентации.
/// </remarks>
public IDockControl? SecondChild => _secondChildControl?.Content as IDockControl;
/// <summary>
/// Происходит при изменении соотношения разделения между дочерними элементами.
/// </summary>
/// <remarks>
/// Событие генерируется при изменении свойства <see cref="SplitRatio"/>,
/// независимо от источника изменения (пользователь, программа или восстановление состояния).
/// Содержит информацию о новом соотношении и источнике изменения.
/// </remarks>
public event EventHandler<SplitRatioChangedEventArgs>? SplitRatioChanged;
/// <summary>
/// Происходит при изменении значения свойства.
/// </summary>
/// <remarks>
/// Событие реализует интерфейс <see cref="INotifyPropertyChanged"/> и используется
/// для уведомления системы привязки данных об изменениях свойств контрола.
/// </remarks>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Вызывается при применении шаблона контрола.
/// </summary>
/// <remarks>
/// Метод получает ссылки на именованные части шаблона и инициализирует
/// внутренние структуры контрола. Вызывает обновление макета для корректного
/// отображения дочерних элементов.
/// </remarks>
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
@@ -314,48 +151,22 @@ 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)
{
_model.PropertyChanged += _modelPropertyChangedHandler;
this.DataContext = _model;
// Инициализируем свойства из модели
_splitRatio = _model.SplitRatio;
UpdateLayoutDefinitions();
}
}
/// <summary>
/// Отсоединяет модель от контрола.
/// </summary>
/// <remarks>
/// Отписывается от событий изменения свойств модели, очищает контекст данных
/// и освобождает ресурсы, связанные с предыдущей моделью.
/// </remarks>
private void DetachModel()
{
if (_model != null)
@@ -365,16 +176,6 @@ 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)
@@ -395,14 +196,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Обновляет определения макета сетки на основе текущей ориентации и соотношения разделения.
/// </summary>
/// <remarks>
/// Метод перестраивает структуру строк и столбцов сетки в зависимости от ориентации
/// разделения и текущего соотношения между дочерними элементами. Обеспечивает
/// корректное позиционирование разделителя и дочерних контролов.
/// </remarks>
private void UpdateLayoutDefinitions()
{
if (_rootGrid == null || _model == null) return;
@@ -412,7 +205,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
if (_model.Orientation == SplitDirection.Horizontal)
{
// Горизонтальное разделение
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition
{ Width = new GridLength(_model.SplitRatio, GridUnitType.Star) });
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition
@@ -420,7 +212,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
_rootGrid.ColumnDefinitions.Add(new ColumnDefinition
{ Width = new GridLength(1 - _model.SplitRatio, GridUnitType.Star) });
// Устанавливаем позиции элементов
if (_firstChildControl != null)
{
Grid.SetColumn(_firstChildControl, 0);
@@ -435,7 +226,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
else
{
// Вертикальное разделение
_rootGrid.RowDefinitions.Add(new RowDefinition
{ Height = new GridLength(_model.SplitRatio, GridUnitType.Star) });
_rootGrid.RowDefinitions.Add(new RowDefinition
@@ -443,7 +233,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
_rootGrid.RowDefinitions.Add(new RowDefinition
{ Height = new GridLength(1 - _model.SplitRatio, GridUnitType.Star) });
// Устанавливаем позиции элементов
if (_firstChildControl != null)
{
Grid.SetRow(_firstChildControl, 0);
@@ -458,15 +247,6 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
}
}
/// <summary>
/// Устанавливает дочерние контролы для отображения.
/// </summary>
/// <param name="firstChild">Контрол для первого элемента.</param>
/// <param name="secondChild">Контрол для второго элемента.</param>
/// <remarks>
/// Метод назначает контролы для визуального представления дочерних элементов группы.
/// После установки контролов обновляет макет для корректного отображения.
/// </remarks>
public void SetChildren(IDockControl? firstChild, IDockControl? secondChild)
{
if (_firstChildControl != null)
@@ -478,44 +258,27 @@ public sealed class LatticeDockGroup : Control, IDockGroupControl, IDisposable
UpdateLayoutDefinitions();
}
/// <summary>
/// Обновляет внешний вид контрола в соответствии с текущим состоянием модели.
/// </summary>
/// <remarks>
/// Вызывает перестройку макета сетки для синхронизации визуального представления
/// с текущими значениями свойств модели (ориентация, соотношение разделения).
/// </remarks>
public object? PrepareDragData()
{
return Model;
}
public bool HandleDrop(object data, DockPosition position)
{
// TODO: Реализовать обработку сброса
return false;
}
public void Refresh()
{
UpdateLayoutDefinitions();
}
/// <summary>
/// Применяет указанную тему к контролу.
/// </summary>
/// <param name="theme">Тема для применения.</param>
/// <remarks>
/// Обновляет стили и параметры отображения контрола в соответствии с заданной темой.
/// В текущей реализации метод является заглушкой и должен быть расширен для
/// поддержки динамического изменения тем оформления.
/// </remarks>
public void ApplyTheme(IDockTheme theme)
{
// Применение темы к контролу
if (theme != null)
{
// TODO: Реализовать применение темы к стилям контрола
}
// TODO: Реализовать применение темы
}
/// <summary>
/// Вызывается при изменении состояния модели для обновления UI.
/// </summary>
/// <param name="propertyName">Имя изменившегося свойства модели.</param>
/// <remarks>
/// Перенаправляет вызов в обработчик изменений модели, обеспечивая уведомление
/// контрола о конкретных изменениях в связанной модели данных.
/// </remarks>
public void OnModelPropertyChanged(string propertyName)
{
if (_model != null)
@@ -524,27 +287,11 @@ 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));
}
/// <summary>
/// Освобождает ресурсы, используемые этим экземпляром контрола.
/// </summary>
/// <remarks>
/// Выполняет отписку от событий модели, очистку ссылок и освобождение ресурсов.
/// После вызова этого метода контрол не должен использоваться.
/// </remarks>
public void Dispose()
{
if (!_disposed)