296 lines
13 KiB
C#
296 lines
13 KiB
C#
using Lattice.UI.Docking.Abstractions;
|
||
using Lattice.UI.Docking.Services;
|
||
using Microsoft.UI.Xaml;
|
||
using Microsoft.UI.Xaml.Controls;
|
||
using System;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Lattice.UI.Docking.WinUI.Services;
|
||
|
||
/// <summary>
|
||
/// Реализация UI-сервиса для платформы WinUI.
|
||
/// Инкапсулирует платформенно-зависимые операции, такие как создание окон,
|
||
/// показ диалогов и синхронизация с UI-потоком.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// <para>
|
||
/// <see cref="WinUIDockUIService"/> предоставляет конкретные реализации методов
|
||
/// <see cref="IDockUIService"/> для платформы WinUI. Это позволяет основной
|
||
/// бизнес-логике док-системы оставаться независимой от конкретной UI-платформы.
|
||
/// </para>
|
||
/// <para>
|
||
/// Сервис использует API WinUI для создания окон, показа ContentDialog и
|
||
/// управления диспетчером потока пользовательского интерфейса.
|
||
/// </para>
|
||
/// </remarks>
|
||
public sealed class WinUIDockUIService : DockUIServiceBase, IDockUIService
|
||
{
|
||
/// <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)
|
||
throw new ArgumentException("Host must be a FrameworkElement", nameof(host));
|
||
|
||
var window = new Window();
|
||
window.Content = hostElement;
|
||
window.AppWindow.Title = "Lattice IDE";
|
||
|
||
// Регистрируем окно в трекере
|
||
Themes.WindowTracker.Register(window);
|
||
|
||
return window;
|
||
}
|
||
|
||
/// <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)
|
||
return null;
|
||
|
||
var dialog = new ContentDialog
|
||
{
|
||
Title = title,
|
||
Content = contentElement,
|
||
PrimaryButtonText = "OK",
|
||
CloseButtonText = "Cancel",
|
||
XamlRoot = GetActiveXamlRoot()
|
||
};
|
||
|
||
// Показываем диалог и возвращаем результат
|
||
var result = dialog.ShowAsync();
|
||
return result.GetAwaiter().GetResult() switch
|
||
{
|
||
ContentDialogResult.Primary => true,
|
||
ContentDialogResult.Secondary => false,
|
||
_ => null
|
||
};
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
Title = caption,
|
||
Content = new TextBlock { Text = message },
|
||
PrimaryButtonText = "OK",
|
||
XamlRoot = GetActiveXamlRoot()
|
||
};
|
||
|
||
dialog.ShowAsync();
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
Title = caption,
|
||
Content = new TextBlock { Text = message },
|
||
PrimaryButtonText = "Yes",
|
||
SecondaryButtonText = "No",
|
||
XamlRoot = GetActiveXamlRoot()
|
||
};
|
||
|
||
var result = dialog.ShowAsync().GetAwaiter().GetResult();
|
||
return result == ContentDialogResult.Primary;
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
Text = defaultValue ?? string.Empty,
|
||
Width = 300,
|
||
Height = 32
|
||
};
|
||
|
||
var dialog = new ContentDialog
|
||
{
|
||
Title = prompt,
|
||
Content = textBox,
|
||
PrimaryButtonText = "OK",
|
||
SecondaryButtonText = "Cancel",
|
||
XamlRoot = GetActiveXamlRoot()
|
||
};
|
||
|
||
var result = dialog.ShowAsync().GetAwaiter().GetResult();
|
||
return result == ContentDialogResult.Primary ? textBox.Text : null;
|
||
}
|
||
|
||
/// <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;
|
||
|
||
var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||
if (dispatcherQueue.HasThreadAccess)
|
||
{
|
||
action();
|
||
}
|
||
else
|
||
{
|
||
dispatcherQueue.TryEnqueue(() => action());
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
|
||
var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||
if (dispatcherQueue.HasThreadAccess)
|
||
{
|
||
await action();
|
||
}
|
||
else
|
||
{
|
||
var tcs = new TaskCompletionSource<bool>();
|
||
dispatcherQueue.TryEnqueue(async () =>
|
||
{
|
||
try
|
||
{
|
||
await action();
|
||
tcs.SetResult(true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
tcs.SetException(ex);
|
||
}
|
||
});
|
||
await tcs.Task;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает XamlRoot активного окна приложения.
|
||
/// </summary>
|
||
/// <returns>
|
||
/// XamlRoot активного окна или null, если нет активных окон.
|
||
/// </returns>
|
||
/// <remarks>
|
||
/// Используется для корректного отображения диалоговых окон в контексте
|
||
/// текущего окна приложения. Перебирает все зарегистрированные окна
|
||
/// и возвращает XamlRoot первого найденного.
|
||
/// </remarks>
|
||
private XamlRoot? GetActiveXamlRoot()
|
||
{
|
||
// Получаем XamlRoot из активного окна
|
||
foreach (var window in Themes.WindowTracker.Windows)
|
||
{
|
||
if (window.Content is FrameworkElement element)
|
||
{
|
||
return element.XamlRoot;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
} |