From 1ada0c8fd87e5399505f8d3cf3f7478672375d12 Mon Sep 17 00:00:00 2001 From: Infarh Date: Wed, 21 Jan 2026 23:09:59 +0300 Subject: [PATCH 01/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=20ScreenShotCommand=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D1=85?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B0=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Реализован универсальный класс ScreenShotCommand для WPF с поддержкой захвата экрана в PNG и GIF. Поддерживаются различные режимы (главное окно, активное окно ОС, монитор под курсором, панорама всех мониторов, UIElement). Добавлены параметры для настройки каталога и формата файлов, а также длительности GIF. Используются вызовы WinAPI для работы с окнами и мониторами. Введён enum CaptureFallbackMode. Подробные XML-комментарии на русском. --- MathCore.WPF/Commands/ScreenShotCommand.cs | 422 +++++++++++++++++++++ MathCore.WPF/MathCore.WPF.csproj | 17 +- 2 files changed, 425 insertions(+), 14 deletions(-) create mode 100644 MathCore.WPF/Commands/ScreenShotCommand.cs diff --git a/MathCore.WPF/Commands/ScreenShotCommand.cs b/MathCore.WPF/Commands/ScreenShotCommand.cs new file mode 100644 index 0000000..a5ebd2b --- /dev/null +++ b/MathCore.WPF/Commands/ScreenShotCommand.cs @@ -0,0 +1,422 @@ +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace MathCore.WPF.Commands; + +/// Универсальная команда захвата экрана с поддержкой создания PNG и GIF +[MarkupExtensionReturnType(typeof(ScreenShotCommand))] +public sealed class ScreenShotCommand : Command +{ + /// Длительность записи GIF в секундах (0 для одиночного кадра) + public double GifLengthSec { get; set; } = 0; + + /// Режим захвата по умолчанию, если параметр не указан + public CaptureFallbackMode DefaultMode { get; set; } = CaptureFallbackMode.MainWindow; + + /// Каталог для сохранения скриншотов ./screenshots + /// Если указан относительный путь, он будет использоваться относительно каталога приложения + public string ScreenshotsDirectory { get; set; } = Path.Combine(AppContext.BaseDirectory, "screenshots"); + + /// Формат имени файла для одиночных скриншотов без расширения screen[{timestamp}] + /// Поддерживает токен {timestamp} который заменяется на текущее UTC время + public string FileNameFormat { get; set; } = "screen[{timestamp}]"; + + /// Формат имени файла для GIF анимаций без расширения anim[{timestamp}] + /// Поддерживает токен {timestamp} который заменяется на текущее UTC время + public string FileNameFormatGif { get; set; } = "anim[{timestamp}]"; + + public override bool CanExecute(object? parameter) => true; + + /// Выполняет захват экрана в режиме GIF или PNG в зависимости от настроек + /// Объект для захвата (Window, UIElement) или null для режима по умолчанию + public override async void Execute(object? parameter) + { + if (GifLengthSec > 0) // GIF режим + { + await CaptureGifAsync(parameter); + return; + } + + // Одиночный кадр + var frame = CaptureFrame(parameter); + SavePng(frame); + } + + /// Захватывает последовательность кадров и сохраняет в GIF + /// Объект для захвата + private async Task CaptureGifAsync(object? parameter) + { + var duration_ms = (int)(GifLengthSec * 1000); + const int interval = 100; // 10 FPS + var frame_capacity = (int)Math.Ceiling(duration_ms / (double)interval); + var frames = new List(frame_capacity); + + var timer = Stopwatch.StartNew(); + while (timer.ElapsedMilliseconds < duration_ms) + { + var frame_source = CaptureFrame(parameter); + var frame = BitmapFrame.Create(frame_source); + frames.Add(frame); + await Task.Delay(interval); + } + + SaveGif(frames); + } + + /// Захватывает одиночный кадр в зависимости от типа параметра + /// Объект для захвата + /// Захваченное изображение + private BitmapSource CaptureFrame(object? parameter) => parameter switch + { + Window win => CaptureWindow(win), // Окно WPF + UIElement ui => CaptureUIElement(ui), // UI элемент WPF + _ => CaptureFallback() // Режим по умолчанию + }; + + /// Захватывает кадр согласно режиму по умолчанию + /// Захваченное изображение + private BitmapSource CaptureFallback() => DefaultMode switch + { + CaptureFallbackMode.MainWindow => CaptureWindow(Application.Current.MainWindow), // Главное окно приложения + CaptureFallbackMode.ActiveWindow => CaptureActiveWindow(), // Активное окно ОС + CaptureFallbackMode.MousePosition => CaptureScreenAtCursor(), // Монитор под курсором + CaptureFallbackMode.Panorama => CaptureAllScreens(), // Все мониторы + _ => CaptureWindow(Application.Current.MainWindow) // Безопасный fallback + }; + + /// Захватывает UI элемент WPF через рендеринг + /// UI элемент для захвата + /// Захваченное изображение + private static BitmapSource CaptureUIElement(UIElement element) + { + var size = new System.Windows.Size(element.RenderSize.Width, element.RenderSize.Height); + var renderer = new RenderTargetBitmap( + (int)size.Width, (int)size.Height, + 96, 96, PixelFormats.Pbgra32); + + renderer.Render(element); + return renderer; + } + + /// Захватывает активное окно операционной системы + /// Захваченное изображение + private static BitmapSource CaptureActiveWindow() + { + var hwnd = GetForegroundWindow(); + return CaptureScreenFromHandle(hwnd); + } + + /// Захватывает монитор, на котором находится курсор мыши + /// Захваченное изображение + private static BitmapSource CaptureScreenAtCursor() + { + GetCursorPos(out var p); + var monitor = MonitorFromPoint(p, MONITOR_DEFAULTTONEAREST); + return CaptureMonitorBounds(monitor); + } + + /// Захватывает панораму всех подключенных мониторов + /// Захваченное изображение + private static BitmapSource CaptureAllScreens() + { + var bounds = GetAllMonitorsBounds(); + return CaptureScreenBounds(bounds); + } + + /// Захватывает монитор, на котором расположено окно с указанным дескриптором + /// Дескриптор окна + /// Захваченное изображение + private static BitmapSource CaptureScreenFromHandle(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) + return CaptureAllScreens(); // Без привязки к окну берём панораму + + var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + return CaptureMonitorBounds(monitor); + } + + /// Захватывает окно WPF + /// Окно для захвата + /// Захваченное изображение + private static BitmapSource CaptureWindow(Window window) + { + if (window is null) throw new ArgumentNullException(nameof(window)); + + var hwnd = new WindowInteropHelper(window).Handle; + var rect = GetWindowRect(hwnd); + return CaptureScreenBounds(rect); + } + + /// Захватывает область экрана с указанными границами + /// Границы области захвата + /// Захваченное изображение + private static BitmapSource CaptureScreenBounds(System.Drawing.Rectangle bounds) + { + if (bounds.Width <= 0 || bounds.Height <= 0) + return BitmapSource.Create(1, 1, 96, 96, PixelFormats.Pbgra32, null, new byte[4], 4); // Защита от невалидных размеров + + using var bmp = new Bitmap(bounds.Width, bounds.Height); + using var g = Graphics.FromImage(bmp); + + g.CopyFromScreen(bounds.Left, bounds.Top, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy); // Источник экран + + var hbitmap = bmp.GetHbitmap(); + try + { + return Imaging.CreateBitmapSourceFromHBitmap( + hbitmap, + IntPtr.Zero, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + } + finally + { + DeleteObject(hbitmap); // Предотвращаем утечку GDI + } + } + + /// Сохраняет одиночный кадр в PNG файл + /// Кадр для сохранения + private void SavePng(BitmapSource frame) + { + if (frame is null) throw new ArgumentNullException(nameof(frame)); + + // Подготавливаем каталог для сохранения (поддержка относительных путей) + var dir = Path.IsPathRooted(ScreenshotsDirectory) + ? ScreenshotsDirectory + : Path.Combine(AppContext.BaseDirectory, ScreenshotsDirectory); + + Directory.CreateDirectory(dir); + + // Формируем имя файла с заменой токена {timestamp} + var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ss.fff"); + var name = (FileNameFormat ?? "screen[{timestamp}]").Replace("{timestamp}", timestamp); + name = SanitizeFileName(name); + var file = Path.Combine(dir, name + ".png"); + + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(frame)); + + using var fs = File.Create(file); + encoder.Save(fs); + } + + /// Сохраняет последовательность кадров в GIF файл + /// Кадры для сохранения + private void SaveGif(IEnumerable frames) + { + if (frames is null) throw new ArgumentNullException(nameof(frames)); + + // Подготавливаем каталог для сохранения (поддержка относительных путей) + var dir = Path.IsPathRooted(ScreenshotsDirectory) + ? ScreenshotsDirectory + : Path.Combine(AppContext.BaseDirectory, ScreenshotsDirectory); + + Directory.CreateDirectory(dir); + + // Формируем имя файла с заменой токена {timestamp} + var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ss.fff"); + var name = (FileNameFormatGif ?? "anim[{timestamp}]").Replace("{timestamp}", timestamp); + name = SanitizeFileName(name); + var file = Path.Combine(dir, name + ".gif"); + + var encoder = new GifBitmapEncoder(); + foreach (var f in frames) + encoder.Frames.Add(f); + + using var fs = File.Create(file); + encoder.Save(fs); + } + + /// Возвращает дескриптор активного окна на текущем рабочем столе + /// Дескриптор окна или + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + /// Возвращает позицию курсора в экранных координатах + /// Экранные координаты курсора + /// true при успехе + [DllImport("user32.dll")] + private static extern bool GetCursorPos(out POINT lpPoint); + + /// Возвращает дескриптор монитора,Nearest к заданной точке + /// Точка в экранных координатах + /// Флаги выбора монитора + /// Дескриптор монитора или + [DllImport("user32.dll")] + private static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags); + + /// Возвращает дескриптор монитора,Nearest к окну + /// Дескриптор окна + /// Флаги выбора монитора + /// Дескриптор монитора или + [DllImport("user32.dll")] + private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); + + /// Заполняет структуру с информацией о мониторе + /// Дескриптор монитора + /// Структура для заполнения, поле cbSize должно быть инициализировано + /// true при успехе + [DllImport("user32.dll")] + private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); + + /// Перечисляет мониторы, отображаемые на указанном контексте устройства + /// Контекст устройства или для всего экрана + /// Область ограничения или + /// Callback, вызываемый для каждого монитора + /// Пользовательские данные для callback + /// true при успехе + [DllImport("user32.dll")] + private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData); + + /// Возвращает прямоугольник окна в экранных координатах + /// Дескриптор окна + /// Прямоугольник окна в экранных координатах + /// true при успехе + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + + /// Освобождает GDI-объект, созданный через WinAPI + /// Дескриптор GDI-объекта + /// true при успехе + [DllImport("gdi32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + + private const uint MONITOR_DEFAULTTONEAREST = 2; + + private static string SanitizeFileName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + return "screen"; + + foreach (var c in Path.GetInvalidFileNameChars()) + name = name.Replace(c, '_'); + + return name; + } + + /// Получает прямоугольник окна по его дескриптору + /// Дескриптор окна + /// Прямоугольник окна или пустой прямоугольник при ошибке + private static System.Drawing.Rectangle GetWindowRect(IntPtr hwnd) + { + if (hwnd == IntPtr.Zero) + return default; + + if (!GetWindowRect(hwnd, out var rect)) + return default; + + return rect.ToRectangle(); + } + + /// Захватывает область монитора по его дескриптору + /// Дескриптор монитора + /// Захваченное изображение + private static BitmapSource CaptureMonitorBounds(IntPtr monitor) + { + var bounds = GetMonitorBounds(monitor); + return CaptureScreenBounds(bounds); + } + + /// Получает границы монитора по его дескриптору + /// Дескриптор монитора + /// Прямоугольник монитора или пустой прямоугольник при ошибке + private static System.Drawing.Rectangle GetMonitorBounds(IntPtr monitor) + { + if (monitor == IntPtr.Zero) + return default; + + var info = new MONITORINFO(cbSize: Marshal.SizeOf()); + if (!GetMonitorInfo(monitor, ref info)) + return default; + + return info.rcMonitor.ToRectangle(); + } + + /// Вычисляет объединённые границы всех подключённых мониторов + /// Прямоугольник, охватывающий все мониторы + private static System.Drawing.Rectangle GetAllMonitorsBounds() + { + var any = false; + var union = default(System.Drawing.Rectangle); + + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + (hMonitor, _, _, _) => + { + var bounds = GetMonitorBounds(hMonitor); + if (bounds.IsEmpty) + return true; // Пропускаем невалидные мониторы + + union = any ? Rectangle.Union(union, bounds) : bounds; + any = true; + return true; // Продолжаем перечисление + }, + IntPtr.Zero); + + return union; + } + + /// Точка в экранных координатах WinAPI + private struct POINT { public int X; public int Y; } + + /// Прямоугольник WinAPI в экранных координатах + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + /// Преобразует WinAPI прямоугольник в + /// Эквивалентный + public System.Drawing.Rectangle ToRectangle() => + Rectangle.FromLTRB(Left, Top, Right, Bottom); + } + + /// WinAPI структура с информацией о мониторе + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MONITORINFO + { + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public uint dwFlags; + + public MONITORINFO(int cbSize) + { + this.cbSize = cbSize; + rcMonitor = default; + rcWork = default; + dwFlags = 0; + } + } + + /// Callback WinAPI для перечисления мониторов + /// Дескриптор монитора + /// Контекст устройства монитора + /// Указатель на прямоугольник монитора + /// Пользовательские данные + /// true чтобы продолжить перечисление + private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, IntPtr lprcMonitor, IntPtr dwData); +} + +/// Режимы захвата экрана по умолчанию +public enum CaptureFallbackMode +{ + /// Главное окно приложения + MainWindow, + /// Активное окно операционной системы + ActiveWindow, + /// Монитор под курсором мыши + MousePosition, + /// Панорама всех мониторов + Panorama +} diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 0a20789..5e463aa 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -1,4 +1,4 @@ - + @@ -61,25 +61,14 @@ - - - - - - - - - - - - - + + From 0124683c0096005b3f1050be17a0ea2ea1a80253 Mon Sep 17 00:00:00 2001 From: Infarh Date: Wed, 21 Jan 2026 23:10:44 +0300 Subject: [PATCH 02/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20NuGet=20=D0=B8=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены C#-скрипты для автоматизации публикации NuGet-пакетов, проверки их версий и работы с XML. В проект добавлены батники для очистки артефактов сборки и публикации пакетов с поддержкой зависимостей. В csproj упрощены зависимости и добавлен System.Drawing.Common для net8.0/9.0. --- .scripts/nuget-ver-remote.cs | 81 ++++++++++++++++++++++++ .scripts/nuget-ver-wait.cs | 116 ++++++++++++++++++++++++++++++++++ .scripts/xml-xpath.cs | 26 ++++++++ clear-cache.bat | 6 ++ clear-obj.bat | 3 + clear.bat | 16 +++++ publish-nuget.bat | 117 +++++++++++++++++++++++++++++++++++ 7 files changed, 365 insertions(+) create mode 100644 .scripts/nuget-ver-remote.cs create mode 100644 .scripts/nuget-ver-wait.cs create mode 100644 .scripts/xml-xpath.cs create mode 100644 clear-cache.bat create mode 100644 clear-obj.bat create mode 100644 clear.bat create mode 100644 publish-nuget.bat diff --git a/.scripts/nuget-ver-remote.cs b/.scripts/nuget-ver-remote.cs new file mode 100644 index 0000000..efd9b21 --- /dev/null +++ b/.scripts/nuget-ver-remote.cs @@ -0,0 +1,81 @@ +#!/usr/local/bin/dotnet run +// Файл file-based app скрипта для определения текущей версии NuGet пакета из удаленного репозитория. +// Использование: dotnet run .scripts/nuget-ver-remote.cs + +#nullable enable + +#:package NuGet.Protocol@7.0.1 +#:package NuGet.Configuration@7.0.1 + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; + +if (args.Length < 1) +{ + Console.Error.WriteLine("Usage: dotnet run .scripts/nuget-ver-remote.cs "); + Environment.Exit(1); +} + +var package_name = args[0]; // имя пакета из аргумента +if (string.IsNullOrWhiteSpace(package_name)) +{ + Console.Error.WriteLine("Package name is empty"); + Environment.Exit(1); +} + +try +{ + // Создадим репозиторий NuGet (v3 API) + var source_repo = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + + // Получим ресурс метаданных пакета + using var cache = new SourceCacheContext(); + var logger = NullLogger.Instance; + var resource = await source_repo.GetResourceAsync().ConfigureAwait(false); + + // Запросим все метаданные по пакету (включая pre-release), не включая unlisted + var metadata = await resource.GetMetadataAsync(package_name, includePrerelease: true, includeUnlisted: false, cache, logger, CancellationToken.None).ConfigureAwait(false); + + var metadata_list = metadata?.ToList() ?? new System.Collections.Generic.List(); + if (metadata_list.Count == 0) + { + Console.Error.WriteLine($"Package not found: {package_name}"); + Environment.Exit(2); + } + + // Соберём версии и выберем последнюю стабильную, если есть, иначе последнюю доступную + var versions = metadata_list + .Select(m => m.Identity.Version) + .Where(v => v is not null) + .OrderBy(v => v) + .ToArray(); + + if (versions.Length == 0) + { + Console.Error.WriteLine("No versions found"); + Environment.Exit(2); + } + + var stable_version = versions + .Where(v => !v.IsPrerelease) + .OrderByDescending(v => v) + .FirstOrDefault() + ?? versions.OrderByDescending(v => v).First(); + + var latest_version = stable_version.ToNormalizedString(); + + Console.WriteLine(latest_version); // вывод версии в stdout + Environment.Exit(0); +} +catch (Exception ex) +{ + Console.Error.WriteLine($"Error: {ex.Message}"); + Environment.Exit(2); +} \ No newline at end of file diff --git a/.scripts/nuget-ver-wait.cs b/.scripts/nuget-ver-wait.cs new file mode 100644 index 0000000..02e0347 --- /dev/null +++ b/.scripts/nuget-ver-wait.cs @@ -0,0 +1,116 @@ +#!/usr/local/bin/dotnet run +// Файл file-based app скрипта для ожидания появления указанной версии NuGet пакета на сервере. +// Использование: dotnet run .scripts/nuget-ver-wait.cs [-n ] [-t ] + +#nullable enable + +#:package NuGet.Protocol@7.0.1 +#:package NuGet.Configuration@7.0.1 + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; + +if (args.Length < 2) +{ + Console.Error.WriteLine("Usage: dotnet run .scripts/nuget-ver-wait.cs [-n ] [-t ]"); + Environment.Exit(1); +} + +var package_name = args[0]; // имя пакета +var target_version_str = args[1]; // требуемая версия +if (string.IsNullOrWhiteSpace(package_name) || string.IsNullOrWhiteSpace(target_version_str)) +{ + Console.Error.WriteLine("Package name or target version is empty"); + Environment.Exit(1); +} + +// Значения по умолчанию +var tries = 10; // -n по умолчанию +var timeout_ms = 1000; // -t по умолчанию + +// Разбор дополнительных аргументов +for (var i = 2; i < args.Length; i++) +{ + var a = args[i]; + const StringComparison cmp = StringComparison.OrdinalIgnoreCase; + if (string.Equals(a, "-n", cmp) && i + 1 < args.Length) + { + if (int.TryParse(args[++i], out var v)) tries = v; + } + else if (string.Equals(a, "-t", cmp) && i + 1 < args.Length) + { + if (int.TryParse(args[++i], out var v)) timeout_ms = v; + } +} + +if (tries <= 0) tries = 1; +if (timeout_ms < 0) timeout_ms = 0; + +if (!NuGetVersion.TryParse(target_version_str, out var target_version)) +{ + Console.Error.WriteLine($"Невозможно распарсить целевую версию: {target_version_str}"); + Environment.Exit(1); +} + +Console.WriteLine($"Ожидание версии {target_version} пакета {package_name} на nuget.org ({tries} попыток, таймаут {timeout_ms}ms)"); + +try +{ + var source_repo = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + using var cache = new SourceCacheContext(); + var logger = NullLogger.Instance; + var resource = await source_repo.GetResourceAsync(); + + for (var attempt = 1; attempt <= tries; attempt++) + { + // Получим метаданные пакета + var metadata = await resource.GetMetadataAsync(package_name, includePrerelease: true, includeUnlisted: false, cache, logger, CancellationToken.None); + var metadata_list = metadata?.ToList() ?? []; + + if (metadata_list.Count == 0) + Console.WriteLine($"[{attempt}/{tries}] Пакет не найден на сервере"); + else + { + var versions = metadata_list + .Select(m => m.Identity.Version) + .Where(v => v is not null) + .OrderByDescending(v => v) + .ToArray(); + + if (versions.Length == 0) + Console.WriteLine($"[{attempt}/{tries}] На сервере нет версий пакета"); + else + { + var latest = versions[0]; + Console.WriteLine($"[{attempt}/{tries}] Серверная последняя версия: {latest}"); + + // Сравним последнюю серверную версию с целевой + if (latest < target_version) + Console.WriteLine($"Серверная версия {latest} младше требуемой {target_version}, ожидаем..."); + else + { + Console.WriteLine($"Требуемая версия {target_version} доступна на сервере (серверная версия {latest})"); + Environment.Exit(0); + } + } + } + + if (attempt < tries) + await Task.Delay(timeout_ms); + } + + Console.Error.WriteLine($"Не удалось дождаться версии {target_version} для пакета {package_name} после {tries} попыток"); + Environment.Exit(2); +} +catch (Exception ex) +{ + Console.Error.WriteLine($"Ошибка: {ex.Message}"); + Environment.Exit(2); +} \ No newline at end of file diff --git a/.scripts/xml-xpath.cs b/.scripts/xml-xpath.cs new file mode 100644 index 0000000..687f443 --- /dev/null +++ b/.scripts/xml-xpath.cs @@ -0,0 +1,26 @@ +#!/usr/local/bin/dotnet run +// Файл file-based app скрипта для выполнения XPath запросов к XML файлам. +// запуск через команду: dotnet run .scripts/xml-xpath.cs + +using System; +using System.Xml; + +if (args.Length != 2) +{ + Console.WriteLine("Usage: dotnet run .scripts/xml-xpath.cs "); + return; +} + +var xml_file = args[0]; +var xpath_query = args[1]; + +XmlDocument xml_doc = new XmlDocument(); +xml_doc.Load(xml_file); + +var nodes = xml_doc.SelectNodes(xpath_query); +if (nodes is null || nodes.Count == 0) + Console.WriteLine("No nodes found."); // нет найденных узлов +else + // используем явный тип XmlNode чтобы не получать object и иметь доступ к OuterXml + foreach (XmlNode node in nodes) + Console.WriteLine(node.OuterXml); diff --git a/clear-cache.bat b/clear-cache.bat new file mode 100644 index 0000000..28d5025 --- /dev/null +++ b/clear-cache.bat @@ -0,0 +1,6 @@ +@echo off + +rmdir /s /q .vs +rmdir /s /q _ReSharper.Caches + +pause \ No newline at end of file diff --git a/clear-obj.bat b/clear-obj.bat new file mode 100644 index 0000000..a3afcc8 --- /dev/null +++ b/clear-obj.bat @@ -0,0 +1,3 @@ +@echo off + +for %%d in (bin obj) do for /f %%f in ('dir /s /b /d %%d') do rd /s /q "%%f" \ No newline at end of file diff --git a/clear.bat b/clear.bat new file mode 100644 index 0000000..b7623f8 --- /dev/null +++ b/clear.bat @@ -0,0 +1,16 @@ +@echo off + +for %%d in ( + bin obj .vs _ReSharper.Caches +) do ( + for /f %%f in ('dir /s /b /d /a %%d') do ( + @if exist "%%f" ( + echo rd "%%f" + rd /s /q "%%f" + ) + ) +) + +rd /s /q TestResults + +pause \ No newline at end of file diff --git a/publish-nuget.bat b/publish-nuget.bat new file mode 100644 index 0000000..3955af1 --- /dev/null +++ b/publish-nuget.bat @@ -0,0 +1,117 @@ +@echo off +:: Запуск процесса публикации пакета NuGet + +:: Изменим кодировку терминала на UTF-8 для корректного отображения символов +chcp 65001 > nul + +set "script_dir=%~dp0" +if "%script_dir:~-1%"=="\" set "script_dir=%script_dir:~0,-1%" +for %%i in ("%script_dir%") do set "PackageName=%%~nxi" +echo Публикация пакета %PackageName% + +:: Получим версию проекта, подставляя %PackageName% в путь к csproj +set "local_version=" +set "temp_ver=%TEMP%\%PackageName%_ver.txt" +:: Запускаем dotnet и перенаправляем вывод во временный файл чтобы избежать проблем с синтаксисом командной строки +dotnet run .scripts\xml-xpath.cs "%~dp0\%PackageName%\%PackageName%.csproj" "/Project/PropertyGroup/Version/text()" > "%temp_ver%" 2>nul +if exist "%temp_ver%" ( + set /p local_version=<"%temp_ver%" + del "%temp_ver%" 2>nul +) +if defined local_version ( + echo Локальная версия: %local_version% +) else ( + echo Не удалось получить версию проекта + exit /b 1 +) + +:: Получим версию пакета на сервере через скрипт .scripts\nuget-ver-remote.cs +set "remote_version=" +set "temp_remote=%TEMP%\%PackageName%_remote_ver.txt" +:: Вызов скрипта, вывод версии в файл +dotnet run .scripts\nuget-ver-remote.cs "%PackageName%" > "%temp_remote%" 2>nul +if exist "%temp_remote%" ( + set /p remote_version=<"%temp_remote%" + del "%temp_remote%" 2>nul +) +if defined remote_version ( + echo Серверная версия: %remote_version% + if "%local_version%"=="%remote_version%" ( + echo Версия на сервере совпадает с локальной, публикация не требуется. + exit /b 0 + ) else ( + echo Локальная версия отличается от серверной, продолжаем публикацию. + ) +) else ( + echo Не удалось получить версию с сервера, продолжаем публикацию. +) + +:: Проверим что в локальном репозитории нет незакоммиченных изменений +rem git status --porcelain > nul +rem if not errorlevel 1 ( +rem echo Есть незакоммиченные изменения. Пожалуйста, закоммитьте их перед публикацией. +rem pause +rem exit /b 1 +rem ) + +git pull + +:: Выполнение мержа из ветки dev в ветку master +::git checkout master +::git merge dev +::git push origin master +::git checkout dev + +:: Сборка и публикация проекта произойдёт автоматически на сервере GitHub Actions + +:: Ожидание обновления новой версии на сервер NuGet.org 15 итераций по 20 секунд +dotnet run .scripts\nuget-ver-wait.cs "%PackageName%" "%local_version%" -n 15 -t 20000 +:: если errorlevel не 0, дождаться не удалось. Требуется внимание пользователя +if errorlevel 1 ( + echo Не удалось подтвердить публикацию пакета на сервере NuGet.org. + echo Пожалуйста, проверьте вручную. + pause + exit /b 1 +) + +:: Теперь пройдём по всем зависимостям от данного пакета и вызовем их публикацию +:: для этого перечислим все строки в файле .\.scripts\dependencies.txt + +echo. +if not exist ".\.scripts\dependencies.txt" ( + echo Файл зависимостей не найден: .\.scripts\dependencies.txt + echo Публикация завершена. + exit /b 0 +) + +rem тут будет выполнение скрипта ожидания завершения проверки пакета на сервере NuGet.org + + +for /f "usebackq delims=" %%i in (".\.scripts\dependencies.txt") do ( + rem Проверим что каталог существует + if not exist "%%i" ( + echo Каталог не найден: %%i + rem пропустить эту итерацию + ) else ( + rem Проверим, что в каталоге есть файл publish-nuget.bat + if not exist "%%i\publish-nuget.bat" ( + echo Файл publish-nuget.bat не найден в каталоге: %%i + rem пропустить эту итерацию + ) else ( + echo. + echo Публикация зависимого пакета: %%i + pushd "%%i" + rem Вызов публикации зависимого пакета (раскомментировать при необходимости) + call publish-nuget.bat + if errorlevel 1 ( + echo Ошибка при публикации зависимого пакета: %%i + popd + exit /b 1 + ) + popd + ) + ) +) + +pause +exit /b 0 \ No newline at end of file From 6b8e4a38cec92505fe861c98d0f7a675a3779e93 Mon Sep 17 00:00:00 2001 From: Infarh Date: Mon, 2 Feb 2026 23:11:32 +0300 Subject: [PATCH 03/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B=20CloseApp=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен новый класс CloseApp, реализующий команду для закрытия всего WPF-приложения. Класс поддерживает задание кода выхода через параметр команды или свойство ExitCode. Если код выхода не указан, используется стандартное завершение приложения. --- MathCore.WPF/Commands/CloseApp.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 MathCore.WPF/Commands/CloseApp.cs diff --git a/MathCore.WPF/Commands/CloseApp.cs b/MathCore.WPF/Commands/CloseApp.cs new file mode 100644 index 0000000..5f0455d --- /dev/null +++ b/MathCore.WPF/Commands/CloseApp.cs @@ -0,0 +1,26 @@ +using System.Windows; + +namespace MathCore.WPF.Commands; + +/// Команда для закрытия всего приложения +public class CloseApp : Command +{ + /// Код выхода из приложения + public int? ExitCode { get; set; } + + /// Выполняет команду закрытия всего приложения + public override void Execute(object? p) + { + var code = p switch + { + int c => c, + string s when int.TryParse(s, out var v) => v, + _ => ExitCode + }; + + if (code is not null) + Application.Current.Shutdown(code.Value); + else + Application.Current.Shutdown(); + } +} \ No newline at end of file From f6ece760b541e69f39a8fbef756e0e4d8c969c9a Mon Sep 17 00:00:00 2001 From: Infarh Date: Mon, 2 Feb 2026 23:17:11 +0300 Subject: [PATCH 04/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20TerminateApp=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=B8=D0=BD=D1=83=D0=B4=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен новый класс TerminateApp в MathCore.WPF.Commands, наследующийся от CloseApp. Класс реализует команду завершения приложения с возможностью указания кода выхода через параметр команды. Если код не задан, используется 0. --- MathCore.WPF/Commands/TerminateApp.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 MathCore.WPF/Commands/TerminateApp.cs diff --git a/MathCore.WPF/Commands/TerminateApp.cs b/MathCore.WPF/Commands/TerminateApp.cs new file mode 100644 index 0000000..824b07d --- /dev/null +++ b/MathCore.WPF/Commands/TerminateApp.cs @@ -0,0 +1,20 @@ +namespace MathCore.WPF.Commands; + +/// Команда для завершения всего приложения принудительно +public class TerminateApp : CloseApp +{ + /// Выполняет команду завершения всего приложения + public override void Execute(object? p) + { + var code = p switch + { + int c => c, + string s when int.TryParse(s, out var v) => v, + _ => ExitCode + }; + if (code is not null) + Environment.Exit(code.Value); + else + Environment.Exit(0); + } +} \ No newline at end of file From 40f2b1a5f990e29968e8b8158806f58ca105ee7b Mon Sep 17 00:00:00 2001 From: Infarh Date: Tue, 3 Feb 2026 11:40:04 +0300 Subject: [PATCH 05/33] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=20Parameter=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=BC=20=D0=B2=20TryExecute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Метод TryExecute теперь принимает необязательный параметр object? Parameter со значением по умолчанию null. Это упрощает вызов метода для команд без параметров и повышает универсальность использования. --- MathCore.WPF/Extensions/CommandEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathCore.WPF/Extensions/CommandEx.cs b/MathCore.WPF/Extensions/CommandEx.cs index 09a9cb0..7a9838c 100644 --- a/MathCore.WPF/Extensions/CommandEx.cs +++ b/MathCore.WPF/Extensions/CommandEx.cs @@ -38,7 +38,7 @@ public static TCommand WithDescription(this TCommand Command, string? /// Команда /// Параметр команды /// Истина, если команда была успешно выполнена - public static bool TryExecute(this ICommand? Command, object Parameter) + public static bool TryExecute(this ICommand? Command, object? Parameter = null) { if (Command is null || !Command.CanExecute(Parameter)) return false; From 291e271d4771de8d27d44ac190bf944fbb7d2981 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 13:07:50 +0300 Subject: [PATCH 06/33] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/explain-code.prompt.md | 21 +++++++ MathCore.WPF.slnx | 1 + MathCore.WPF/Operations/Operation.cs | 56 +++++++++---------- Tests/MathCore.WPF.WindowTest/App.xaml | 2 +- .../MathCore.WPF.WindowTest/TestWindows8.xaml | 46 ++++++++++----- 5 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 .github/explain-code.prompt.md diff --git a/.github/explain-code.prompt.md b/.github/explain-code.prompt.md new file mode 100644 index 0000000..8b8029b --- /dev/null +++ b/.github/explain-code.prompt.md @@ -0,0 +1,21 @@ +--- +agent: 'agent' +description: 'Generate a clear code explanation with examples' +--- + +Explain the following code in a clear, beginner-friendly way: + +Code to explain: ${input:code:Paste your code here} +Target audience: ${input:audience:Who is this explanation for? (e.g., beginners, intermediate developers, etc.)} + +Please provide: + +* A brief overview of what the code does +* A step-by-step breakdown of the main parts +* Explanation of any key concepts or terminology +* A simple example showing how it works +* Common use cases or when you might use this approach + +Use clear, simple language and avoid unnecessary jargon. + +Отвечай используя русский язык. \ No newline at end of file diff --git a/MathCore.WPF.slnx b/MathCore.WPF.slnx index 4b540bd..a30974a 100644 --- a/MathCore.WPF.slnx +++ b/MathCore.WPF.slnx @@ -5,6 +5,7 @@ + diff --git a/MathCore.WPF/Operations/Operation.cs b/MathCore.WPF/Operations/Operation.cs index 98e4a0b..b50b390 100644 --- a/MathCore.WPF/Operations/Operation.cs +++ b/MathCore.WPF/Operations/Operation.cs @@ -36,12 +36,12 @@ public class Operation(OperationAction Execute, Func? CanExecute /// Логика выполнения - Выполнить операцию private Task OnStartCommandExecutedAsync(object? p) { - Error = null; + Error = null; _Cancellation = new(); var cancel = _Cancellation.Token; - _Timer = Stopwatch.StartNew(); + _Timer = Stopwatch.StartNew(); _OperationTask = _Execute(p, new Progress(progress => Progress = progress), cancel); - InProgress = true; + InProgress = true; _ = _OperationTask.ContinueWith(OnOperationCompletedAsync, CancellationToken.None); @@ -54,15 +54,15 @@ protected virtual Task OnOperationCompletedAsync(Task ResultTask) { Error = ResultTask switch { - { IsFaulted : false } => null, + { IsFaulted: false } => null, { Exception.InnerExceptions: { Count: 1 } errors } => errors[0], - _ => ResultTask.Exception + _ => ResultTask.Exception }; _Timer?.Stop(); _Cancellation = null; - InProgress = false; - Progress = 0; + InProgress = false; + Progress = 0; SetTime(default, default, double.NaN); return Task.CompletedTask; @@ -96,8 +96,8 @@ protected set /// Скорость движения значения прогресса - число процентов в секунду private void SetTime(TimeSpan Elapsed, TimeSpan Remaining, double pps) { - ElapsedTime = Elapsed; - RemainingTime = Remaining; + ElapsedTime = Elapsed; + RemainingTime = Remaining; PercentPerSecond = pps; OnPropertyChanged(nameof(ElapsedTime)); @@ -174,7 +174,7 @@ event EventHandler? ICommand.CanExecuteChanged { add => Start.CanExecuteChanged += value; remove => Start.CanExecuteChanged -= value; - } + } #endregion } @@ -208,12 +208,12 @@ public class Operation(OperationAction Execute, Func? CanExecute /// Логика выполнения - Выполнить операцию private Task OnStartCommandExecutedAsync(T? p) { - Error = null; + Error = null; _Cancellation = new(); var cancel = _Cancellation.Token; - _Timer = Stopwatch.StartNew(); + _Timer = Stopwatch.StartNew(); _OperationTask = _Execute(p, new Progress(progress => Progress = progress), cancel); - InProgress = true; + InProgress = true; _ = _OperationTask.ContinueWith(OnOperationCompletedAsync, CancellationToken.None); @@ -226,15 +226,15 @@ protected virtual Task OnOperationCompletedAsync(Task ResultTask) { Error = ResultTask switch { - { IsFaulted : false } => null, + { IsFaulted: false } => null, { Exception.InnerExceptions: { Count: 1 } errors } => errors[0], - _ => ResultTask.Exception + _ => ResultTask.Exception }; _Timer?.Stop(); _Cancellation = null; - InProgress = false; - Progress = 0; + InProgress = false; + Progress = 0; SetTime(default, default, double.NaN); return Task.CompletedTask; @@ -268,8 +268,8 @@ protected set /// Скорость движения значения прогресса - число процентов в секунду private void SetTime(TimeSpan Elapsed, TimeSpan Remaining, double pps) { - ElapsedTime = Elapsed; - RemainingTime = Remaining; + ElapsedTime = Elapsed; + RemainingTime = Remaining; PercentPerSecond = pps; OnPropertyChanged(nameof(ElapsedTime)); @@ -380,12 +380,12 @@ public class Operation(OperationFunc Execute, FuncЛогика выполнения - Выполнить операцию private Task OnStartCommandExecutedAsync(T? p) { - Error = null; + Error = null; _Cancellation = new(); var cancel = _Cancellation.Token; - _Timer = Stopwatch.StartNew(); + _Timer = Stopwatch.StartNew(); _OperationTask = _Execute(p, new Progress(progress => Progress = progress), cancel); - InProgress = true; + InProgress = true; _ = _OperationTask.ContinueWith(OnOperationCompletedAsync, CancellationToken.None); @@ -398,9 +398,9 @@ protected virtual async Task OnOperationCompletedAsync(Task ResultTask) { Error = ResultTask switch { - { IsFaulted : false } => null, + { IsFaulted: false } => null, { Exception.InnerExceptions: { Count: 1 } errors } => errors[0], - _ => ResultTask.Exception + _ => ResultTask.Exception }; if (!ResultTask.IsFaulted) @@ -408,8 +408,8 @@ protected virtual async Task OnOperationCompletedAsync(Task ResultTask) _Timer?.Stop(); _Cancellation = null; - InProgress = false; - Progress = 0; + InProgress = false; + Progress = 0; SetTime(default, default, double.NaN); } @@ -451,8 +451,8 @@ protected set /// Скорость движения значения прогресса - число процентов в секунду private void SetTime(TimeSpan Elapsed, TimeSpan Remaining, double pps) { - ElapsedTime = Elapsed; - RemainingTime = Remaining; + ElapsedTime = Elapsed; + RemainingTime = Remaining; PercentPerSecond = pps; OnPropertyChanged(nameof(ElapsedTime)); diff --git a/Tests/MathCore.WPF.WindowTest/App.xaml b/Tests/MathCore.WPF.WindowTest/App.xaml index 5ffd5fa..89ec0df 100644 --- a/Tests/MathCore.WPF.WindowTest/App.xaml +++ b/Tests/MathCore.WPF.WindowTest/App.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:MathCore.WPF.WindowTest" - StartupUri="TestToastNotifyWindow.xaml"> + StartupUri="TestWindows8.xaml"> diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml index 055264b..9e59cf5 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml @@ -1,28 +1,46 @@  - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + From 6a9b52b571670d62318d8fe9e211f0c60fb2daa9 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 15:28:57 +0300 Subject: [PATCH 07/33] =?UTF-8?q?=D0=A3=D0=BD=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D1=83=D0=B3=20=D0=B8=20=D1=81=D0=B5=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=20Arc/Pie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Уточнена документация и примеры для Arc и Pie. Дуги и сектора теперь всегда строятся по часовой стрелке между нормализованными углами. Исправлены алгоритмы построения: добавлена нормализация углов, корректно обрабатываются полные окружности и малые дуги/сектора, устранена поддержка обратного направления. Исправлены ошибки построения внутренней дуги в Pie. Улучшена предсказуемость и соответствие документации. --- MathCore.WPF/Shapes/Arc.cs | 34 ++++++---- MathCore.WPF/Shapes/Pie.cs | 68 ++++++++++++------- .../MathCore.WPF.WindowTest/TestWindows8.xaml | 43 +++--------- 3 files changed, 75 insertions(+), 70 deletions(-) diff --git a/MathCore.WPF/Shapes/Arc.cs b/MathCore.WPF/Shapes/Arc.cs index f70914f..933b229 100644 --- a/MathCore.WPF/Shapes/Arc.cs +++ b/MathCore.WPF/Shapes/Arc.cs @@ -60,7 +60,8 @@ static Arc() /// Начальный угол дуги в градусах /// /// Отсчёт ведётся по часовой стрелке, 0 градусов направлен вправо, 90 градусов вниз - /// Дуга рисуется от к , знак разности задаёт направление обхода + /// Дуга всегда рисуется по часовой стрелке от нормализованного к нормализованному + /// Для задания больших дуг (более 180°) или дуг против часовой используйте углы вне диапазона [0;360) /// public double StartAngle { get => (double)GetValue(StartAngleProperty); set => SetValue(StartAngleProperty, value); } @@ -74,8 +75,11 @@ static Arc() /// Конечный угол дуги в градусах /// - /// Если разница между и по модулю близка к 360 градусам, - /// будет отрисована полная окружность вместо дуги + /// После нормализации обоих углов к диапазону [0;360) дуга рисуется по часовой стрелке + /// Примеры: + /// - StartAngle=270, StopAngle=60 → дуга 150° по часовой (270→360→60) + /// - StartAngle=60, StopAngle=270 → дуга 210° по часовой (60→270) + /// - StartAngle=0, StopAngle=360 → полная окружность (разность исходных углов = 360°) /// public double StopAngle { get => (double)GetValue(StopAngleProperty); set => SetValue(StopAngleProperty, value); } @@ -146,18 +150,23 @@ private static Geometry GetGeometry(Rect rect, double Start, double End, double var h = rect.Height; if (w == 0 || h == 0) return Geometry.Empty; // Если хотя бы одна из сторон прямоугольника равна нулю, возвращаем пустую геометрию + // Вычисляем разность исходных углов для определения полной окружности + var d_raw_abs = Math.Abs(End - Start); + + // Если длина дуги по модулю близка к полному кругу или превышает его, рисуем полную окружность + if (d_raw_abs >= FullCircleDegrees - MinArcDegrees) + return new EllipseGeometry(rect); + + // Нормализуем углы к диапазону [0;360) var start_angle = NormalizeAngle(Start); var end_angle = NormalizeAngle(End); - var d_raw = end_angle - start_angle; - var d_abs = Math.Abs(d_raw); - - // Если длина дуги по модулю близка к полному кругу, рисуем полную окружность - if (d_abs >= FullCircleDegrees - MinArcDegrees) - return new EllipseGeometry(rect); + // Вычисляем угловое расстояние по часовой стрелке от start_angle до end_angle + var delta_clockwise = end_angle - start_angle; + if (delta_clockwise < 0) delta_clockwise += FullCircleDegrees; // Приводим к диапазону [0;360) // Слишком маленькая дуга считается нулевой - if (d_abs < MinArcDegrees) + if (delta_clockwise < MinArcDegrees) return Geometry.Empty; var p1 = GetPoint(start_angle, Radius, rect); // Вычисляем координаты начальной точки дуги @@ -170,8 +179,9 @@ private static Geometry GetGeometry(Rect rect, double Start, double End, double var radius_y = Math.Max(0, half_height * Radius); var arc = new Size(radius_x, radius_y); // Размеры дуги (радиусы эллипса), гарантируем неотрицательность - var is_large = d_abs > 180; // Определяем, является ли дуга большой (более 180 градусов) - var sweep_direction = d_raw >= 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise; // Учитываем направление дуги + // Дуга всегда рисуется по часовой стрелке между нормализованными углами + var is_large = delta_clockwise > 180; // Большая дуга, если угловое расстояние больше 180 градусов + var sweep_direction = SweepDirection.Clockwise; var geometry = new StreamGeometry(); // Создаём потоковую геометрию для описания дуги using var context = geometry.Open(); // Открываем контекст для построения фигуры diff --git a/MathCore.WPF/Shapes/Pie.cs b/MathCore.WPF/Shapes/Pie.cs index b3e78e6..65b99a6 100644 --- a/MathCore.WPF/Shapes/Pie.cs +++ b/MathCore.WPF/Shapes/Pie.cs @@ -17,6 +17,9 @@ public class Pie : Shape private const FrameworkPropertyMetadataOptions __DependendPropertyMetadataOptions = FrameworkPropertyMetadataOptions.AffectsRender; + private const double FullCircleDegrees = 360d; + private const double MinArcDegrees = 1e-6; // минимальная длина дуги в градусах + static Pie() { //StretchProperty.OverrideMetadata(typeof(Pie), new FrameworkPropertyMetadata(Stretch.None)); @@ -82,6 +85,10 @@ static Pie() coerceValueCallback: null)); /// Получает или устанавливает начальный угол сектора в градусах + /// + /// Отсчёт ведётся по часовой стрелке, 0 градусов направлен вправо, 90 градусов вниз + /// Сектор всегда рисуется по часовой стрелке от нормализованного к нормализованному + /// public double StartAngle { get => (double)GetValue(StartAngleProperty); set => SetValue(StartAngleProperty, value); } /// Определяет зависимое свойство для конечного угла сектора @@ -98,6 +105,13 @@ private static void OnStopAngleChanged(DependencyObject o, DependencyPropertyCha o.SetValue(AngleProperty, (double)e.NewValue - ((Pie)o).StartAngle); /// Получает или устанавливает конечный угол сектора в градусах + /// + /// После нормализации обоих углов к диапазону [0;360) сектор рисуется по часовой стрелке + /// Примеры: + /// - StartAngle=270, StopAngle=60 → сектор 150° по часовой (270→360→60) + /// - StartAngle=60, StopAngle=270 → сектор 210° по часовой (60→270) + /// - StartAngle=0, StopAngle=360 → полный круг (разность исходных углов = 360°) + /// public double StopAngle { get => (double)GetValue(StopAngleProperty); set => SetValue(StopAngleProperty, value); } /// Определяет зависимое свойство для угла раствора сектора @@ -177,7 +191,7 @@ protected override Size ArrangeOverride(Size FinalSize) return size; } - /// Вычисляетсектора на основе заданных параметров + /// Вычисляет геометрию сектора на основе заданных параметров /// Прямоугольник ограничивающей области /// Начальный угол в градусах /// Конечный угол в градусах @@ -205,7 +219,7 @@ private Geometry GetGeometry(Rect rect, double start, double stop, double R, dou return _Pie; } - /// Обновляетэллипсов внешнего и внутреннего радиусов + /// Обновляет геометрию эллипсов внешнего и внутреннего радиусов /// Прямоугольник ограничивающей области /// Внешний радиус /// Внутренний радиус @@ -227,15 +241,7 @@ private void ChangeGeometry(Rect rect, double R, double r, bool aligned) _InnerEllipse.RadiusY = h * r; } - //private static Point GetPoint(Point p0, Rect rect, double a, double r, double w, double h) - //{ - // const double to_rad = Math.PI / 180.0; - // a -= 90; - // a *= to_rad; - // return new Point(p0.X + r * Math.Cos(a) * w / 2, p0.Y + r * Math.Sin(a) * h / 2); - //} - - /// Вычисляетна эллипсе по углу и радиусу + /// Вычисляет координата точки на эллипсе по углу и радиусу /// Прямоугольник ограничивающей области /// Угол в градусах /// Радиус (от 0 до 1) @@ -251,7 +257,14 @@ private static Point GetPoint(Rect rect, double a, double r) return new(x, y); } - /// Рисует гев контекст потока + /// Нормализует угол к диапазону [0;360) + private static double NormalizeAngle(double angle) + { + angle %= FullCircleDegrees; + return angle < 0 ? angle + FullCircleDegrees : angle; + } + + /// Рисует геометрию сектора в контекст потока /// Контекст потока геометрии /// Прямоугольник ограничивающей области /// Внешний радиус @@ -269,11 +282,16 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d // Вычисляем центральную точку прямоугольника var p0 = new Point(0.5 * rect.Width + rect.Left, 0.5 * rect.Height + rect.Top); - // Нормализуем углы: a - меньший угол, b - больший угол - var a = Math.Min(start, stop); - var b = Math.Max(start, stop); - var d = b - a; // Разница углов (угол раствора сектора) - if (d is 0d) return; + // Нормализуем углы к диапазону [0;360) + var start_angle = NormalizeAngle(start); + var stop_angle = NormalizeAngle(stop); + + // Вычисляем угловое расстояние по часовой стрелке от start_angle до stop_angle + var delta_clockwise = stop_angle - start_angle; + if (delta_clockwise < 0) delta_clockwise += FullCircleDegrees; // Приводим к диапазону [0;360) + + // Слишком маленькая дуга считается нулевой + if (delta_clockwise < MinArcDegrees) return; // Если включено выравнивание, приводим к квадрату по меньшей стороне if (aligned) @@ -283,13 +301,13 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d } // Вычисляем ключевые точки для построения сектора: - var in_arc_stop = GetPoint(rect, a, r); // конечная точка внутренней дуги (начальный угол) - var out_arc_start = GetPoint(rect, a, R); // начальная точка внешней дуги (начальный угол) - var out_arc_stop = GetPoint(rect, b, R); // конечная точка внешней дуги (конечный угол) - var in_arc_start = GetPoint(rect, b, r); // начальная точка внутренней дуги (конечный угол) + var out_arc_start = GetPoint(rect, start_angle, R); // начальная точка внешней дуги + var out_arc_stop = GetPoint(rect, stop_angle, R); // конечная точка внешней дуги + var in_arc_start = GetPoint(rect, start_angle, r); // начальная точка внутренней дуги + var in_arc_stop = GetPoint(rect, stop_angle, r); // конечная точка внутренней дуги // Определяем тип дуги (большая дуга если угол > 180°) - var arc_isout = d > 180.0; + var arc_isout = delta_clockwise > 180.0; // Вычисляем размеры эллипсов для внутренней и внешней дуг var in_arc_size = new Size(r * w / 2, r * h / 2); @@ -304,7 +322,7 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d else { // Начинаем с центра (если r = 0) или с точки на внутренней дуге - g.BeginFigure(r is 0d ? p0 : in_arc_stop, true, true); + g.BeginFigure(r is 0d ? p0 : in_arc_start, true, true); g.LineTo(out_arc_start, true, true); // Линия к началу внешней дуги } @@ -314,9 +332,9 @@ private static void DrawGeometry(StreamGeometryContext g, Rect rect, double R, d if (r is 0d || line_only) return; // Если внутренний радиус 0 или это линия, завершаем // Рисуем линию к началу внутренней дуги - g.LineTo(in_arc_start, true, true); + g.LineTo(in_arc_stop, true, true); // Рисуем внутреннюю дугу от конечного до начального угла против часовой стрелки - g.ArcTo(in_arc_stop, in_arc_size, 0, arc_isout, SweepDirection.Counterclockwise, true, true); + g.ArcTo(in_arc_start, in_arc_size, 0, arc_isout, SweepDirection.Counterclockwise, true, true); } } \ No newline at end of file diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml index 9e59cf5..fc01484 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml @@ -4,43 +4,20 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:MathCore.WPF.WindowTest.ViewModels" + xmlns:sh="clr-namespace:MathCore.WPF.Shapes;assembly=MathCore.WPF" + Title="{Binding Title}" Width="800" Height="450"> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + From 3efb6f5173b6b0e70703243983070240c06a8a99 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:16:58 +0300 Subject: [PATCH 08/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20README.md=20=D0=B4=D0=BB=D1=8F=20MathCore.WPF.Shapes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена структурированная документация на русском языке, описывающая все основные компоненты библиотеки: ShapeBase, Arc, Pie, Arrow, LineEx, LinePoint и PointLine. Включены описания, таблицы свойств, особенности, примеры использования в XAML и C#, рекомендации по выбору фигур и пример круговой диаграммы. README предназначен для быстрого ознакомления и практического применения расширенных WPF-фигур. --- MathCore.WPF/Shapes/README.md | 617 ++++++++++++++++++++++++++++++++++ 1 file changed, 617 insertions(+) create mode 100644 MathCore.WPF/Shapes/README.md diff --git a/MathCore.WPF/Shapes/README.md b/MathCore.WPF/Shapes/README.md new file mode 100644 index 0000000..6644f4b --- /dev/null +++ b/MathCore.WPF/Shapes/README.md @@ -0,0 +1,617 @@ +# MathCore.WPF.Shapes + +Модуль содержит набор расширенных WPF фигур и вспомогательных классов для работы с геометрическими фигурами в приложениях на Windows Presentation Foundation. + +## Содержание + +- [ShapeBase](#shapebase) — базовый класс для фигур +- [Arc](#arc) — дуга окружности +- [Pie](#pie) — сектор круга или кольца +- [Arrow](#arrow) — стрелка с настраиваемыми параметрами +- [LineEx](#lineex) — расширение для Line с поддержкой привязки к точкам +- [LinePoint](#linepoint) — вспомогательный класс для работы с точками линии +- [PointLine](#pointline) — линия с поддержкой точек + +--- + +## ShapeBase + +Абстрактный базовый класс для всех пользовательских фигур с поддержкой растягивания и измерения видимого прямоугольника. + +### Описание + +`ShapeBase` наследуется от `System.Windows.Shapes.Shape` и предоставляет: +- Вычисление видимого прямоугольника (`_VisibleRect`) с учётом толщины обводки +- Обработка различных режимов растягивания (`Stretch`) +- Переопределяемые методы для измерения и размещения фигуры + +### Основные методы + +#### `ArrangeOverride(Size)` + +Переопределённый метод, который: +- Вычисляет видимый прямоугольник с учётом толщины обводки +- Применяет режим растягивания (`Stretch.None`, `Stretch.Fill`, `Stretch.Uniform`, `Stretch.UniformToFill`) + +```csharp +protected override Size ArrangeOverride(Size FinalSize) +``` + +**Параметры:** +- `FinalSize` — конечный размер для расположения фигуры + +**Возвращаемое значение:** +- `Size` — размер после расположения + +#### `MeasureOverride(Size)` + +Переопределённый метод для измерения фигуры: + +```csharp +protected override Size MeasureOverride(Size ConstraintSize) +``` + +**Параметры:** +- `ConstraintSize` — размер ограничения для измерения + +--- + +## Arc + +Фигура WPF для рисования дуги окружности с поддержкой эллиптических дуг. + +### Описание + +`Arc` — это фигура, которая рисует дугу с заданными начальным и конечным углами. Дуга может быть части эллипса, благодаря независимым радиусам по осям X и Y (заложено в прямоугольнике отрисовки). + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **R** | `double` | `1.0` | Радиус дуги в относительных единицах от 0 до 1. Значение интерпретируется относительно размеров контейнера. При значении 1 дуга строится по максимальному доступному радиусу в пределах прямоугольника | +| **StartAngle** | `double` | `0.0` | Начальный угол дуги в градусах. Отсчёт ведётся по часовой стрелке, 0° направлен вправо, 90° вниз | +| **StopAngle** | `double` | `360.0` | Конечный угол дуги в градусах. После нормализации обоих углов дуга рисуется по часовой стрелке от StartAngle к StopAngle | + +### Примеры использования + +#### Полная окружность + +```xaml + + + + + +``` + +#### Полукруг + +```xaml + +``` + +#### Четверть окружности с внутренним радиусом + +```xaml + +``` + +#### С привязкой в коде + +```csharp +var arc = new Arc +{ + Width = 100, + Height = 100, + R = 1, + StartAngle = 0, + StopAngle = 270, + Stroke = Brushes.Purple, + StrokeThickness = 2 +}; + +// Привязка углов к свойствам ViewModel +var startBinding = new Binding("StartAngleValue"); +arc.SetBinding(Arc.StartAngleProperty, startBinding); + +var stopBinding = new Binding("StopAngleValue"); +arc.SetBinding(Arc.StopAngleProperty, stopBinding); +``` + +### Особенности + +- Дуга всегда рисуется **по часовой стрелке** от нормализованного `StartAngle` к нормализованному `StopAngle` +- Углы нормализуются к диапазону `[0°; 360°)` +- Значения радиуса `R` вне диапазона `[0; 1]` автоматически ограничиваются +- Очень малые дуги (менее `1e-6` градусов) игнорируются +- Окружность строится при `|StopAngle - StartAngle| >= 360 - MinArcDegrees` + +### Примеры углов + +``` +StartAngle=270, StopAngle=60 → дуга 150° по часовой (270→360→60) +StartAngle=60, StopAngle=270 → дуга 210° по часовой (60→270) +StartAngle=0, StopAngle=360 → полная окружность +StartAngle=90, StopAngle=90 → ничего не рисуется (дуга 0°) +``` + +--- + +## Pie + +Фигура для рисования сектора круга или кольца (сектора с внутренним радиусом). + +### Описание + +`Pie` — это фигура, которая рисует сектор с поддержкой как полного сектора (от центра), так и кольцевого сектора (с внутренним радиусом). Сектор может быть использован для создания круговых диаграмм, кольцевых диаграмм (доннатов) и других визуализаций. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **OuterRadius** | `double` | `1.0` | Внешний радиус сектора в относительных единицах от 0 до 1 | +| **InnerRadius** | `double` | `0.0` | Внутренний радиус сектора в относительных единицах от 0 до 1. При значении 0 сектор рисуется от центра (типичный сектор). При значении > 0 рисуется кольцевой сектор | +| **StartAngle** | `double` | `0.0` | Начальный угол сектора в градусах (отсчёт по часовой стрелке) | +| **StopAngle** | `double` | `360.0` | Конечный угол сектора в градусах | +| **Angle** | `double` | `360.0` | Угол раствора сектора в градусах (вычисляется как `StopAngle - StartAngle`) | +| **IsAligned** | `bool` | `false` | Если `true`, сектор выравнивается по меньшему размеру контейнера (квадрат) | + +### Примеры использования + +#### Сектор пиццы (45°) + +```xaml + +``` + +#### Кольцевой сектор (донат) + +```xaml + +``` + +#### Круговая диаграмма (элемент) + +```xaml + +``` + +#### С привязкой данных + +```csharp +var pie = new Pie +{ + Width = 200, + Height = 200, + Fill = Brushes.SkyBlue, + Stroke = Brushes.Navy, + StrokeThickness = 2, + OuterRadius = 1, + InnerRadius = 0.5 +}; + +// Привязка углов к свойствам ViewModel +var binding = new Binding("PieAngle"); +pie.SetBinding(Pie.AngleProperty, binding); + +var startBinding = new Binding("StartAngle"); +pie.SetBinding(Pie.StartAngleProperty, startBinding); +``` + +### Особенности + +- Сектор рисуется **по часовой стрелке** от нормализованного `StartAngle` к нормализованному `StopAngle` +- При `InnerRadius = 0` рисуется типичный сектор круга (от центра) +- При `InnerRadius > 0` рисуется кольцевой сектор (донат, сегмент кольца) +- Система автоматически гарантирует `OuterRadius >= InnerRadius` +- Очень малые углы игнорируются +- Свойство `Angle` связано с `StopAngle`: `StopAngle = StartAngle + Angle` + +### Пример использования в коде + +```csharp +// Создание круговой диаграммы с четырьмя сегментами +var colors = new[] { Brushes.Red, Brushes.Yellow, Brushes.Green, Brushes.Blue }; +var percentages = new[] { 25, 25, 30, 20 }; + +double startAngle = 0; +foreach (var percentage in percentages) +{ + var pie = new Pie + { + Width = 300, + Height = 300, + Fill = colors[Array.IndexOf(percentages, percentage)], + OuterRadius = 1, + InnerRadius = 0.3, // Кольцевая диаграмма + StartAngle = startAngle, + Angle = percentage * 3.6 // Преобразование процентов в градусы + }; + + canvas.Children.Add(pie); + startAngle += percentage * 3.6; +} +``` + +--- + +## Arrow + +Визуальный элемент стрелки с настраиваемыми параметрами линии и головы. + +### Описание + +`Arrow` — это фигура, которая рисует стрелку, состоящую из линии и треугольной головы. Поддерживает полную настройку координат, размеров головы, стиля линии и заливки. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **X1** | `double` | `0.0` | X-координата начальной точки линии стрелки | +| **Y1** | `double` | `0.0` | Y-координата начальной точки линии стрелки | +| **X2** | `double` | `0.0` | X-координата конечной точки линии стрелки | +| **Y2** | `double` | `0.0` | Y-координата конечной точки линии стрелки | +| **ArrowHeadWidth** | `double` | `10.0` | Ширина головы стрелки (ширина треугольника в основании) | +| **ArrowHeadLength** | `double` | `15.0` | Длина головы стрелки (высота треугольника) | +| **ArrowHeadOffset** | `double` | `0.0` | Отступ между концом линии и основанием головы стрелки | +| **IsArrowHeadClosed** | `bool` | `true` | Замкнут ли контур головы стрелки (заливка треугольника) | + +### Примеры использования + +#### Простая стрелка + +```xaml + +``` + +#### Горизонтальная стрелка с большой головой + +```xaml + +``` + +#### Открытая стрелка (без заливки головы) + +```xaml + +``` + +#### С привязкой данных + +```csharp +var arrow = new Arrow +{ + ArrowHeadWidth = 12, + ArrowHeadLength = 16, + ArrowHeadOffset = 2, + IsArrowHeadClosed = true, + Stroke = Brushes.Navy, + StrokeThickness = 2, + Fill = Brushes.SkyBlue +}; + +// Привязка координат к свойствам ViewModel +var x1Binding = new Binding("StartPoint.X"); +var y1Binding = new Binding("StartPoint.Y"); +var x2Binding = new Binding("EndPoint.X"); +var y2Binding = new Binding("EndPoint.Y"); + +arrow.SetBinding(Arrow.X1Property, x1Binding); +arrow.SetBinding(Arrow.Y1Property, y1Binding); +arrow.SetBinding(Arrow.X2Property, x2Binding); +arrow.SetBinding(Arrow.Y2Property, y2Binding); +``` + +### Особенности + +- Стрелка состоит из линии (от (X1, Y1) к конечной точке) и треугольной головы +- Глава автоматически ориентируется в направлении линии +- Параметры `ArrowHeadWidth`, `ArrowHeadLength`, `ArrowHeadOffset` не могут быть отрицательными +- При очень малой длине линии стрелка может не отрисоваться корректно +- `IsArrowHeadClosed = true` означает, что голова заливается указанным цветом (`Fill`) +- `IsArrowHeadClosed = false` означает, что голова рисуется только контуром + +--- + +## LineEx + +Статический вспомогательный класс с присоединяемыми свойствами для работы с точками линии (`System.Windows.Shapes.Line`). + +### Описание + +`LineEx` предоставляет присоединяемые свойства `P1` и `P2` для привязки начальной и конечной точек линии напрямую к моделям-представлениям (ViewModel). Обеспечивает автоматическую синхронизацию между точками и координатами `X1`, `Y1`, `X2`, `Y2`. + +### Основные присоединяемые свойства + +| Свойство | Тип | Описание | +|----------|-----|---------| +| **P1** | `Point` | Начальная точка линии. Привязывается к свойству ViewModel, заменяя необходимость в привязке координат X1/Y1 | +| **P2** | `Point` | Конечная точка линии. Привязывается к свойству ViewModel, заменяя необходимость в привязке координат X2/Y2 | + +### Примеры использования + +#### Привязка через точки (рекомендуется) + +```xaml + +``` + +#### Привязка отдельных координат (альтернатива) + +```xaml + +``` + +#### В коде + +```csharp +var line = new Line(); + +// Привязка через присоединяемые свойства +var p1Binding = new Binding("StartPoint"); +var p2Binding = new Binding("EndPoint"); + +LineEx.SetP1(line, p1Binding); +LineEx.SetP2(line, p2Binding); + +// Или установка значений напрямую +LineEx.SetP1(line, new Point(10, 10)); +LineEx.SetP2(line, new Point(100, 100)); +``` + +### Особенности + +- Использует `ConditionalWeakTable` для хранения вспомогательных объектов, предотвращая утечку памяти +- Автоматически синхронизирует точки с координатами при изменении через привязку +- При прямом изменении координат в коде точки могут не обновляться +- Оптимизирован для использования в шаблонах элементов (`ItemsControl`, `ListBox` и т.д.) + +--- + +## LinePoint + +Статический вспомогательный класс для работы с присоединяемыми свойствами для начальной и конечной точек линии с использованием слабых ссылок. + +### Описание + +`LinePoint` предоставляет альтернативный механизм присоединяемых свойств для синхронизации точек линии, использующий список слабых ссылок для отслеживания привязанных линий. + +### Основные методы + +- **IsStartAttached(Line)** — проверяет, привязана ли линия к начальной точке +- **IsEndAttached(Line)** — проверяет, привязана ли линия к конечной точке + +### Примечание + +Данный класс используется внутри фреймворка для отслеживания привязанных элементов и управления жизненным циклом привязок. Обычно разработчики используют `LineEx` для работы с точками линии. + +--- + +## PointLine + +Фигура WPF для рисования линии с поддержкой работы с точками (альтернатива стандартной `System.Windows.Shapes.Line`). + +### Описание + +`PointLine` — это расширение стандартной линии WPF с дополнительной поддержкой свойств `Start` и `End` для удобной работы с точками. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Start** | `Point` | `(0, 0)` | Начальная точка линии | +| **End** | `Point` | `(0, 0)` | Конечная точка линии | +| **X1** | `double` | `0.0` | X-координата начальной точки (связана со свойством `Start`) | +| **Y1** | `double` | `0.0` | Y-координата начальной точки (связана со свойством `Start`) | +| **X2** | `double` | `0.0` | X-координата конечной точки (связана со свойством `End`) | +| **Y2** | `double` | `0.0` | Y-координата конечной точки (связана со свойством `End`) | + +### Примеры использования + +#### Через точки + +```xaml + +``` + +#### Через координаты + +```xaml + +``` + +#### С привязкой в коде + +```csharp +var line = new PointLine +{ + Stroke = Brushes.Green, + StrokeThickness = 2 +}; + +var startBinding = new Binding("StartPoint"); +var endBinding = new Binding("EndPoint"); + +line.SetBinding(PointLine.StartProperty, startBinding); +line.SetBinding(PointLine.EndProperty, endBinding); +``` + +### Особенности + +- Koordinаты `X1`, `Y1`, `X2`, `Y2` автоматически синхронизируются с точками `Start` и `End` +- При изменении любой из координат, соответствующая точка обновляется +- Проще в использовании, чем стандартная `Line`, если вы работаете с точками + +--- + +## Рекомендации по использованию + +### Выбор между Arc, Pie и стандартной Ellipse + +| Сценарий | Рекомендуемая фигура | +|----------|---------------------| +| Рисование полной окружности | `Arc` с `StartAngle=0, StopAngle=360` или стандартная `Ellipse` | +| Рисование дуги | `Arc` | +| Рисование сектора круга | `Pie` с `InnerRadius=0` | +| Рисование кольцевого сектора (донат) | `Pie` с `InnerRadius > 0` | +| Круговая диаграмма | Несколько `Pie` с разными `StartAngle` и `Angle` | + +### Выбор между Line, LineEx и PointLine + +| Сценарий | Рекомендуемая фигура | +|----------|---------------------| +| Простая линия без привязки | Стандартная `Line` | +| Привязка к ViewModel через отдельные координаты | Стандартная `Line` с привязкой X1, Y1, X2, Y2 | +| Привязка к ViewModel через точки | `LineEx` с `P1` и `P2` | +| Работа с точками в коде | `PointLine` | + +### Производительность + +- `Arc`, `Pie` и `Arrow` используют `StreamGeometry` для оптимальной производительности +- Все фигуры замораживаются (`Freeze()`) после создания для улучшения производительности +- При необходимости рисования большого количества фигур рассмотрите использование `DrawingContext` напрямую + +--- + +## Пример: Создание круговой диаграммы + +```xaml + + + + + + + + + + + + + + + +``` + +--- + +## Примечания + +- Все фигуры наследуются от `System.Windows.Shapes.Shape` и поддерживают стандартные свойства WPF: `Stroke`, `StrokeThickness`, `Fill`, `Stretch` и т.д. +- Угловые свойства (`StartAngle`, `StopAngle`, `Angle`) работают в градусах +- Углы нормализуются внутри фигур, поэтому допускаются значения вне диапазона [0°; 360°) +- Все геометрии замораживаются после создания для оптимизации производительности From de9120f90d239e31bc1367931be9dc2f7e197aa5 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:20:57 +0300 Subject: [PATCH 09/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20README.md=20=D0=B4=D0=BB=D1=8F=20MathCore.WPF.Shaders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В проект добавлен README.md на русском языке с полным описанием всех доступных шейдерных эффектов, их свойств, примеров использования в XAML и C#, а также рекомендациями по производительности и аппаратным требованиям. Документация предназначена для быстрого старта и эффективной интеграции MathCore.WPF.Shaders в WPF-приложения. --- MathCore.WPF/Shaders/README.md | 747 +++++++++++++++++++++++++++++++++ 1 file changed, 747 insertions(+) create mode 100644 MathCore.WPF/Shaders/README.md diff --git a/MathCore.WPF/Shaders/README.md b/MathCore.WPF/Shaders/README.md new file mode 100644 index 0000000..c44e42e --- /dev/null +++ b/MathCore.WPF/Shaders/README.md @@ -0,0 +1,747 @@ +# MathCore.WPF.Shaders + +Модуль содержит набор пиксельных шейдеров для применения различных визуальных эффектов к элементам WPF. Все эффекты наследуются от `System.Windows.Media.Effects.ShaderEffect` и используют предскомпилированные пиксельные шейдеры (HLSL), встроенные в сборку. + +## Содержание + +- [Основные концепции](#основные-концепции) +- [Blur](#blur) — стандартное размытие +- [DirectionalBlur](#directionalblur) — направленное размытие +- [ZoomBlur](#zoomblur) — размытие из центра +- [GrayScale](#grayscale) — оттенки серого +- [BlackAndWhite](#blackandwhite) — чёрно-белое изображение +- [Invert](#invert) — инвертирование цветов +- [Sepia](#sepia) — эффект сепии +- [Opacity](#opacity) — контроль прозрачности +- [ColorAlphaKey](#coloralphakey) — установка прозрачного цвета +- [Рекомендации по использованию](#рекомендации-по-использованию) +- [Примеры](#примеры) + +--- + +## Основные концепции + +### Что такое ShaderEffect? + +`ShaderEffect` — это базовый класс для применения пиксельных шейдеров к элементам WPF. Шейдеры компилируются из HLSL кода (High-Level Shading Language) в промежуточное представление (*.ps файлы) и встраиваются в сборку как ресурсы. + +### Как работают эффекты? + +1. Все эффекты наследуются от `ShaderEffect` +2. Свойство `Input` получает исходное изображение (обычно устанавливается автоматически) +3. Дополнительные свойства контролируют параметры эффекта (например, `Factor`, `BlurAmount`) +4. Результат применяется к визуальному элементу в реальном времени + +### Свойство Input + +Все эффекты имеют свойство `Input` типа `Brush`, которое: +- Автоматически устанавливается системой WPF +- Представляет исходное изображение элемента +- Помечено атрибутом `[Browsable(false)]`, так как не предназначено для ручного установки + +### Производительность + +- Эффекты выполняются на графическом процессоре (GPU) +- Лучше всего работают на современных видеокартах +- Может быть снижение производительности на очень старых GPU +- Рекомендуется избегать применения сложных эффектов к большому количеству элементов одновременно + +--- + +## Blur + +Эффект стандартного размытия, размывающего изображение во всех направлениях одинаково. + +### Описание + +`Blur` — это эффект, который размывает изображение, делая его мягче и менее четким. Интенсивность размытия контролируется свойством `Factor`. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Factor** | `float` | `0.5f` | Интенсивность размытия (0.0 = без размытия, 1.0 = максимальное размытие) | + +### Примеры использования + +#### В XAML + +```xaml + + + + + + + + + +``` + +#### Анимация размытия + +```xaml + + + + + + + + + + + + + + + + +``` + +#### В коде + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +image.Effect = new Blur { Factor = 0.7f }; +canvas.Children.Add(image); + +// Анимация +var animation = new DoubleAnimation +{ + From = 0, + To = 1, + Duration = TimeSpan.FromSeconds(1), + AutoReverse = true +}; +image.Effect.BeginAnimation(Blur.FactorProperty, animation); +``` + +--- + +## DirectionalBlur + +Размытие в определённом направлении. Размывает изображение вдоль прямой линии. + +### Описание + +`DirectionalBlur` — это эффект, который размывает изображение в указанном направлении. Полезен для имитации движения или создания специальных визуальных эффектов. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Angle** | `double` | `0.0` | Направление размытия в градусах (0° вправо, 90° вниз) | +| **BlurAmount** | `double` | `0.1` | Интенсивность размытия (0.0 = без размытия, >0 = размутие) | + +### Примеры использования + +#### Горизонтальное размытие (имитация движения) + +```xaml + + + + + + + +``` + +#### Вертикальное размытие + +```xaml + + + + + +``` + +#### Диагональное размытие + +```xaml + + + + + +``` + +#### С вращением направления в коде + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +var blur = new DirectionalBlur { BlurAmount = 0.2 }; +image.Effect = blur; + +// Анимация вращения направления +var animation = new DoubleAnimation +{ + From = 0, + To = 360, + Duration = TimeSpan.FromSeconds(5), + RepeatBehavior = RepeatBehavior.Forever +}; +blur.BeginAnimation(DirectionalBlur.AngleProperty, animation); + +canvas.Children.Add(image); +``` + +--- + +## ZoomBlur + +Размытие, исходящее из центральной точки, создающее эффект приближения или отдаления камеры. + +### Описание + +`ZoomBlur` — это эффект, который создаёт иллюзию движения из указанной центральной точки во все стороны. Полезен для создания эффектов прыжков, взрывов или быстрого приближения. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Center** | `Point` | `(0.5, 0.5)` | Центр размытия в нормализованных координатах (0.0–1.0). (0.5, 0.5) = центр элемента | +| **BlurAmount** | `double` | `0.1` | Интенсивность размытия (0.0 = без размытия, >0 = размытие) | + +### Примеры использования + +#### По центру элемента + +```xaml + + + + + +``` + +#### По левому верхнему углу + +```xaml + + + + + +``` + +#### Из верхней части изображения + +```xaml + + + + + +``` + +#### С анимацией центра в коде + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +var zoomBlur = new ZoomBlur { BlurAmount = 0.2 }; +image.Effect = zoomBlur; + +// Анимация движения центра +var centerAnimation = new PointAnimation +{ + From = new Point(0.25, 0.25), + To = new Point(0.75, 0.75), + Duration = TimeSpan.FromSeconds(2), + AutoReverse = true, + RepeatBehavior = RepeatBehavior.Forever +}; +zoomBlur.BeginAnimation(ZoomBlur.CenterProperty, centerAnimation); + +canvas.Children.Add(image); +``` + +--- + +## GrayScale + +Преобразование изображения в оттенки серого (ч/б). + +### Описание + +`GrayScale` — это эффект, который преобразует цветное изображение в оттенки серого. Свойство `Factor` позволяет плавно переходить от цветного изображения к чёрно-белому. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Factor** | `float` | `1.0f` | Степень десатурации (0.0 = полностью цветной, 1.0 = полностью ч/б) | + +### Примеры использования + +#### Полностью чёрно-белое + +```xaml + + + + + +``` + +#### Частичная десатурация (50%) + +```xaml + + + + + +``` + +#### Переход в ч/б при наведении мыши + +```xaml + + + + + + + + + + + + + + + + + + + + + + + +``` + +#### В коде + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +image.Effect = new GrayScale { Factor = 0.75f }; +canvas.Children.Add(image); +``` + +--- + +## BlackAndWhite + +Преобразование в чёрное и белое (без оттенков серого). + +### Описание + +`BlackAndWhite` — это эффект, который преобразует изображение в чистое чёрно-белое изображение без промежуточных оттенков серого. Свойство `Factor` контролирует степень применения эффекта. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Factor** | `float` | `1.0f` | Степень применения эффекта (0.0 = оригинальное, 1.0 = полный ч/б) | + +### Примеры использования + +#### Полностью чёрно-белое + +```xaml + + + + + +``` + +#### Частичное применение (50%) + +```xaml + + + + + +``` + +--- + +## Invert + +Инвертирование всех цветов изображения. + +### Описание + +`Invert` — это эффект, который инвертирует все цвета изображения (чёрное становится белым, синее становится жёлтым и т.д.). Не имеет дополнительных параметров, так как эффект либо применяется, либо нет. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | + +### Примеры использования + +#### Инвертирование цветов + +```xaml + + + + + +``` + +#### С фильтром Invert для кнопки (негатив при нажатии) + +```xaml + +``` + +#### В коде + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +image.Effect = new Invert(); +canvas.Children.Add(image); +``` + +--- + +## Sepia + +Эффект сепии (коричневатый, винтажный тон). + +### Описание + +`Sepia` — это эффект, который придаёт изображению коричневато-жёлтый винтажный тон, часто используемый для имитации старых фотографий. Свойство `Factor` контролирует интенсивность эффекта. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Factor** | `float` | `0.5f` | Интенсивность сепии (0.0 = оригинальные цвета, 1.0 = полная сепия) | + +### Примеры использования + +#### Полная сепия + +```xaml + + + + + +``` + +#### Лёгкая сепия (50%) + +```xaml + + + + + +``` + +#### Винтажная галерея фотографий + +```xaml + + + + + + + + + + + +``` + +#### В коде с анимацией + +```csharp +var image = new Image { Source = new BitmapImage(new Uri("image.jpg", UriKind.Relative)) }; +var sepia = new Sepia { Factor = 0.3f }; +image.Effect = sepia; + +// Анимация усиления сепии +var animation = new DoubleAnimation +{ + From = 0, + To = 1, + Duration = TimeSpan.FromSeconds(1), + AutoReverse = true +}; +sepia.BeginAnimation(Sepia.FactorProperty, animation); + +canvas.Children.Add(image); +``` + +--- + +## Opacity + +Контроль прозрачности элемента через шейдер. + +### Описание + +`Opacity` — это эффект, который контролирует прозрачность изображения на уровне пиксельного шейдера. Отличается от стандартного свойства `Opacity` WPF тем, что применяется как эффект. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | +| **Factor** | `float` | `0.5f` | Степень видимости (0.0 = полностью прозрачный, 1.0 = полностью видимый) | + +### Примеры использования + +#### Полупрозрачное изображение + +```xaml + + + + + +``` + +#### Как маска (плавное исчезновение) + +```xaml + + + + + + + + + + + + + + + + +``` + +--- + +## ColorAlphaKey + +Установка прозрачного цвета (подобно "ключу цвета" в видеомонтаже). + +### Описание + +`ColorAlphaKey` — это эффект, который делает определённый цвет прозрачным (альфа-ключ). Полезен для создания масок и удаления фонов определённого цвета. + +### Основные свойства + +| Свойство | Тип | Значение по умолчанию | Описание | +|----------|-----|----------------------|---------| +| **Input** | `Brush` | автоматически | Исходное изображение (устанавливается системой WPF) | + +**Примечание:** Данный эффект не имеет свойства для выбора цвета в текущей реализации. Требуется расширение функциональности или использование напрямую через HLSL. + +### Примеры использования + +```xaml + + + + + +``` + +--- + +## Рекомендации по использованию + +### Выбор эффекта + +| Сценарий | Рекомендуемый эффект | +|----------|---------------------| +| Размытие всего изображения | `Blur` | +| Имитация движения в одном направлении | `DirectionalBlur` | +| Эффект приближения/удаления | `ZoomBlur` | +| Чёрно-белое изображение с переходом | `GrayScale` | +| Винтажная фотография | `Sepia` | +| Чистое чёрно-белое | `BlackAndWhite` | +| Негатив изображения | `Invert` | +| Контроль прозрачности через шейдер | `Opacity` | +| Удаление цветного фона | `ColorAlphaKey` | + +### Производительность + +- **Лучшее:** Одиночные эффекты на одном элементе +- **Хорошее:** 5-10 элементов с эффектами на экране +- **Избегать:** Применение эффектов к сотням элементов одновременно +- Рекомендуется отключать эффекты при сворачивании окна + +### Качество видеокарты + +- **Требуется:** DirectX 9 или выше +- **Оптимально:** Современные GPU (2010+) +- **Может быть медленным:** Интегрированная графика на старых компьютерах + +--- + +## Примеры + +### Пример 1: Интерактивная галерея с эффектами + +```xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### Пример 2: Анимированные эффекты в коде + +```csharp +public class ImageEffectViewModel +{ + public void ApplyBlurEffect(Image image, double duration) + { + var blur = new Blur { Factor = 0 }; + image.Effect = blur; + + var animation = new DoubleAnimation + { + From = 0, + To = 1, + Duration = TimeSpan.FromSeconds(duration), + AutoReverse = true, + RepeatBehavior = RepeatBehavior.Forever + }; + + blur.BeginAnimation(Blur.FactorProperty, animation); + } + + public void ApplyGrayScaleOnLoad(Image image) + { + image.Effect = new GrayScale { Factor = 1.0f }; + + // Переход к цвету при наведении (нужен код в XAML) + } + + public void ApplyVintageEffect(Image image) + { + image.Effect = new Sepia { Factor = 0.8f }; + } +} +``` + +### Пример 3: Комбинирование эффектов + +```xaml + + + + + + + + + + + + + + + + + + +``` + +--- + +## Примечания + +- Все эффекты выполняются на GPU, что делает их очень быстрыми +- Поддержка эффектов зависит от видеокарты и драйверов +- На некоторых системах эффекты могут быть недоступны; в этом случае элементы отрисовываются без эффектов +- Для максимальной производительности избегайте частого изменения свойств эффектов +- Используйте анимации вместо прямого изменения свойств в обработчиках событий From d23fea212fb804a1b15970f75a0f8ee2055dd4a3 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:29:41 +0300 Subject: [PATCH 10/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8=20=D0=B8=20README=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=88=D0=B5=D0=B9=D0=B4=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Расширена документация ко всем *.psh-файлам: добавлены подробные комментарии на русском языке с описанием параметров, назначения, алгоритмов и формул. Внесены технические уточнения (например, исправлен альфа-канал в 3D анаглифных шейдерах). Добавлен структурированный README.md с описанием всех шейдеров, их категорий, требований, формул и принципов работы. Изменения направлены на повышение прозрачности и удобства использования коллекции видеошейдеров MathCore.WPF. --- MathCore.WPF/Shaders/new/0-255 to 16-235.psh | 6 +- .../Shaders/new/16-235 to 0-255 [SD].psh | 8 +- .../Shaders/new/16-235 to 0-255 [SD][HD].psh | 6 +- .../Shaders/new/3D OAU GreenMagenta.psh | 5 +- MathCore.WPF/Shaders/new/3D OAU RedCyan.psh | 7 +- MathCore.WPF/Shaders/new/3D OAU to 2D.psh | 9 +- .../Shaders/new/3D SBS GreenMagenta.psh | 5 +- MathCore.WPF/Shaders/new/3D SBS RedCyan.psh | 6 +- MathCore.WPF/Shaders/new/3D SBS to 2D.psh | 9 +- MathCore.WPF/Shaders/new/BT.601 to BT.709.psh | 12 +- .../Shaders/new/LCD angle correction.psh | 16 +- MathCore.WPF/Shaders/new/Levels.psh | 8 +- MathCore.WPF/Shaders/new/Levels2.psh | 8 +- MathCore.WPF/Shaders/new/README.md | 631 ++++++++++++++++++ MathCore.WPF/Shaders/new/Sharpen_3x3.psh | 7 +- MathCore.WPF/Shaders/new/Sharpen_5x5.psh | 10 +- .../Shaders/new/YV12 Chroma Upsampling.psh | 7 + MathCore.WPF/Shaders/new/contour.psh | 4 +- .../Shaders/new/deinterlace (blend).psh | 6 +- MathCore.WPF/Shaders/new/denoise.psh | 7 +- MathCore.WPF/Shaders/new/edge sharpen.psh | 22 +- MathCore.WPF/Shaders/new/emboss.psh | 4 + MathCore.WPF/Shaders/new/grayscale.psh | 5 +- MathCore.WPF/Shaders/new/invert.psh | 4 +- MathCore.WPF/Shaders/new/letterbox.psh | 5 +- .../Shaders/new/sharpen complex 2.psh | 43 +- MathCore.WPF/Shaders/new/sharpen complex.psh | 30 +- MathCore.WPF/Shaders/new/sharpen flou.psh | 13 +- MathCore.WPF/Shaders/new/sharpen.psh | 5 +- 29 files changed, 819 insertions(+), 89 deletions(-) create mode 100644 MathCore.WPF/Shaders/new/README.md diff --git a/MathCore.WPF/Shaders/new/0-255 to 16-235.psh b/MathCore.WPF/Shaders/new/0-255 to 16-235.psh index c2cb379..f906936 100644 --- a/MathCore.WPF/Shaders/new/0-255 to 16-235.psh +++ b/MathCore.WPF/Shaders/new/0-255 to 16-235.psh @@ -1,3 +1,7 @@ +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование диапазона видеозначений с полного (0-255) на ограниченный (16-235) +// для видео стандарта BT.601/BT.709 + // $MinimumShaderProfile: ps_2_0 sampler s0 : register(s0); @@ -6,7 +10,7 @@ sampler s0 : register(s0); #define const_2 (219.0 / 255.0) float4 main(float2 tex : TEXCOORD0) : COLOR { - // original pixel + // исходный пиксель float4 c0 = tex2D(s0, tex); return (c0 * const_2) + const_1; diff --git a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh index c6a50fa..f1e1498 100644 --- a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh +++ b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD].psh @@ -1,4 +1,6 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование диапазона видеозначений с ограниченного (16-235) на полный (0-255) +// для видео стандарта SD (стандартной четкости), с проверкой разрешения sampler s0 : register(s0); float4 p0 : register(c0); @@ -10,10 +12,10 @@ float4 p0 : register(c0); #define const_2 (255.0 / 219.0) float4 main(float2 tex : TEXCOORD0) : COLOR { - // original pixel + // исходный пиксель float4 c0 = tex2D(s0, tex); - // ATI driver only looks at the height + // драйверы ATI проверяют только высоту if (height >= 720) { return c0; } else { diff --git a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh index 2f5fc67..b86e80e 100644 --- a/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh +++ b/MathCore.WPF/Shaders/new/16-235 to 0-255 [SD][HD].psh @@ -1,4 +1,6 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование диапазона видеозначений с ограниченного (16-235) на полный (0-255) +// для видео стандартов SD и HD sampler s0 : register(s0); @@ -6,7 +8,7 @@ sampler s0 : register(s0); #define const_2 (255.0 / 219.0) float4 main(float2 tex : TEXCOORD0) : COLOR { - // original pixel + // исходный пиксель float4 c0 = tex2D(s0, tex); return ((c0 - const_1) * const_2); diff --git a/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh b/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh index c344afd..e766b95 100644 --- a/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh +++ b/MathCore.WPF/Shaders/new/3D OAU GreenMagenta.psh @@ -1,4 +1,7 @@ -// 3D OverandUnder Green/Magenta +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео OverAndUnder (верхний кадр - зеленый, нижний - фиолетовый) +// в стереоскопическое цветное изображение зеленый-фиолетовый анаглиф +// Формула: красный из нижнего кадра (25% от RGB), зеленый из верхнего (70%), голубой из верхнего (70%) sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh b/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh index 536b421..c6e3fb2 100644 --- a/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh +++ b/MathCore.WPF/Shaders/new/3D OAU RedCyan.psh @@ -1,4 +1,7 @@ -// 3D OverandUnder Red/Cyan +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео OverAndUnder (верхний кадр - красный, нижний - голубой) +// в стереоскопическое цветное изображение красный-голубой анаглиф +// Формула: красный из верхнего кадра, голубой из нижнего sampler s0 : register(s0); float4 p0 : register(c0); @@ -20,5 +23,5 @@ float4 main(float2 tex : TEXCOORD0) : COLOR float green = r.g * 0.8; float blue = r.b; - return float4(red, green, blue, 9); + return float4(red, green, blue, 1); } \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D OAU to 2D.psh b/MathCore.WPF/Shaders/new/3D OAU to 2D.psh index ffd31ec..0e7510c 100644 --- a/MathCore.WPF/Shaders/new/3D OAU to 2D.psh +++ b/MathCore.WPF/Shaders/new/3D OAU to 2D.psh @@ -1,4 +1,6 @@ -// 3D OverandUnder to 2D +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео OverAndUnder (верхний и нижний кадры) в 2D изображение +// Функция: выбирает верхний кадр в качестве итогового результата sampler s0 : register(s0); float4 p0 : register(c0); @@ -11,11 +13,10 @@ float4 main(float2 tex : TEXCOORD0) : COLOR tex.y = tex.y / 2; float4 l = tex2D(s0, tex); - float4 r = tex2D(s0, tex); float red = l.r; - float green = r.g; - float blue = r.b; + float green = l.g; + float blue = l.b; return float4(red, green, blue, 1); } \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh b/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh index 029bd7f..6a06cb5 100644 --- a/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh +++ b/MathCore.WPF/Shaders/new/3D SBS GreenMagenta.psh @@ -1,4 +1,7 @@ -// 3D SidebySide Green/Magenta +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео SideBySide (левый и правый кадры рядом) +// в стереоскопическое цветное изображение зеленый-фиолетовый анаглиф +// Формула: красный из правого кадра (25% от RGB), зеленый из левого (70%), голубой из левого (70%) sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh b/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh index 74e5aec..36aaeb0 100644 --- a/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh +++ b/MathCore.WPF/Shaders/new/3D SBS RedCyan.psh @@ -1,4 +1,6 @@ -// 3D SidebySide Red/Cyan +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео SideBySide (левый и правый кадры в одном изображении, слева направо) +// в стереоскопическое цветное изображение красный-голубой анаглиф sampler s0 : register(s0); float4 p0 : register(c0); @@ -20,5 +22,5 @@ float4 main(float2 tex : TEXCOORD0) : COLOR float green = r.g * 0.8; float blue = r.b; - return float4(red, green, blue, 9); + return float4(red, green, blue, 1); } \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/3D SBS to 2D.psh b/MathCore.WPF/Shaders/new/3D SBS to 2D.psh index 8f0b8f2..e4482ca 100644 --- a/MathCore.WPF/Shaders/new/3D SBS to 2D.psh +++ b/MathCore.WPF/Shaders/new/3D SBS to 2D.psh @@ -1,4 +1,6 @@ -// 3D SidebySide to 2D +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: преобразование 3D видео SideBySide (левый и правый кадры рядом) в 2D изображение +// Функция: выбирает левый кадр в качестве итогового результата sampler s0 : register(s0); float4 p0 : register(c0); @@ -11,11 +13,10 @@ float4 main(float2 tex : TEXCOORD0) : COLOR tex.x = tex.x / 2; float4 l = tex2D(s0, tex); - float4 r = tex2D(s0, tex); float red = l.r; - float green = r.g; - float blue = r.b; + float green = l.g; + float blue = l.b; return float4(red, green, blue, 1); } \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh b/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh index f520eba..7e4f0bd 100644 --- a/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh +++ b/MathCore.WPF/Shaders/new/BT.601 to BT.709.psh @@ -1,4 +1,10 @@ -// $MinimumShaderProfile: ps_2_0 +// $MinimumShaderProfile: ps_2_0 + +// Параметры: c0.x, c0.y (разрешение видео в пикселях) +// Назначение: преобразование цветового пространства BT.601 [SD] в BT.709 [HD] +// Использование: для коррекции видео HD, закодированного с неправильным цветовым пространством SD +// Принцип: преобразование RGB -> Y'CbCr (BT.601) -> RGB (BT.709) +// Примечание: для видео SD (< 1120x630) никаких изменений не производится // (C) 2011 Jan-Willem Krans (janwillem32 hotmail.com) released under GPL v2; see COPYING.txt @@ -9,9 +15,9 @@ sampler s0; float2 c0; float4 main(float2 tex : TEXCOORD0) : COLOR { - float4 si = tex2D(s0, tex); // original pixel + float4 si = tex2D(s0, tex); // исходный пиксель if (c0.x < 1120 && c0.y < 630) { - return si; // this shader does not alter SD video + return si; // этот шейдер не влияет на видео SD } float3 s1 = si.rgb; s1 = s1.rrr * float3(0.299, -0.1495 / 0.886, 0.5) + s1.ggg * float3(0.587, -0.2935 / 0.886, -0.2935 / 0.701) + s1.bbb * float3(0.114, 0.5, -0.057 / 0.701); // RGB to Y'CbCr, BT.601 [SD] colorspace diff --git a/MathCore.WPF/Shaders/new/LCD angle correction.psh b/MathCore.WPF/Shaders/new/LCD angle correction.psh index f88f0d0..d06892e 100644 --- a/MathCore.WPF/Shaders/new/LCD angle correction.psh +++ b/MathCore.WPF/Shaders/new/LCD angle correction.psh @@ -1,4 +1,10 @@ -// $MinimumShaderProfile: ps_2_0 +// $MinimumShaderProfile: ps_2_0 + +// Параметры: регистры констант для коэффициентов яркости, контраста, гаммы +// Назначение: коррекция яркости, контраста и гаммы с линейным масштабированием сверху вниз +// Использование: для коррекции углов обзора ЖК-мониторов +// Требования: шейдер профиля PS 2.0, работает с линейным RGB +// Примечание: для обычного R'G'B' с видеогаммой необходимо сначала преобразовать в линейную форму // (C) 2011 Jan-Willem Krans (janwillem32 hotmail.com) released under GPL v2; see COPYING.txt @@ -38,12 +44,12 @@ sampler s0; float4 main(float2 tex : TEXCOORD0) : COLOR { float3 s1 = tex2D(s0, tex).rgb; - // original pixel + // исходный пиксель float texyi = 1.0 - tex.y; s1 = s1 * (texyi * float3(RedContrastTop, GreenContrastTop, BlueContrastTop) + tex.y * float3(RedContrastBottom, GreenContrastBottom, BlueContrastBottom)) + texyi * float3(RedBrightnessTop, GreenBrightnessTop, BlueBrightnessTop) + tex.y * float3(RedBrightnessBottom, GreenBrightnessBottom, BlueBrightnessBottom); - // process contrast and brightness on the original pixel - // preserve the sign bits of RGB values + // применение контраста и яркости к исходному пикселю + // сохранение битов знака значений RGB float3 sb = sign(s1); return (sb*pow(abs(s1), texyi * float3(RedGammaTop, GreenGammaTop, BlueGammaTop) + tex.y * float3(RedGammaBottom, GreenGammaBottom, BlueGammaBottom))).rgbb; - // process gamma correction and output + // применение коррекции гаммы и вывод результата } diff --git a/MathCore.WPF/Shaders/new/Levels.psh b/MathCore.WPF/Shaders/new/Levels.psh index 96fbc55..5fced07 100644 --- a/MathCore.WPF/Shaders/new/Levels.psh +++ b/MathCore.WPF/Shaders/new/Levels.psh @@ -1,5 +1,7 @@ -// Levels=ps_2_0 -// Code from MPC +// Параметры: входное изображение в диапазоне ограниченного видео (16-235) +// Назначение: преобразование видеозначений с ограниченного диапазона (16-235) на полный (0-255) +// Применение: для видеопроцессинга без проверки разрешения +// Код: из проекта MPC (Media Player Classic) sampler s0 : register(s0); @@ -8,7 +10,7 @@ sampler s0 : register(s0); float4 main(float2 tex : TEXCOORD0) : COLOR { - // original pixel + // исходный пиксель float4 c0 = tex2D(s0,tex); return((c0 - const_1) * const_2); diff --git a/MathCore.WPF/Shaders/new/Levels2.psh b/MathCore.WPF/Shaders/new/Levels2.psh index ff4fdbb..65eb6fc 100644 --- a/MathCore.WPF/Shaders/new/Levels2.psh +++ b/MathCore.WPF/Shaders/new/Levels2.psh @@ -1,5 +1,7 @@ -// Levels=ps_2_0 -// Code from MPC +// Параметры: height (высота изображения в пикселях) +// Назначение: преобразование видеозначений с ограниченного (16-235) на полный (0-255) диапазон +// Применение: с проверкой высоты для определения HD/SD видео +// Код: из проекта MPC (Media Player Classic) sampler s0 : register(s0); float4 p0 : register(c0); @@ -12,7 +14,7 @@ float4 p0 : register(c0); float4 main(float2 tex : TEXCOORD0) : COLOR { - // original pixel + // исходный пиксель float4 c0 = tex2D(s0,tex); if(height > 719 ) { diff --git a/MathCore.WPF/Shaders/new/README.md b/MathCore.WPF/Shaders/new/README.md new file mode 100644 index 0000000..25f688c --- /dev/null +++ b/MathCore.WPF/Shaders/new/README.md @@ -0,0 +1,631 @@ +# Коллекция видеошейдеров для DirectX 9 + +Данный каталог содержит коллекцию пиксельных шейдеров (*.psh) для видеообработки, используемых в проекте MathCore.WPF. Шейдеры предназначены для работы с видеопотоками и применения различных эффектов обработки. + +## Требования + +- **Минимальная версия DirectX:** 9.0 +- **Минимальный профиль пиксельного шейдера:** PS 2.0 (для некоторых шейдеров требуется PS 3.0) +- **Формат входных данных:** Обычно RGBA изображение в виде текстуры + +--- + +## Категория 1: Преобразование формата 3D видео + +Шейдеры для конвертации различных форматов трёхмерного видео в анаглифные изображения или 2D формат. + +### 1. **3D OAU RedCyan.psh** +Преобразование 3D видео Over-And-Under (OverAndUnder) в анаглиф красный-голубой + +**Назначение:** Конвертирует видео формата OverAndUnder (верхний кадр для левого глаза, нижний для правого) в стереоанаглифное изображение с цветовым разделением красный-голубой. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Верхняя половина изображения - левый кадр (красный канал) +- Нижняя половина изображения - правый кадр (голубой канал) +- Интенсивность зелёного канала ослабляется для оптимального восприятия + +--- + +### 2. **3D OAU GreenMagenta.psh** +Преобразование 3D видео Over-And-Under в анаглиф зелёный-фиолетовый + +**Назначение:** Конвертирует видео OverAndUnder в стереоанаглифное изображение с цветовым разделением зелёный-фиолетовый (зелёный-пурпурный). + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Верхний кадр (левый глаз) преобразуется в зелёный канал (коэффициент 0.7) +- Нижний кадр (правый глаз) преобразуется в красный (0.3) и голубой (0.7) каналы +- Формула красного: 0.3×R + 0.4×G + 0.1×B + +--- + +### 3. **3D OAU to 2D.psh** +Преобразование 3D видео Over-And-Under в 2D изображение + +**Назначение:** Конвертирует видео OverAndUnder в обычное 2D изображение, выбирая верхний (левый) кадр в качестве результата. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Изображение делится на две половины по вертикали +- Верхняя половина используется как итоговое изображение + +--- + +### 4. **3D SBS GreenMagenta.psh** +Преобразование 3D видео Side-By-Side в анаглиф зелёный-фиолетовый + +**Назначение:** Конвертирует видео SideBySide (левый и правый кадры расположены рядом) в анаглифное изображение зелёный-фиолетовый. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Левая половина - левый кадр → зелёный канал +- Правая половина - правый кадр → красный и голубой каналы +- Аналогично OAU GreenMagenta + +--- + +### 5. **3D SBS RedCyan.psh** +Преобразование 3D видео Side-By-Side в анаглиф красный-голубой + +**Назначение:** Конвертирует видео SideBySide в анаглифное изображение красный-голубой. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Левая половина → красный канал +- Правая половина → голубой канал (синий) +- Зелёный канал из обеих половин с коэффициентом 0.8 + +--- + +### 6. **3D SBS to 2D.psh** +Преобразование 3D видео Side-By-Side в 2D изображение + +**Назначение:** Конвертирует видео SideBySide в обычное 2D изображение, выбирая левый кадр. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +- Изображение делится на две половины по горизонтали +- Левая половина используется как итоговое изображение + +--- + +## Категория 2: Коррекция видео и цветового пространства + +Шейдеры для преобразования диапазонов значений цвета и цветовых пространств видео. + +### 7. **0-255 to 16-235.psh** +Преобразование диапазона 0-255 в 16-235 + +**Назначение:** Преобразует видеозначения с полного диапазона (0-255) на ограниченный диапазон видео (16-235), используемый в стандартах BT.601/BT.709. + +**Параметры:** Отсутствуют + +**Формула:** +``` +новый = исходный × (219/255) + (16/255) +``` + +--- + +### 8. **16-235 to 0-255 [SD].psh** +Преобразование диапазона 16-235 в 0-255 для видео SD + +**Назначение:** Преобразует видеозначения с ограниченного диапазона (16-235) на полный (0-255) с проверкой разрешения. Применяется к видео SD (стандартной четкости), видео HD проходит без изменений. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Логика:** +- Если высота ≥ 720 пикселей (HD) → без изменений +- Если высота < 720 пикселей (SD) → применяется формула преобразования + +**Формула:** +``` +новый = (исходный - 16/255) × (255/219) +``` + +--- + +### 9. **16-235 to 0-255 [SD][HD].psh** +Преобразование диапазона 16-235 в 0-255 для всех разрешений + +**Назначение:** Универсальное преобразование видеозначений с ограниченного диапазона на полный для видео любого разрешения. + +**Параметры:** Отсутствуют + +**Формула:** +``` +новый = (исходный - 16/255) × (255/219) +``` + +--- + +### 10. **Levels.psh** +Коррекция видеоуровней (Levels) + +**Назначение:** Преобразование видеозначений с ограниченного диапазона (16-235) на полный (0-255) без проверки разрешения. Универсальный инструмент для коррекции видеоуровней. + +**Параметры:** Отсутствуют + +**Формула:** +``` +новый = (исходный - 16/255) × (255/219) +``` + +**Примечание:** Основана на коде из проекта MPC (Media Player Classic) + +--- + +### 11. **Levels2.psh** +Коррекция видеоуровней с проверкой разрешения + +**Назначение:** Преобразование видеозначений с проверкой высоты для определения типа видео (HD/SD). Для HD видео (высота > 719) - без изменений, для SD применяется коррекция. + +**Параметры:** +- `height` (c0[1]) - высота видео в пикселях + +**Логика:** +- Если высота > 719 пикселей → без изменений (HD видео) +- Иначе → применяется преобразование диапазона (SD видео) + +--- + +### 12. **BT.601 to BT.709.psh** +Преобразование цветового пространства BT.601 в BT.709 + +**Назначение:** Корректирует цветовое пространство видео с BT.601 [SD] на BT.709 [HD]. Используется для видео HD, которое было неправильно закодировано с цветовым пространством SD. + +**Параметры:** +- `c0.x` - ширина видео в пикселях +- `c0.y` - высота видео в пикселях + +**Логика:** +- Если разрешение < 1120×630 пикселей → без изменений (видео SD не обрабатывается) +- Для HD видео применяется трёхэтапное преобразование: RGB → Y'CbCr (BT.601) → RGB (BT.709) + +**Принцип работы:** Использует матричные коэффициенты для преобразования между цветовыми пространствами + +--- + +### 13. **YV12 Chroma Upsampling.psh** +Коррекция апсемплинга хроминанса YV12 + +**Назначение:** Исправляет проблемы апсемплинга хроминанса (цвета) в видео формата YV12, когда аппаратное удвоение значений вызывает артефакты (блочные красные края на тёмных фонах). + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +1. Преобразование RGB в YUV +2. Интерполяция компонент U и V с соседними пикселями +3. Сохранение яркости (компонента Y) из центрального пикселя +4. Преобразование результата обратно в RGB + +--- + +## Категория 3: Обработка краев и резкость + +Шейдеры для повышения резкости, выявления краёв и создания специальных эффектов на основе контуров. + +### 14. **sharpen.psh** +Базовый фильтр резкости + +**Назначение:** Простой фильтр резкости на основе матрицы 3×3 соседних пикселей. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Коэффициенты:** +- Центр: 2.0 (усиление центрального пикселя) +- Соседи: -0.125 (ослабление периферийных пикселей) +- Эффект ширина: 1.6 пикселя + +**Матрица 3×3:** +``` +[-0.125, -0.125, -0.125] +[-0.125, 2.0, -0.125] +[-0.125, -0.125, -0.125] +``` + +--- + +### 15. **sharpen complex.psh** +Сложная резкость с детектированием краёв + +**Назначение:** Продвинутый фильтр резкости, использующий гаусово размытие для вычисления деталей и оператор Собеля для выявления краёв. + +**Параметры:** +- `dx` (c1[0]) - смещение по X для выборки соседних пикселей +- `dy` (c1[1]) - смещение по Y для выборки соседних пикселей + +**Принцип работы:** +1. Вычисляется гаусово размытие изображения (матрица 3×3) +2. Из оригинального изображения вычитается размытие для получения деталей +3. Применяется оператор Собеля для выявления краёв +4. На краях применяется усиленная резкость, на ровных областях - коррекция +5. Порог края для резкости: 0.3 + +--- + +### 16. **sharpen complex 2.psh** +Сложная резкость версии 2 с настраиваемыми параметрами + +**Назначение:** Улучшенная версия сложной резкости с возможностью настройки параметров размытия и резкости. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях +- `px` (c1[0]) - размер пикселя по X (1/width) +- `py` (c1[1]) - размер пикселя по Y (1/height) + +**Настраиваемые коэффициенты:** +- `mean` = 0.6 - радиус гауссова размытия +- `CoefBlur` = 2 - вес размытого изображения +- `CoefOrig` = 3 - вес оригинального изображения +- `SharpenEdge` = 0.2 - порог для выявления краёв +- `Sharpen_val0` = 2 - коэффициент резкости центра +- `Sharpen_val1` = 0.125 - коэффициент ослабления соседей + +**Требование:** PS 2.a или выше + +--- + +### 17. **sharpen flou.psh** +Резкость с размытием + +**Назначение:** Фильтр резкости, основанный на вычитании гауссова размытия из оригинального изображения. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях +- `one_over_width` (c1[0]) - предвычисленное значение 1/width +- `one_over_height` (c1[1]) - предвычисленное значение 1/height + +**Матрица гауссова фильтра 3×3:** +``` +[1, 2, 1] +[2, 4, 2] +[1, 2, 1] +``` +(нормализуется путём деления на 16) + +--- + +### 18. **Sharpen_3x3.psh** +Резкость матрица 3×3 с коэффициентами + +**Назначение:** Фильтр резкости матрица 3×3 с предустановленными коэффициентами для оптимального результата. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях +- `one_over_width` (c1[0]) - предвычисленное значение 1/width +- `one_over_height` (c1[1]) - предвычисленное значение 1/height + +**Коэффициенты матрицы:** +``` +[0.1, 0.15, 0.1] +[0.15, 2.0, 0.15] +[0.1, 0.15, 0.1] +``` + +**Параметр масштабирования:** x = 0.8 + +**Источник:** http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 + +--- + +### 19. **Sharpen_5x5.psh** +Резкость матрица 5×5 с многоуровневым взвешиванием + +**Назначение:** Продвинутый фильтр резкости, использующий матрицу 5×5 с различными коэффициентами на разных расстояниях от центра для плавной резкости. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях +- `one_over_width` (c1[0]) - предвычисленное значение 1/width +- `one_over_height` (c1[1]) - предвычисленное значение 1/height + +**Коэффициенты по расстояниям:** +- 2 пикселя: 0.025 (угловые) +- 1 пиксель диагональ: 0.05 +- 1 пиксель ось: 0.1 +- Центр: 2.0 + +**Требование:** PS 3.0 + +**Источник:** http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 + +--- + +### 20. **edge sharpen.psh** +Резкость на основе обнаружения краёв + +**Назначение:** Селективная резкость, которая применяется только на выявленных краях, оставляя ровные области без изменений. Использует оператор Прьюитта для выявления краёв. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Параметры фильтра:** +- `NbPixel` = 1 - размер окрестности в пикселях +- `Edge_threshold` = 0.2 - минимальная величина края для применения резкости +- `Sharpen_val0` = 2.0 - коэффициент усиления центра +- `Sharpen_val1` = 0.125 - коэффициент ослабления соседей + +**Принцип работы:** +1. Применяется оператор Прьюитта для выявления краёв +2. Если величина края > порога → применяется фильтр резкости +3. Иначе → пиксель остаётся без изменений + +--- + +### 21. **contour.psh** +Выявление контуров (Edge Detection) + +**Назначение:** Фильтр для выявления контуров изображения с использованием оператора Лапласа. Результат - белые линии на чёрном фоне на краях объектов. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Оператор Лапласа (крест 5 пикселей):** +``` +[ 0, -1, 0] +[ -1, 4, -1] +[ 0, -1, 0] +``` + +**Логика:** +- Если величина края < 1.0 → чёрный пиксель (0, 0, 0) +- Если величина края ≥ 1.0 → белый пиксель (1, 1, 1) + +--- + +### 22. **emboss.psh** +Эффект тиснения (Emboss) + +**Назначение:** Создаёт эффект трёхмерного тиснения на изображении с выявлением бороздок и выступов. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +1. Берутся диагональные пиксели: верхний-левый и нижний-правый +2. Вычисляется разница с коэффициентами для выявления бороздок +3. Результат усредняется по RGB каналам +4. Добавляется смещение +0.5 для получения серого фона + +--- + +## Категория 4: Фильтры размытия и шума + +Шейдеры для снижения шума, размытия и деинтерлейсинга видео. + +### 23. **deinterlace (blend).psh** +Удаление чересстрочности путём смешивания + +**Назначение:** Фильтр деинтерлейсинга, который удаляет артефакты чересстрочного видео путём усреднения пикселей соседних строк. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Принцип работы:** +``` +результат = (текущий × 2 + верхний + нижний) / 4 +``` + +**Использование:** Эффективно для видео с видимыми полосами чересстрочности + +--- + +### 24. **denoise.psh** +Снижение шума многократным размытием + +**Назначение:** Фильтр шумоподавления, использующий многократное гаусово размытие с растущим радиусом для эффективного удаления шума при сохранении деталей. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Параметры фильтра:** +- `val0` = 1.0 - вес центрального пикселя +- `val1` = 0.125 - вес соседних пикселей +- `effect_width` = 0.1 - начальный радиус размытия +- Итераций: 16 с растущим радиусом + +**Требование:** PS 3.0 + +**Принцип работы:** +1. Выполняется 16 итераций размытия с радиусом 0.1, 0.2, 0.3, ... 1.6 +2. На каждой итерации берутся 8 соседних пикселей +3. Результаты усредняются для получения финального гладкого изображения + +--- + +## Категория 5: Прочие фильтры + +### 25. **grayscale.psh** +Преобразование в оттенки серого + +**Назначение:** Конвертирует цветное изображение RGB в монохромное (оттенки серого) с использованием стандарта BT.601. + +**Параметры:** Отсутствуют + +**Коэффициенты:** +- Red: 0.299 +- Green: 0.587 +- Blue: 0.114 + +**Формула:** +``` +серый = R × 0.299 + G × 0.587 + B × 0.114 +``` + +**Примечание:** Коэффициенты отражают чувствительность человеческого глаза к разным цветам (глаз чувствительнее к зелёному, менее к синему) + +--- + +### 26. **invert.psh** +Инверсия цветов + +**Назначение:** Создаёт эффект фотонегатива путём инверсии всех цветов изображения. + +**Параметры:** Отсутствуют + +**Формула:** +``` +новый = (1, 1, 1, 1) - исходный +``` + +**Принцип работы:** Каждый канал RGB преобразуется как: новый = 1 - исходный + +--- + +### 27. **letterbox.psh** +Добавление чёрных полос для соотношения сторон 16:9 + +**Назначение:** Добавляет чёрные полосы сверху и снизу изображения для приведения его к стандартному кинематографическому соотношению сторон 16:9. + +**Параметры:** +- `width` (c0[0]) - ширина видео в пикселях +- `height` (c0[1]) - высота видео в пикселях + +**Алгоритм:** +1. Вычисляется необходимая высота чёрных полос на основе текущего разрешения +2. Контент выводится только в центральной области +3. Верхняя и нижняя части заполняются чёрным цветом (0, 0, 0) + +--- + +### 28. **LCD angle correction.psh** +Коррекция яркости, контраста и гаммы для ЖК-мониторов + +**Назначение:** Корректирует яркость, контраст и гамму изображения с линейным масштабированием от верхней части экрана к нижней для коррекции углов обзора ЖК-мониторов. + +**Параметры:** Отсутствуют (используются макросы-константы) + +**Настраиваемые коэффициенты:** +- `RedBrightnessTop`, `GreenBrightnessTop`, `BlueBrightnessTop` (по умолчанию 0) +- `RedBrightnessBottom`, `GreenBrightnessBottom`, `BlueBrightnessBottom` (по умолчанию 0) +- `RedContrastTop`, `GreenContrastTop`, `BlueContrastTop` (по умолчанию 1) +- `RedContrastBottom`, `GreenContrastBottom`, `BlueContrastBottom` (по умолчанию 1) +- `RedGammaTop`, `GreenGammaTop`, `BlueGammaTop` (по умолчанию 0.8) +- `RedGammaBottom`, `GreenGammaBottom`, `BlueGammaBottom` (по умолчанию 1) + +**Диапазоны:** +- Яркость: [-10, 10], по умолчанию 0 +- Контраст: [0, 10], по умолчанию 1 +- Гамма: (0, 10], по умолчанию 1 + +**Принцип работы:** +1. Значение texY интерполируется линейно между верхними (texY=1) и нижними (texY=0) коэффициентами +2. Применяется коррекция контраста и яркости +3. Применяется коррекция гаммы с сохранением знака значений + +**Требование:** Работает с линейным RGB (не с гамма-кодированным R'G'B') + +**Автор:** Jan-Willem Krans (janwillem32@hotmail.com), GPL v2 + +--- + +## Таблица требований по версиям PS + +| Шейдер | Требуемый PS | Тип | +|--------|------------|------| +| 0-255 to 16-235.psh | PS 2.0 | Коррекция | +| 16-235 to 0-255 [SD].psh | PS 2.0 | Коррекция | +| 16-235 to 0-255 [SD][HD].psh | PS 2.0 | Коррекция | +| 3D OAU RedCyan.psh | PS 2.0 | 3D | +| 3D OAU GreenMagenta.psh | PS 2.0 | 3D | +| 3D OAU to 2D.psh | PS 2.0 | 3D | +| 3D SBS RedCyan.psh | PS 2.0 | 3D | +| 3D SBS GreenMagenta.psh | PS 2.0 | 3D | +| 3D SBS to 2D.psh | PS 2.0 | 3D | +| BT.601 to BT.709.psh | PS 2.0 | Цветовое пространство | +| YV12 Chroma Upsampling.psh | PS 2.0 | Коррекция | +| contour.psh | PS 2.0 | Края | +| deinterlace (blend).psh | PS 2.0 | Фильтр | +| emboss.psh | PS 2.0 | Края | +| grayscale.psh | PS 2.0 | Цвет | +| invert.psh | PS 2.0 | Цвет | +| letterbox.psh | PS 2.0 | Формат | +| Levels.psh | PS 2.0 | Коррекция | +| Levels2.psh | PS 2.0 | Коррекция | +| LCD angle correction.psh | PS 2.0 | Коррекция | +| sharpen.psh | PS 2.0 | Резкость | +| sharpen complex.psh | PS 2.0 | Резкость | +| sharpen complex 2.psh | **PS 2.a** | Резкость | +| sharpen flou.psh | PS 2.0 | Резкость | +| edge sharpen.psh | PS 2.0 | Резкость | +| Sharpen_3x3.psh | PS 2.0 | Резкость | +| Sharpen_5x5.psh | **PS 3.0** | Резкость | +| denoise.psh | **PS 3.0** | Фильтр | + +--- + +## Примечания по использованию + +### Параметры регистров констант + +В DirectX 9 параметры передаются через регистры констант: +- **c0** - основные параметры (width, height, counter, clock) +- **c1** - дополнительные параметры (смещения, масштабы) + +### Форматы входных данных + +- **TEXCOORD0** - координаты текстуры (0.0 - 1.0) +- **s0** - основной сэмплер текстуры +- **COLOR** - выходной цвет (RGBA) + +### Оптимизация производительности + +- Шейдеры PS 2.0 совместимы с большинством видеокарт +- Шейдеры PS 3.0 требуют более новое оборудование +- Для реального времени рекомендуется использовать PS 2.0 + +--- + +## История и источники + +- Некоторые шейдеры разработаны на основе кода проекта **MPC (Media Player Classic)** +- Шейдеры для коррекции цветового пространства основаны на работах **Jan-Willem Krans** +- Шейдеры резкости адаптированы из примеров на **homecinema-fr.com** + +--- + +## Лицензирование + +Шейдеры, помеченные авторством Jan-Willem Krans, распространяются под лицензией **GPL v2**. + +Остальные шейдеры являются частью проекта MathCore.WPF и подпадают под его лицензионные условия. + +--- + +**Версия документации:** 1.0 +**Последнее обновление:** 2024 +**Язык:** Русский diff --git a/MathCore.WPF/Shaders/new/Sharpen_3x3.psh b/MathCore.WPF/Shaders/new/Sharpen_3x3.psh index 00d30f8..3381827 100644 --- a/MathCore.WPF/Shaders/new/Sharpen_3x3.psh +++ b/MathCore.WPF/Shaders/new/Sharpen_3x3.psh @@ -1,5 +1,8 @@ -// Sharpen_3x3=ps_2_0 -// http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 +// Параметры: width, height (размеры в пикселях), +// one_over_width, one_over_height (предвычисленные 1/width и 1/height) +// Назначение: фильтр резкости 3x3 с настраиваемым коэффициентом шкалирования +// Метод: центральный пиксель умножается на 2.0, соседние на 0.1-0.15 +// Источник: http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/Sharpen_5x5.psh b/MathCore.WPF/Shaders/new/Sharpen_5x5.psh index 6807b61..1b6fc19 100644 --- a/MathCore.WPF/Shaders/new/Sharpen_5x5.psh +++ b/MathCore.WPF/Shaders/new/Sharpen_5x5.psh @@ -1,5 +1,9 @@ -// Sharpen_5x5=ps_3_0 -// http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 +// Параметры: width, height (размеры в пикселях), +// one_over_width, one_over_height (предвычисленные 1/width и 1/height) +// Назначение: фильтр резкости 5x5 с многоуровневым взвешиванием для лучшей резкости +// Метод: использует коэффициенты от 0.025 до 2.0 на разных расстояниях от центра +// Требование: шейдер профиля PS 3.0 +// Источник: http://www.homecinema-fr.com/forum/viewtopic.php?t=29814317 sampler s0 : register(s0); @@ -61,4 +65,4 @@ float4 main(float2 tex : TEXCOORD0) : COLOR c0 -= c1; return c0; -} \ No newline at end of file +} \ No newline at end of file diff --git a/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh b/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh index 9314303..8f709af 100644 --- a/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh +++ b/MathCore.WPF/Shaders/new/YV12 Chroma Upsampling.psh @@ -1,3 +1,10 @@ +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: коррекция хроминанса YV12 (апсемплинг) с интерполяцией вместо удвоения значений +// Использование: для видео в формате YV12, где аппаратное удвоение значений вызывает артефакты +// Проблема: блочные красные края на темных фонах из-за неправильного апсемплинга +// Метод: преобразование в YUV, интерполяция U и V компонент, преобразование обратно в RGB +// Автор: Kurt Bernhard 'Leak' Pruenner + // $MinimumShaderProfile: ps_2_0 /* diff --git a/MathCore.WPF/Shaders/new/contour.psh b/MathCore.WPF/Shaders/new/contour.psh index f26449d..c53f0a8 100644 --- a/MathCore.WPF/Shaders/new/contour.psh +++ b/MathCore.WPF/Shaders/new/contour.psh @@ -1,4 +1,6 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: детектирование контуров (edge detection) с использованием оператора Лапласа +// Результат: белые линии на черном фоне там, где обнаружены края sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/deinterlace (blend).psh b/MathCore.WPF/Shaders/new/deinterlace (blend).psh index 338fb12..9813c5f 100644 --- a/MathCore.WPF/Shaders/new/deinterlace (blend).psh +++ b/MathCore.WPF/Shaders/new/deinterlace (blend).psh @@ -1,4 +1,8 @@ -// $MinimumShaderProfile: ps_2_0 +// $MinimumShaderProfile: ps_2_0 + +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: удаление чересстрочности (деинтерлейсинг) с использованием усреднения +// Метод: смешивание пикселя с соседними строками для сглаживания артефактов чересстрочности sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/denoise.psh b/MathCore.WPF/Shaders/new/denoise.psh index 537b7cf..1301444 100644 --- a/MathCore.WPF/Shaders/new/denoise.psh +++ b/MathCore.WPF/Shaders/new/denoise.psh @@ -1,4 +1,9 @@ -// $MinimumShaderProfile: ps_3_0 +// $MinimumShaderProfile: ps_3_0 + +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: снижение шума с использованием многократного гаусова размытия +// Метод: размытие с постепенно растущим радиусом (16 итераций) для эффективного снижения шума +// Требования: шейдер профиля PS 3.0 или выше sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/edge sharpen.psh b/MathCore.WPF/Shaders/new/edge sharpen.psh index ee46478..9e3b4c8 100644 --- a/MathCore.WPF/Shaders/new/edge sharpen.psh +++ b/MathCore.WPF/Shaders/new/edge sharpen.psh @@ -1,4 +1,10 @@ -// $MinimumShaderProfile: ps_2_0 +// $MinimumShaderProfile: ps_2_0 + +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: резкость только на краях (edge-aware sharpening) +// Метод: обнаружение краев оператором Prewitt, затем применение фильтра резкости только на них +// Пороги: Edge_threshold = 0.2 (минимальная величина края для применения резкости) +// Коэффициент резкости: val0 = 2.0 (усиление центра), val1 = 0.125 (ослабление соседей) sampler s0 : register(s0); float4 p0 : register(c0); @@ -12,13 +18,13 @@ float4 p0 : register(c0); #define Sharpen_val1 0.125 float4 main(float2 tex : TEXCOORD0) : COLOR { - // size of NbPixel pixels + // размер окрестности из NbPixel пикселей float dx = NbPixel / width; float dy = NbPixel / height; float4 Res = 0; - // Edge detection using Prewitt operator - // Get neighbor points + // Детектирование краев оператором Prewitt + // Матрица соседних пикселей: // [ 1, 2, 3 ] // [ 4, 0, 5 ] // [ 6, 7, 8 ] @@ -32,19 +38,19 @@ float4 main(float2 tex : TEXCOORD0) : COLOR { float4 c7 = tex2D(s0, tex + float2( 0, dy)); float4 c8 = tex2D(s0, tex + float2( dx, dy)); - // Computation of the 3 derived vectors (hor, vert, diag1, diag2) + // Вычисление 3 производных векторов (горизонтальная, вертикальная, диагональные) float4 delta1 = (c6 + c4 + c1 - c3 - c5 - c8); float4 delta2 = (c4 + c1 + c2 - c5 - c8 - c7); float4 delta3 = (c1 + c2 + c3 - c8 - c7 - c6); float4 delta4 = (c2 + c3 + c5 - c7 - c6 - c4); - // Computation of the Prewitt operator + // Вычисление оператора Prewitt float value = length(abs(delta1) + abs(delta2) + abs(delta3) + abs(delta4)) / 6; - // If we have an edge (vector length > Edge_threshold) => apply sharpen filter + // Если обнаружен край (длина вектора > Edge_threshold) => применяем фильтр резкости if (value > Edge_threshold) { Res = c0 * Sharpen_val0 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * Sharpen_val1; - // Display edges in red... + // Раскомментируйте для отображения краев красным цветом: //Res = float4( 1.0, 0.0, 0.0, 0.0 ); return Res; diff --git a/MathCore.WPF/Shaders/new/emboss.psh b/MathCore.WPF/Shaders/new/emboss.psh index 59b3685..aca85f5 100644 --- a/MathCore.WPF/Shaders/new/emboss.psh +++ b/MathCore.WPF/Shaders/new/emboss.psh @@ -1,3 +1,7 @@ +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: создание эффекта "тиснения" (emboss effect) +// Метод: линейное сочетание диагональных пикселей с сдвигом в центр и добавлением смещения 0.5 + // $MinimumShaderProfile: ps_2_0 sampler s0 : register(s0); diff --git a/MathCore.WPF/Shaders/new/grayscale.psh b/MathCore.WPF/Shaders/new/grayscale.psh index 617eecb..6177806 100644 --- a/MathCore.WPF/Shaders/new/grayscale.psh +++ b/MathCore.WPF/Shaders/new/grayscale.psh @@ -1,4 +1,7 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: входной пиксель RGB +// Назначение: преобразование цветного изображения в оттенки серого (grayscale conversion) +// Формула: скалярное произведение RGB с весовыми коэффициентами стандарта BT.601 +// Коэффициенты: R=0.299, G=0.587, B=0.114 (отражают чувствительность человеческого глаза) sampler s0 : register(s0); diff --git a/MathCore.WPF/Shaders/new/invert.psh b/MathCore.WPF/Shaders/new/invert.psh index b3d607e..79d4809 100644 --- a/MathCore.WPF/Shaders/new/invert.psh +++ b/MathCore.WPF/Shaders/new/invert.psh @@ -1,4 +1,6 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: входное изображение RGB +// Назначение: инверсия цветов изображения (отрицатив) +// Формула: новый пиксель = (1, 1, 1, 1) - исходный пиксель sampler s0 : register(s0); diff --git a/MathCore.WPF/Shaders/new/letterbox.psh b/MathCore.WPF/Shaders/new/letterbox.psh index b643f81..a865b7d 100644 --- a/MathCore.WPF/Shaders/new/letterbox.psh +++ b/MathCore.WPF/Shaders/new/letterbox.psh @@ -1,4 +1,7 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: добавление черных полос сверху и снизу для соответствия формату 16:9 +// Метод: вычисление высоты полос в зависимости от текущего разрешения видео +// Формат: автоматическое центрирование контента с соотношением сторон 16:9 sampler s0 : register(s0); float4 p0 : register(c0); diff --git a/MathCore.WPF/Shaders/new/sharpen complex 2.psh b/MathCore.WPF/Shaders/new/sharpen complex 2.psh index 82c2e42..b258f10 100644 --- a/MathCore.WPF/Shaders/new/sharpen complex 2.psh +++ b/MathCore.WPF/Shaders/new/sharpen complex 2.psh @@ -1,4 +1,11 @@ -// $MinimumShaderProfile: ps_2_a +// $MinimumShaderProfile: ps_2_a + +// Параметры: width, height (размеры изображения в пикселях), +// px, py (размер пикселя для вычисления смещений выборки) +// Назначение: сложная резкость версии 2 с параметрами размытия и резкости +// Метод: гаусово размытие, вычитание размытия, детектирование краев Sobel, условная резкость +// Требование: шейдер профиля PS 2.a (требует более высокого уровня поддержки, чем PS 2.0) +// Параметры: mean=0.6 (радиус размытия), CoefBlur=2 (вес размытия), SharpenEdge=0.2 (порог края) /* Sharpen complex v2 (requires ps >= 2a) */ @@ -9,13 +16,13 @@ float4 p1 : register(c1); #define width (p0[0]) #define height (p0[1]) -// pixel "width" +// размер пикселя в текстурных координатах #define px (p1[0]) #define py (p1[1]) -/* Parameters */ +/* Параметры */ -// for the blur filter +// для фильтра размытия #define mean 0.6 #define dx (mean * px) #define dy (mean * py) @@ -23,16 +30,16 @@ float4 p1 : register(c1); #define CoefBlur 2 #define CoefOrig (1 + CoefBlur) -// for the sharpen filter +// для фильтра резкости #define SharpenEdge 0.2 #define Sharpen_val0 2 #define Sharpen_val1 ((Sharpen_val0 - 1) / 8.0) float4 main(float2 tex : TEXCOORD0) : COLOR { - // get original pixel + // получение исходного пикселя float4 orig = tex2D(s0, tex); - // compute blurred image (gaussian filter) + // вычисление размытого изображения (гаусов фильтр) float4 c1 = tex2D(s0, tex + float2(-dx, -dy)); float4 c2 = tex2D(s0, tex + float2( 0, -dy)); float4 c3 = tex2D(s0, tex + float2( dx, -dy)); @@ -42,19 +49,19 @@ float4 main(float2 tex : TEXCOORD0) : COLOR { float4 c7 = tex2D(s0, tex + float2( 0, dy)); float4 c8 = tex2D(s0, tex + float2( dx, dy)); - // gaussian filter + // гаусов фильтр // [ 1, 2, 1 ] // [ 2, 4, 2 ] // [ 1, 2, 1 ] - // to normalize the values, we need to divide by the coeff sum + // для нормализации значений делим на сумму коэффициентов // 1 / (1+2+1+2+4+2+1+2+1) = 1 / 16 = 0.0625 float4 flou = (c1 + c3 + c6 + c8 + 2 * (c2 + c4 + c5 + c7) + 4 * orig) * 0.0625; - // substract blurred image from original image + // вычитание размытого изображения из оригинального float4 corrected = CoefOrig * orig - CoefBlur * flou; - // edge detection - // Get neighbor points + // детектирование краев + // Матрица соседних пикселей // [ c1, c2, c3 ] // [ c4, orig, c5 ] // [ c6, c7, c8 ] @@ -67,26 +74,26 @@ float4 main(float2 tex : TEXCOORD0) : COLOR { c7 = tex2D(s0, tex + float2( 0, py)); c8 = tex2D(s0, tex + float2( px, py)); - // using Sobel filter - // horizontal gradient + // использование фильтра Sobel + // горизонтальный градиент // [ -1, 0, 1 ] // [ -2, 0, 2 ] // [ -1, 0, 1 ] float delta1 = (c3 + 2 * c5 + c8) - (c1 + 2 * c4 + c6); - // vertical gradient + // вертикальный градиент // [ -1, - 2, -1 ] // [ 0, 0, 0 ] // [ 1, 2, 1 ] float delta2 = (c6 + 2 * c7 + c8) - (c1 + 2 * c2 + c3); - // computation + // вычисление if (sqrt(mul(delta1, delta1) + mul(delta2, delta2)) > SharpenEdge) { - // if we have an edge, use sharpen + // если обнаружен край, применяем резкость //return float4(1,0,0,0); return orig * Sharpen_val0 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * Sharpen_val1; } else { - // else return corrected image + // иначе возвращаем скорректированное изображение return corrected; } } diff --git a/MathCore.WPF/Shaders/new/sharpen complex.psh b/MathCore.WPF/Shaders/new/sharpen complex.psh index 82a7947..a55d06d 100644 --- a/MathCore.WPF/Shaders/new/sharpen complex.psh +++ b/MathCore.WPF/Shaders/new/sharpen complex.psh @@ -1,4 +1,8 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: сложная резкость с детектированием краев (Sobel) и размытием (Gaussian) +// Метод: 1) вычисление размытого изображения, 2) вычитание размытия из оригинала, +// 3) обнаружение краев, 4) применение резкости только на краях +// Требование: шейдер профиля PS 2.0 или выше sampler s0 : register(s0); float4 p1 : register(c1); @@ -7,13 +11,13 @@ float4 p1 : register(c1); #define dy (p1[1]) float4 main(float2 tex : TEXCOORD0) : COLOR { - // Pixels definition: original, blurred, corrected, final + // Определения пикселей: исходный, размытый, скорректированный, финальный float4 orig; float4 blurred; float4 corrected; float4 final; - // Get neighbor points + // Матрица соседних пикселей // [ 1, 2, 3 ] // [ 4, orig, 5 ] // [ 6, 7, 8 ] @@ -28,37 +32,37 @@ float4 main(float2 tex : TEXCOORD0) : COLOR { float4 c7 = tex2D(s0, tex + float2( 0, dy)); float4 c8 = tex2D(s0, tex + float2( dx, dy)); - // Computation of the blurred image (gaussian filter) - // to normalize the values, we need to divide by the coeff sum + // Вычисление размытого изображения (гаусов фильтр) + // для нормализации значений делим на сумму коэффициентов // 1/(1+2+1+2+4+2+1+2+1) = 1/16 = 0.0625 blurred = (c1 + c3 + c6 + c8 + 2 * (c2 + c4 + c5 + c7) + 4 * orig) * 0.0625; - // substract blurred image from original image + // Вычитание размытого изображения из оригинального corrected = 2 * orig - blurred; - // edge detection + // Детектирование краев float delta1; float delta2; float value; - // using Sobel filter - // horizontal gradient + // Использование фильтра Sobel + // горизонтальный градиент // [ -1, 0, 1 ] // [ -2, 0, 2 ] // [ -1, 0, 1 ] delta1 = (c3 + 2 * c5 + c8) - (c1 + 2 * c4 + c6); - // vertical gradient + // вертикальный градиент // [ -1, -2, -1 ] // [ 0, 0, 0 ] // [ 1, 2, 1 ] delta2 = (c6 + 2 * c7 + c8) - (c1 + 2 * c2 + c3); - // computation + // вычисление value = sqrt(mul(delta1, delta1) + mul(delta2, delta2)); if (value > 0.3) { - // if we have an edge, use sharpen + // если обнаружен край, применяем резкость #define Sharpen_val0 2.0 #define Sharpen_val1 0.125 final = orig * 2 - (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8) * 0.125; @@ -66,6 +70,6 @@ float4 main(float2 tex : TEXCOORD0) : COLOR { return final; } - // else return corrected image + // иначе возвращаем скорректированное изображение return corrected; } diff --git a/MathCore.WPF/Shaders/new/sharpen flou.psh b/MathCore.WPF/Shaders/new/sharpen flou.psh index 0c96bfa..bd65739 100644 --- a/MathCore.WPF/Shaders/new/sharpen flou.psh +++ b/MathCore.WPF/Shaders/new/sharpen flou.psh @@ -1,4 +1,9 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях), +// one_over_width, one_over_height (предвычисленные 1/width и 1/height) +// Назначение: резкость с размытием (sharpen with blur) +// Метод: вычисление гауссова размытия в матрице 3x3, вычитание его из оригинального пикселя +// Требование: шейдер профиля PS 2.0 + sampler s0 : register(s0); float4 p0 : register(c0); float4 p1 : register(c1); @@ -17,7 +22,7 @@ float4 main( float2 tex : TEXCOORD0 ) : COLOR float dx = one_over_width; float dy = one_over_height; -// recupperation de la matrice de 9 points +// получение матрицы из 9 пикселей // [ 1, 2 , 3 ] // [ 4,ori, 5 ] // [ 6, 7 , 8 ] @@ -32,7 +37,7 @@ float dy = one_over_height; float4 c7 = tex2D(s0, tex + float2(0,dy)); float4 c8 = tex2D(s0, tex + float2(dx,dy)); -// calcul image floue (filtre gaussien) +// вычисление размытого изображения (гаусов фильтр) float multipliers[9]= {1,2,1, 2,4,2, @@ -52,7 +57,7 @@ float4 total=0; // 1/(1+2+1+2+4+2+1+2+1) = 1/ 16 = .0625 total *= 0.0625f; -// soustraction de l'image flou a l'image originale +// вычитание размытого изображения из оригинального total = 2*ori - total; //return ori; return total; diff --git a/MathCore.WPF/Shaders/new/sharpen.psh b/MathCore.WPF/Shaders/new/sharpen.psh index 73fe8dd..d7803f1 100644 --- a/MathCore.WPF/Shaders/new/sharpen.psh +++ b/MathCore.WPF/Shaders/new/sharpen.psh @@ -1,4 +1,7 @@ -// $MinimumShaderProfile: ps_2_0 +// Параметры: width, height (размеры изображения в пикселях) +// Назначение: фильтр резкости с использованием матрицы выборки 3x3 +// Коэффициенты: центр=2.0 (усиление), соседи=-0.125 (ослабление) +// Эффект: повышение контрастности краев и деталей изображения sampler s0 : register(s0); float4 p0 : register(c0); From 1f4b63b32dda49c29251931b35133651e13999b6 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:44:55 +0300 Subject: [PATCH 11/33] =?UTF-8?q?=D0=92=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=20ViewModel=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20XML-=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/ViewModels/ViewModel.cs | 189 ++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 4 deletions(-) diff --git a/MathCore.WPF/ViewModels/ViewModel.cs b/MathCore.WPF/ViewModels/ViewModel.cs index b8bf9cd..98b42f1 100644 --- a/MathCore.WPF/ViewModels/ViewModel.cs +++ b/MathCore.WPF/ViewModels/ViewModel.cs @@ -49,8 +49,7 @@ public event PropertyChangedEventHandler? PropertyChanged /// Отсоединяемый обработчик события protected virtual void PropertyChanged_RemoveHandler(PropertyChangedEventHandler handler) => PropertyChangedEvent -= handler; - /// Признак того, что мы находимся в режиме разработки под Visual Studio - //public static bool IsDesignMode { get; set; } = LicenseManager.UsageMode == LicenseUsageMode.Designtime; + /// Признак работы в режиме дизайна public static bool IsDesignMode { get; set; } = DesignerProperties.GetIsInDesignMode(new()); private readonly object _PropertiesDependenciesSyncRoot = new(); @@ -173,6 +172,9 @@ protected bool PropertyDependencies_Clear(string PropertyName) private Dictionary? _PropertyChangedHandlers; + /// Добавить обработчик изменения указанного свойства + /// Имя свойства + /// Обработчик изменения свойства protected void PropertyChanged_AddHandler(string PropertyName, Action handler) { lock (_PropertiesDependenciesSyncRoot) @@ -185,6 +187,10 @@ protected void PropertyChanged_AddHandler(string PropertyName, Action handler) } } + /// Удалить обработчик изменения указанного свойства + /// Имя свойства + /// Обработчик изменения свойства + /// Истина, если обработчик удалён protected bool PropertyChanged_RemoveHandler(string PropertyName, Action handler) { lock (_PropertiesDependenciesSyncRoot) @@ -200,12 +206,17 @@ protected bool PropertyChanged_RemoveHandler(string PropertyName, Action handler } } + /// Очистить обработчики изменения указанного свойства + /// Имя свойства + /// Истина, если обработчики удалены protected bool PropertyChanged_ClearHandlers(string PropertyName) { lock (_PropertiesDependenciesSyncRoot) return _PropertyChangedHandlers is { Count: > 0 } && _PropertyChangedHandlers.Remove(PropertyName); } + /// Очистить все обработчики изменения свойств + /// Истина, если обработчики удалены protected virtual bool PropertyChanged_ClearHandlers() { lock (_PropertiesDependenciesSyncRoot) @@ -339,6 +350,12 @@ protected ViewModel(bool CheckDependencies = true) private readonly Dictionary _ModelPropertyValues = []; + /// Установить значение свойства в словаре модели + /// Тип значения свойства + /// Новое значение свойства + /// Имя свойства + /// Обновить состояния команд + /// Истина, если значение изменилось protected bool Set( T? value, [CallerMemberName] string Property = null!, @@ -351,6 +368,10 @@ protected bool Set( return true; } + /// Получить значение свойства из словаря модели + /// Тип значения свойства + /// Имя свойства + /// Значение свойства или значение по умолчанию protected T? Get([CallerMemberName] string Property = null!) => _ModelPropertyValues.TryGetValue(Property ?? throw new ArgumentNullException(nameof(Property)), out var value) ? (T?)value @@ -360,9 +381,9 @@ protected bool Set( /// Тип значения свойства /// Ссылка на поле, хранящее значение свойства /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства /// Объект-источник события /// Имя свойства - /// Метод уведомления об изменении значения свойства /// Истина, если значение свойства установлено успешно public static bool Set( ref T? field, @@ -377,6 +398,13 @@ public static bool Set( return true; } + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Истина, если значение свойства установлено успешно public static bool Set( ref T? field, T? value, @@ -401,6 +429,15 @@ public static bool Set( return true; } + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод проверки возможности установки значения + /// Метод уведомления об изменении значения свойства + /// Объект-источник события + /// Имя свойства + /// Истина, если значение свойства установлено успешно public static bool Set( ref T? field, T? value, @@ -410,6 +447,13 @@ public static bool Set( [CallerMemberName] string PropertyName = null!) => ValueChecker(value) && Set(ref field, value, OnPropertyChanged, Sender, PropertyName); + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Результат установки значения public SetStaticValueResult SetValue( ref T? field, T? value, @@ -424,6 +468,14 @@ public SetStaticValueResult SetValue( return new(true, old_value, value, OnPropertyChanged); } + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод проверки возможности установки значения + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Результат установки значения public static SetStaticValueResult SetValue( [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, @@ -439,6 +491,10 @@ public static SetStaticValueResult SetValue( return new(true, old_value, value, OnPropertyChanged); } + /// Проверить путь к файлу в режиме дизайна + /// Относительный путь к файлу + /// Путь к исходному файлу + /// Корректный путь к файлу public static string? CheckDesignModeFilePath( string? RelativeFileName, [CallerFilePath] string? SourceFilePath = null) => @@ -449,7 +505,6 @@ public static SetStaticValueResult SetValue( && RelativeFileName[1] == ':' ? RelativeFileName : Path.Combine(Path.GetDirectoryName(SourceFilePath) ?? "", RelativeFileName); - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства /// Тип значения свойства /// Ссылка на поле, хранящее значение свойства @@ -513,8 +568,13 @@ public readonly ref struct SetValueResult private readonly T? _OldValue; private readonly T? _NewValue; + /// Признак успешного изменения значения public bool Result => _Result; + + /// Предыдущее значение public T? OldValue => _OldValue; + + /// Новое значение public T? NewValue => _NewValue; internal SetValueResult(bool Result, T? OldValue, ViewModel model) : this(Result, OldValue, OldValue, model) { } @@ -526,78 +586,120 @@ internal SetValueResult(bool Result, T? OldValue, T? NewValue, ViewModel model) _Model = model; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(); return _Result; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(NewValue); return _Result; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(NewValue); return _Result; } + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool ThenAsync(Action execute) { if (_Result) Task.Run(execute); return _Result; } + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool ThenAsync(Action execute) { if (_Result) NewValue.Async(execute); return _Result; } + /// Выполнить действие при выполнении условия + /// Условие выполнения + /// Действие + /// Истина, если изменение произошло public bool ThenIf(Func predicate, Action execute) { if (_Result && predicate(NewValue)) execute(NewValue); return _Result; } + /// Выполнить действие асинхронно при выполнении условия + /// Условие выполнения + /// Действие + /// Истина, если изменение произошло public bool ThenIfAsync(Func predicate, Action execute) { if (_Result && predicate(NewValue)) NewValue.Async(execute); return _Result; } + /// Установить значение при успешном изменении + /// Действие установки + /// Текущий результат public SetValueResult ThenSet(Action SetAction) { if (_Result) SetAction(NewValue); return this; } + /// Установить значение асинхронно при успешном изменении + /// Действие установки + /// Текущий результат public SetValueResult ThenSetAsync(Action SetAction) { if (_Result) NewValue.Async(SetAction); return this; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(OldValue, NewValue); return _Result; } + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool ThenAsync(Action execute) { if (_Result) OldValue.Async(NewValue, execute); return _Result; } + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Обновить состояния команд + /// Текущий результат public SetValueResult ThenUpdate(string PropertyName, bool UpdateCommands = false) { if (_Result) _Model.OnPropertyChanged(PropertyName, UpdateCommands); return this; } + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат public SetValueResult ThenUpdate(params string[] PropertyNames) { if (!_Result) return this; @@ -606,6 +708,10 @@ public SetValueResult ThenUpdate(params string[] PropertyNames) return this; } + /// Уведомить об изменении указанных свойств + /// Обновить состояния команд + /// Имена свойств + /// Текущий результат public SetValueResult ThenUpdate(bool UpdateCommands, params string[] PropertyNames) { if (!_Result) return this; @@ -614,57 +720,86 @@ public SetValueResult ThenUpdate(bool UpdateCommands, params string[] Propert return this; } + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Обновить состояния команд + /// Текущий результат public SetValueResult Update(string PropertyName, bool UpdateCommands = false) { _Model.OnPropertyChanged(PropertyName, UpdateCommands); return this; } + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат public SetValueResult Update(params string[] PropertyName) { foreach (var name in PropertyName) _Model.OnPropertyChanged(name); return this; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(); return _Result; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_Result); return _Result; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(NewValue); return _Result; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(NewValue, _Result); return _Result; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(OldValue, NewValue); return _Result; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(OldValue, NewValue, _Result); return _Result; } + /// Преобразовать результат в логическое значение + /// Результат установки + /// Истина, если изменение произошло public static implicit operator bool(SetValueResult result) => result._Result; } + /// Результат установки значения свойства со статическим обработчиком public readonly ref struct SetStaticValueResult { private readonly bool _Result; @@ -681,66 +816,108 @@ internal SetStaticValueResult(bool Result, T? OldValue, T? NewValue, ActionВыполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(); return _Result; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(_NewValue); return _Result; } + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло public bool Then(Action execute) { if (_Result) execute(_OldValue, _NewValue); return _Result; } + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Текущий результат public SetStaticValueResult Update(string PropertyName) { _OnPropertyChanged(PropertyName); return this; } + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат public SetStaticValueResult Update(params string[] PropertyName) { foreach (var name in PropertyName) _OnPropertyChanged(name); return this; } + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(); return _Result; } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_Result); return _Result; } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_NewValue); return _Result; } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_NewValue, _Result); return _Result; } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_OldValue, _NewValue); return _Result; } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло public bool AnywayThen(Action execute) { execute(_OldValue, _NewValue, _Result); return _Result; } + + /// Преобразовать результат в логическое значение + /// Результат установки + /// Истина, если изменение произошло public static implicit operator bool(SetStaticValueResult result) => result._Result; } @@ -861,6 +1038,10 @@ public override object ProvideValue(IServiceProvider Service) return this; } + /// Обработчик инициализации в XAML + /// Целевой объект + /// Целевое свойство + /// Корневой объект protected virtual void OnInitialized(object? target, object? property, object? root) { From 5016fdddde70b28025e8703056a71af06a6f34bc Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:51:07 +0300 Subject: [PATCH 12/33] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3:=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B?= =?UTF-8?q?=20Set/SetValue=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Удалены методы и структуры установки свойств из ViewModel.cs. Вся логика Set, SetValue, SetValueResult и SetStaticValueResult перемещена в отдельные файлы для улучшения структуры и поддержки кода. Добавлены новые файлы с соответствующими реализациями и директивами. Улучшена читаемость и модульность проекта. --- MathCore.WPF/ViewModels/ViewModel.Set.cs | 223 ++++++ .../ViewModel.SetStaticValueResult.cs | 134 ++++ MathCore.WPF/ViewModels/ViewModel.SetValue.cs | 91 +++ .../ViewModels/ViewModel.SetValueResult.cs | 250 +++++++ MathCore.WPF/ViewModels/ViewModel.cs | 663 +----------------- 5 files changed, 701 insertions(+), 660 deletions(-) create mode 100644 MathCore.WPF/ViewModels/ViewModel.Set.cs create mode 100644 MathCore.WPF/ViewModels/ViewModel.SetStaticValueResult.cs create mode 100644 MathCore.WPF/ViewModels/ViewModel.SetValue.cs create mode 100644 MathCore.WPF/ViewModels/ViewModel.SetValueResult.cs diff --git a/MathCore.WPF/ViewModels/ViewModel.Set.cs b/MathCore.WPF/ViewModels/ViewModel.Set.cs new file mode 100644 index 0000000..bcd4e41 --- /dev/null +++ b/MathCore.WPF/ViewModels/ViewModel.Set.cs @@ -0,0 +1,223 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Input; + +using MathCore.Annotations; + +namespace MathCore.WPF.ViewModels; + +public partial class ViewModel +{ + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Тип значения, устанавливаемого для свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод преобразования значения + /// Имя свойства + /// Истина, если значение свойства установлено успешно + [NotifyPropertyChangedInvocator] + protected virtual bool Set( + [Attributes.NotNullIfNotNull(nameof(value))] ref TField? field, + TValue? value, + Func converter, + [CallerMemberName] string PropertyName = null!) => + Set(ref field, converter(value), PropertyName); + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод проверки правильности устанавливаемого значения + /// Имя свойства + /// Истина, если значение свойства установлено успешно + protected virtual bool Set( + [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, + T? value, + Func ValueChecker, + [CallerMemberName] string PropertyName = null!) => + ValueChecker(value) && Set(ref field, value, PropertyName); + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Тип значения, получаемого из свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод преобразования значения + /// Метод проверки правильности устанавливаемого значения + /// Имя свойства + /// Истина, если значение свойства установлено успешно + [NotifyPropertyChangedInvocator] + protected virtual bool Set( + [Attributes.NotNullIfNotNull(nameof(value))] ref TField? field, + TValue? value, + Func converter, + Func ValueChecker, + [CallerMemberName] string PropertyName = null!) => + Set(ref field, converter(value), ValueChecker, PropertyName); + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Обновить состояния команд + /// Имя свойства + /// Истина, если значение свойства установлено успешно + protected virtual bool Set( + [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, + T? value, + bool UpdateCommandsState, + [CallerMemberName] string PropertyName = null!) + { + var result = Set(ref field, value, PropertyName); + if (result && UpdateCommandsState) + CommandManager.InvalidateRequerySuggested(); + return result; + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Новое значение свойства + /// Старое значение свойства + /// Метод установки нового значения свойства + /// Метод проверки возможности установки нового значения свойства + /// Имя свойства + /// Истина, если значение свойства установлено успешно + protected virtual bool Set( + T? Value, + T? OldValue, + Action Setter, + Func? ValueValidator, + [CallerMemberName] string PropertyName = null!) + { + if (Equals(Value, OldValue)) return false; + if (ValueValidator is not null && !ValueValidator(Value)) return false; + Setter(Value); + OnPropertyChanged(PropertyName); + return true; + } + + /// Асинхронный метод изменения значения свойства + /// Тип значения свойства + /// Поле, хранящее значение свойства + /// Новое значение свойства + /// Имя свойства + /// Задача, возвращающая истину, если свойство изменило своё значение + protected virtual ValueTask SetAsync([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return new(false); + field = value; + + return SetPropertyAsync(PropertyName); + async ValueTask SetPropertyAsync(string property) + { + await Task.Factory.StartNew(s => OnPropertyChanged((string)s!), property); + return true; + } + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Предыдущее значение + /// Имя свойства + /// Истина, если значение свойства установлено успешно + [NotifyPropertyChangedInvocator] + protected virtual bool Set([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, out T? OldValue, [CallerMemberName] string PropertyName = null!) + { + OldValue = field; + if (Equals(field, value)) return false; + field = value; + OnPropertyChanged(PropertyName); + return true; + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Имя свойства + /// Истина, если значение свойства установлено успешно + [NotifyPropertyChangedInvocator] + protected virtual bool Set( + [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, + T? value, + [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return false; + field = value; + OnPropertyChanged(PropertyName); + return true; + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод проверки возможности установки значения + /// Метод уведомления об изменении значения свойства + /// Объект-источник события + /// Имя свойства + /// Истина, если значение свойства установлено успешно + public static bool Set( + ref T? field, + T? value, + Func ValueChecker, + PropertyChangedEventHandler OnPropertyChanged, + object? Sender, + [CallerMemberName] string PropertyName = null!) => + ValueChecker(value) && Set(ref field, value, OnPropertyChanged, Sender, PropertyName); + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Объект-источник события + /// Имя свойства + /// Истина, если значение свойства установлено успешно + public static bool Set( + ref T? field, + T? value, + PropertyChangedEventHandler OnPropertyChanged, + object? Sender, + [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return false; + field = value; + OnPropertyChanged(Sender, new(PropertyName)); + return true; + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Истина, если значение свойства установлено успешно + public static bool Set( + ref T? field, + T? value, + Action OnPropertyChanged, + object? Sender, [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return false; + field = value; + OnPropertyChanged(Sender, PropertyName); + return true; + } + + public static bool Set( + ref T? field, + T? value, + Action OnPropertyChanged, + [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return false; + field = value; + OnPropertyChanged(PropertyName); + return true; + } +} diff --git a/MathCore.WPF/ViewModels/ViewModel.SetStaticValueResult.cs b/MathCore.WPF/ViewModels/ViewModel.SetStaticValueResult.cs new file mode 100644 index 0000000..184473c --- /dev/null +++ b/MathCore.WPF/ViewModels/ViewModel.SetStaticValueResult.cs @@ -0,0 +1,134 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ParameterHidesMember + +// ReSharper disable VirtualMemberNeverOverridden.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedMethodReturnValue.Global +// ReSharper disable UnusedParameter.Global + +namespace MathCore.WPF.ViewModels; + +public abstract partial class ViewModel +{ + /// Результат установки значения свойства со статическим обработчиком + public readonly ref struct SetStaticValueResult + { + private readonly bool _Result; + private readonly T? _OldValue; + private readonly T? _NewValue; + private readonly Action _OnPropertyChanged; + + internal SetStaticValueResult(bool Result, T? OldValue, Action OnPropertyChanged) : this(Result, OldValue, OldValue, OnPropertyChanged) { } + internal SetStaticValueResult(bool Result, T? OldValue, T? NewValue, Action OnPropertyChanged) + { + _Result = Result; + _OldValue = OldValue; + _NewValue = NewValue; + _OnPropertyChanged = OnPropertyChanged; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(); + return _Result; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(_NewValue); + return _Result; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(_OldValue, _NewValue); + return _Result; + } + + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Текущий результат + public SetStaticValueResult Update(string PropertyName) + { + _OnPropertyChanged(PropertyName); + return this; + } + + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат + public SetStaticValueResult Update(params string[] PropertyName) + { + foreach (var name in PropertyName) _OnPropertyChanged(name); + return this; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_Result); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_NewValue); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_NewValue, _Result); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_OldValue, _NewValue); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_OldValue, _NewValue, _Result); + return _Result; + } + + /// Преобразовать результат в логическое значение + /// Результат установки + /// Истина, если изменение произошло + public static implicit operator bool(SetStaticValueResult result) => result._Result; + } +} \ No newline at end of file diff --git a/MathCore.WPF/ViewModels/ViewModel.SetValue.cs b/MathCore.WPF/ViewModels/ViewModel.SetValue.cs new file mode 100644 index 0000000..d04e824 --- /dev/null +++ b/MathCore.WPF/ViewModels/ViewModel.SetValue.cs @@ -0,0 +1,91 @@ +using System.Runtime.CompilerServices; + +using MathCore.Annotations; + +namespace MathCore.WPF.ViewModels; + +public partial class ViewModel +{ + [NotifyPropertyChangedInvocator] + protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value)) return new(false, field, field, this); + var old_value = field; + field = value; + OnPropertyChanged(PropertyName); + return new(true, old_value, value, this); + } + + [NotifyPropertyChangedInvocator] + protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, Func value_checker, [CallerMemberName] string PropertyName = null!) + { + if (Equals(field, value) || !value_checker(value)) + return new(false, field, value, this); + var old_value = field; + field = value; + OnPropertyChanged(PropertyName); + return new(true, old_value, value, this); + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Результат установки значения + public SetStaticValueResult SetValue( + ref T? field, + T? value, + Action OnPropertyChanged, + [CallerMemberName] string PropertyName = null!) + { + if (OnPropertyChanged is null) throw new ArgumentNullException(nameof(OnPropertyChanged)); + if (Equals(field, value)) return new(false, field, field, OnPropertyChanged); + var old_value = field; + field = value; + OnPropertyChanged(PropertyName); + return new(true, old_value, value, OnPropertyChanged); + } + + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод проверки возможности установки значения + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Результат установки значения + public static SetStaticValueResult SetValue( + [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, + T? value, + Func ValueChecker, + Action OnPropertyChanged, + [CallerMemberName] string PropertyName = null!) + { + if (OnPropertyChanged is null) throw new ArgumentNullException(nameof(OnPropertyChanged)); + if (Equals(field, value) || !ValueChecker(value)) return new(false, field, value, OnPropertyChanged); + var old_value = field; + field = value; + OnPropertyChanged(PropertyName); + return new(true, old_value, value, OnPropertyChanged); + } + + /// Установить значение свойства в словаре модели + /// Тип значения свойства + /// Новое значение свойства + /// Имя свойства + /// Обновить состояния команд + /// Истина, если значение изменилось + protected bool Set( + T? value, + [CallerMemberName] string Property = null!, + bool UpdateCommandsState = false) + { + if (_ModelPropertyValues.TryGetValue(Property ?? throw new ArgumentNullException(nameof(Property), "Имя свойства не задано"), out var old_value) && Equals(old_value, value)) + return false; + _ModelPropertyValues[Property] = value; + OnPropertyChanged(Property, UpdateCommandsState); + return true; + } +} diff --git a/MathCore.WPF/ViewModels/ViewModel.SetValueResult.cs b/MathCore.WPF/ViewModels/ViewModel.SetValueResult.cs new file mode 100644 index 0000000..5b982a7 --- /dev/null +++ b/MathCore.WPF/ViewModels/ViewModel.SetValueResult.cs @@ -0,0 +1,250 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ParameterHidesMember + +// ReSharper disable VirtualMemberNeverOverridden.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedMethodReturnValue.Global +// ReSharper disable UnusedParameter.Global + +namespace MathCore.WPF.ViewModels; + +public abstract partial class ViewModel +{ + public readonly ref struct SetValueResult + { + private readonly ViewModel _Model; + private readonly bool _Result; + private readonly T? _OldValue; + private readonly T? _NewValue; + + /// Признак успешного изменения значения + public bool Result => _Result; + + /// Предыдущее значение + public T? OldValue => _OldValue; + + /// Новое значение + public T? NewValue => _NewValue; + + internal SetValueResult(bool Result, T? OldValue, ViewModel model) : this(Result, OldValue, OldValue, model) { } + internal SetValueResult(bool Result, T? OldValue, T? NewValue, ViewModel model) + { + _Result = Result; + _OldValue = OldValue; + _NewValue = NewValue; + _Model = model; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(); + return _Result; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(NewValue); + return _Result; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(NewValue); + return _Result; + } + + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool ThenAsync(Action execute) + { + if (_Result) Task.Run(execute); + return _Result; + } + + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool ThenAsync(Action execute) + { + if (_Result) NewValue.Async(execute); + return _Result; + } + + /// Выполнить действие при выполнении условия + /// Условие выполнения + /// Действие + /// Истина, если изменение произошло + public bool ThenIf(Func predicate, Action execute) + { + if (_Result && predicate(NewValue)) execute(NewValue); + return _Result; + } + + /// Выполнить действие асинхронно при выполнении условия + /// Условие выполнения + /// Действие + /// Истина, если изменение произошло + public bool ThenIfAsync(Func predicate, Action execute) + { + if (_Result && predicate(NewValue)) NewValue.Async(execute); + return _Result; + } + + /// Установить значение при успешном изменении + /// Действие установки + /// Текущий результат + public SetValueResult ThenSet(Action SetAction) + { + if (_Result) SetAction(NewValue); + return this; + } + + /// Установить значение асинхронно при успешном изменении + /// Действие установки + /// Текущий результат + public SetValueResult ThenSetAsync(Action SetAction) + { + if (_Result) NewValue.Async(SetAction); + return this; + } + + /// Выполнить действие при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool Then(Action execute) + { + if (_Result) execute(OldValue, NewValue); + return _Result; + } + + /// Выполнить действие асинхронно при успешном изменении + /// Действие + /// Истина, если изменение произошло + public bool ThenAsync(Action execute) + { + if (_Result) OldValue.Async(NewValue, execute); + return _Result; + } + + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Обновить состояния команд + /// Текущий результат + public SetValueResult ThenUpdate(string PropertyName, bool UpdateCommands = false) + { + if (_Result) _Model.OnPropertyChanged(PropertyName, UpdateCommands); + return this; + } + + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат + public SetValueResult ThenUpdate(params string[] PropertyNames) + { + if (!_Result) return this; + foreach (var property in PropertyNames) + _Model.OnPropertyChanged(property); + return this; + } + + /// Уведомить об изменении указанных свойств + /// Обновить состояния команд + /// Имена свойств + /// Текущий результат + public SetValueResult ThenUpdate(bool UpdateCommands, params string[] PropertyNames) + { + if (!_Result) return this; + foreach (var property in PropertyNames) + _Model.OnPropertyChanged(property, UpdateCommands); + return this; + } + + /// Уведомить об изменении указанного свойства + /// Имя свойства + /// Обновить состояния команд + /// Текущий результат + public SetValueResult Update(string PropertyName, bool UpdateCommands = false) + { + _Model.OnPropertyChanged(PropertyName, UpdateCommands); + return this; + } + + /// Уведомить об изменении указанных свойств + /// Имена свойств + /// Текущий результат + public SetValueResult Update(params string[] PropertyName) + { + foreach (var name in PropertyName) _Model.OnPropertyChanged(name); + return this; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(_Result); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(NewValue); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(NewValue, _Result); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(OldValue, NewValue); + return _Result; + } + + /// Выполнить действие независимо от результата + /// Действие + /// Истина, если изменение произошло + public bool AnywayThen(Action execute) + { + execute(OldValue, NewValue, _Result); + return _Result; + } + + /// Преобразовать результат в логическое значение + /// Результат установки + /// Истина, если изменение произошло + public static implicit operator bool(SetValueResult result) => result._Result; + } +} \ No newline at end of file diff --git a/MathCore.WPF/ViewModels/ViewModel.cs b/MathCore.WPF/ViewModels/ViewModel.cs index 98b42f1..7abe2b3 100644 --- a/MathCore.WPF/ViewModels/ViewModel.cs +++ b/MathCore.WPF/ViewModels/ViewModel.cs @@ -7,8 +7,6 @@ using System.Windows.Markup; using System.Xaml; -using MathCore.Annotations; - // ReSharper disable MemberCanBePrivate.Global // ReSharper disable ParameterHidesMember @@ -301,7 +299,7 @@ protected async void OnPropertyChangedAsync(string PropertyName, int Timeout = 0 return; } - var now = DateTime.Now; + var now = DateTime.Now; var properties_invoke_time = _PropertyAsyncInvokeTime; if (properties_invoke_time.TryGetValue(PropertyName, out var last_call_time) && (now - last_call_time).TotalMilliseconds < Timeout) { @@ -350,24 +348,6 @@ protected ViewModel(bool CheckDependencies = true) private readonly Dictionary _ModelPropertyValues = []; - /// Установить значение свойства в словаре модели - /// Тип значения свойства - /// Новое значение свойства - /// Имя свойства - /// Обновить состояния команд - /// Истина, если значение изменилось - protected bool Set( - T? value, - [CallerMemberName] string Property = null!, - bool UpdateCommandsState = false) - { - if (_ModelPropertyValues.TryGetValue(Property ?? throw new ArgumentNullException(nameof(Property), "Имя свойства не задано"), out var old_value) && Equals(old_value, value)) - return false; - _ModelPropertyValues[Property] = value; - OnPropertyChanged(Property, UpdateCommandsState); - return true; - } - /// Получить значение свойства из словаря модели /// Тип значения свойства /// Имя свойства @@ -377,120 +357,6 @@ protected bool Set( ? (T?)value : default; - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод уведомления об изменении значения свойства - /// Объект-источник события - /// Имя свойства - /// Истина, если значение свойства установлено успешно - public static bool Set( - ref T? field, - T? value, - PropertyChangedEventHandler OnPropertyChanged, - object? Sender, - [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(Sender, new(PropertyName)); - return true; - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод уведомления об изменении значения свойства - /// Имя свойства - /// Истина, если значение свойства установлено успешно - public static bool Set( - ref T? field, - T? value, - Action OnPropertyChanged, - object? Sender, [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(Sender, PropertyName); - return true; - } - - public static bool Set( - ref T? field, - T? value, - Action OnPropertyChanged, - [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(PropertyName); - return true; - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод проверки возможности установки значения - /// Метод уведомления об изменении значения свойства - /// Объект-источник события - /// Имя свойства - /// Истина, если значение свойства установлено успешно - public static bool Set( - ref T? field, - T? value, - Func ValueChecker, - PropertyChangedEventHandler OnPropertyChanged, - object? Sender, - [CallerMemberName] string PropertyName = null!) => - ValueChecker(value) && Set(ref field, value, OnPropertyChanged, Sender, PropertyName); - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод уведомления об изменении значения свойства - /// Имя свойства - /// Результат установки значения - public SetStaticValueResult SetValue( - ref T? field, - T? value, - Action OnPropertyChanged, - [CallerMemberName] string PropertyName = null!) - { - if (OnPropertyChanged is null) throw new ArgumentNullException(nameof(OnPropertyChanged)); - if (Equals(field, value)) return new(false, field, field, OnPropertyChanged); - var old_value = field; - field = value; - OnPropertyChanged(PropertyName); - return new(true, old_value, value, OnPropertyChanged); - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод проверки возможности установки значения - /// Метод уведомления об изменении значения свойства - /// Имя свойства - /// Результат установки значения - public static SetStaticValueResult SetValue( - [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, - T? value, - Func ValueChecker, - Action OnPropertyChanged, - [CallerMemberName] string PropertyName = null!) - { - if (OnPropertyChanged is null) throw new ArgumentNullException(nameof(OnPropertyChanged)); - if (Equals(field, value) || !ValueChecker(value)) return new(false, field, value, OnPropertyChanged); - var old_value = field; - field = value; - OnPropertyChanged(PropertyName); - return new(true, old_value, value, OnPropertyChanged); - } - /// Проверить путь к файлу в режиме дизайна /// Относительный путь к файлу /// Путь к исходному файлу @@ -501,533 +367,10 @@ public static SetStaticValueResult SetValue( !IsDesignMode || SourceFilePath is null || RelativeFileName is null - || RelativeFileName.Length > 3 - && RelativeFileName[1] == ':' + || (RelativeFileName.Length > 3 + && RelativeFileName[1] == ':') ? RelativeFileName : Path.Combine(Path.GetDirectoryName(SourceFilePath) ?? "", RelativeFileName); - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Имя свойства - /// Истина, если значение свойства установлено успешно - [NotifyPropertyChangedInvocator] - protected virtual bool Set( - [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, - T? value, - [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(PropertyName); - return true; - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Предыдущее значение - /// Имя свойства - /// Истина, если значение свойства установлено успешно - [NotifyPropertyChangedInvocator] - protected virtual bool Set([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, out T? OldValue, [CallerMemberName] string PropertyName = null!) - { - OldValue = field; - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(PropertyName); - return true; - } - - [NotifyPropertyChangedInvocator] - protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return new(false, field, field, this); - var old_value = field; - field = value; - OnPropertyChanged(PropertyName); - return new(true, old_value, value, this); - } - - [NotifyPropertyChangedInvocator] - protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, Func value_checker, [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value) || !value_checker(value)) - return new(false, field, value, this); - var old_value = field; - field = value; - OnPropertyChanged(PropertyName); - return new(true, old_value, value, this); - } - - public readonly ref struct SetValueResult - { - private readonly ViewModel _Model; - private readonly bool _Result; - private readonly T? _OldValue; - private readonly T? _NewValue; - - /// Признак успешного изменения значения - public bool Result => _Result; - - /// Предыдущее значение - public T? OldValue => _OldValue; - - /// Новое значение - public T? NewValue => _NewValue; - - internal SetValueResult(bool Result, T? OldValue, ViewModel model) : this(Result, OldValue, OldValue, model) { } - internal SetValueResult(bool Result, T? OldValue, T? NewValue, ViewModel model) - { - _Result = Result; - _OldValue = OldValue; - _NewValue = NewValue; - _Model = model; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(); - return _Result; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(NewValue); - return _Result; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(NewValue); - return _Result; - } - - /// Выполнить действие асинхронно при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool ThenAsync(Action execute) - { - if (_Result) Task.Run(execute); - return _Result; - } - - /// Выполнить действие асинхронно при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool ThenAsync(Action execute) - { - if (_Result) NewValue.Async(execute); - return _Result; - } - - /// Выполнить действие при выполнении условия - /// Условие выполнения - /// Действие - /// Истина, если изменение произошло - public bool ThenIf(Func predicate, Action execute) - { - if (_Result && predicate(NewValue)) execute(NewValue); - return _Result; - } - - /// Выполнить действие асинхронно при выполнении условия - /// Условие выполнения - /// Действие - /// Истина, если изменение произошло - public bool ThenIfAsync(Func predicate, Action execute) - { - if (_Result && predicate(NewValue)) NewValue.Async(execute); - return _Result; - } - - /// Установить значение при успешном изменении - /// Действие установки - /// Текущий результат - public SetValueResult ThenSet(Action SetAction) - { - if (_Result) SetAction(NewValue); - return this; - } - - /// Установить значение асинхронно при успешном изменении - /// Действие установки - /// Текущий результат - public SetValueResult ThenSetAsync(Action SetAction) - { - if (_Result) NewValue.Async(SetAction); - return this; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(OldValue, NewValue); - return _Result; - } - - /// Выполнить действие асинхронно при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool ThenAsync(Action execute) - { - if (_Result) OldValue.Async(NewValue, execute); - return _Result; - } - - /// Уведомить об изменении указанного свойства - /// Имя свойства - /// Обновить состояния команд - /// Текущий результат - public SetValueResult ThenUpdate(string PropertyName, bool UpdateCommands = false) - { - if (_Result) _Model.OnPropertyChanged(PropertyName, UpdateCommands); - return this; - } - - /// Уведомить об изменении указанных свойств - /// Имена свойств - /// Текущий результат - public SetValueResult ThenUpdate(params string[] PropertyNames) - { - if (!_Result) return this; - foreach (var property in PropertyNames) - _Model.OnPropertyChanged(property); - return this; - } - - /// Уведомить об изменении указанных свойств - /// Обновить состояния команд - /// Имена свойств - /// Текущий результат - public SetValueResult ThenUpdate(bool UpdateCommands, params string[] PropertyNames) - { - if (!_Result) return this; - foreach (var property in PropertyNames) - _Model.OnPropertyChanged(property, UpdateCommands); - return this; - } - - /// Уведомить об изменении указанного свойства - /// Имя свойства - /// Обновить состояния команд - /// Текущий результат - public SetValueResult Update(string PropertyName, bool UpdateCommands = false) - { - _Model.OnPropertyChanged(PropertyName, UpdateCommands); - return this; - } - - /// Уведомить об изменении указанных свойств - /// Имена свойств - /// Текущий результат - public SetValueResult Update(params string[] PropertyName) - { - foreach (var name in PropertyName) _Model.OnPropertyChanged(name); - return this; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_Result); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(NewValue); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(NewValue, _Result); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(OldValue, NewValue); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(OldValue, NewValue, _Result); - return _Result; - } - - /// Преобразовать результат в логическое значение - /// Результат установки - /// Истина, если изменение произошло - public static implicit operator bool(SetValueResult result) => result._Result; - } - - /// Результат установки значения свойства со статическим обработчиком - public readonly ref struct SetStaticValueResult - { - private readonly bool _Result; - private readonly T? _OldValue; - private readonly T? _NewValue; - private readonly Action _OnPropertyChanged; - - internal SetStaticValueResult(bool Result, T? OldValue, Action OnPropertyChanged) : this(Result, OldValue, OldValue, OnPropertyChanged) { } - internal SetStaticValueResult(bool Result, T? OldValue, T? NewValue, Action OnPropertyChanged) - { - _Result = Result; - _OldValue = OldValue; - _NewValue = NewValue; - _OnPropertyChanged = OnPropertyChanged; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(); - return _Result; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(_NewValue); - return _Result; - } - - /// Выполнить действие при успешном изменении - /// Действие - /// Истина, если изменение произошло - public bool Then(Action execute) - { - if (_Result) execute(_OldValue, _NewValue); - return _Result; - } - - /// Уведомить об изменении указанного свойства - /// Имя свойства - /// Текущий результат - public SetStaticValueResult Update(string PropertyName) - { - _OnPropertyChanged(PropertyName); - return this; - } - - /// Уведомить об изменении указанных свойств - /// Имена свойств - /// Текущий результат - public SetStaticValueResult Update(params string[] PropertyName) - { - foreach (var name in PropertyName) _OnPropertyChanged(name); - return this; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_Result); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_NewValue); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_NewValue, _Result); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_OldValue, _NewValue); - return _Result; - } - - /// Выполнить действие независимо от результата - /// Действие - /// Истина, если изменение произошло - public bool AnywayThen(Action execute) - { - execute(_OldValue, _NewValue, _Result); - return _Result; - } - - /// Преобразовать результат в логическое значение - /// Результат установки - /// Истина, если изменение произошло - public static implicit operator bool(SetStaticValueResult result) => result._Result; - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Тип значения, устанавливаемого для свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод преобразования значения - /// Имя свойства - /// Истина, если значение свойства установлено успешно - [NotifyPropertyChangedInvocator] - protected virtual bool Set( - [Attributes.NotNullIfNotNull(nameof(value))] ref TField? field, - TValue? value, - Func converter, - [CallerMemberName] string PropertyName = null!) => - Set(ref field, converter(value), PropertyName); - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод проверки правильности устанавливаемого значения - /// Имя свойства - /// Истина, если значение свойства установлено успешно - protected virtual bool Set( - [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, - T? value, - Func ValueChecker, - [CallerMemberName] string PropertyName = null!) => - ValueChecker(value) && Set(ref field, value, PropertyName); - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Тип значения, получаемого из свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Метод преобразования значения - /// Метод проверки правильности устанавливаемого значения - /// Имя свойства - /// Истина, если значение свойства установлено успешно - [NotifyPropertyChangedInvocator] - protected virtual bool Set( - [Attributes.NotNullIfNotNull(nameof(value))] ref TField? field, - TValue? value, - Func converter, - Func ValueChecker, - [CallerMemberName] string PropertyName = null!) => - Set(ref field, converter(value), ValueChecker, PropertyName); - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Ссылка на поле, хранящее значение свойства - /// Значение свойства, которое надо установить - /// Обновить состояния команд - /// Имя свойства - /// Истина, если значение свойства установлено успешно - protected virtual bool Set( - [Attributes.NotNullIfNotNull(nameof(value))] ref T? field, - T? value, - bool UpdateCommandsState, - [CallerMemberName] string PropertyName = null!) - { - var result = Set(ref field, value, PropertyName); - if (result && UpdateCommandsState) - CommandManager.InvalidateRequerySuggested(); - return result; - } - - /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства - /// Тип значения свойства - /// Новое значение свойства - /// Старое значение свойства - /// Метод установки нового значения свойства - /// Метод проверки возможности установки нового значения свойства - /// Имя свойства - /// Истина, если значение свойства установлено успешно - protected virtual bool Set( - T? Value, - T? OldValue, - Action Setter, - Func? ValueValidator, - [CallerMemberName] string PropertyName = null!) - { - if (Equals(Value, OldValue)) return false; - if (ValueValidator is not null && !ValueValidator(Value)) return false; - Setter(Value); - OnPropertyChanged(PropertyName); - return true; - } - - /// Асинхронный метод изменения значения свойства - /// Тип значения свойства - /// Поле, хранящее значение свойства - /// Новое значение свойства - /// Имя свойства - /// Задача, возвращающая истину, если свойство изменило своё значение - protected virtual ValueTask SetAsync([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, [CallerMemberName] string PropertyName = null!) - { - if (Equals(field, value)) return new(false); - field = value; - - return SetPropertyAsync(PropertyName); - async ValueTask SetPropertyAsync(string property) - { - await Task.Factory.StartNew(s => OnPropertyChanged((string)s!), property); - return true; - } - } /// public override object ProvideValue(IServiceProvider Service) From f358ff3eebabe5dce77bbdef56ac1fe8bdf7f3cb Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:53:38 +0300 Subject: [PATCH 13/33] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=BE?= =?UTF-8?q?=D0=B2=20ViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены и обновлены XML-комментарии для методов SuppressPropertyChanges, Set и SetValue. Улучшено описание параметров, типов и возвращаемых значений для повышения понятности и поддержки автодокументирования. --- .../ViewModel.PropertyChangedEventsSuppressor.cs | 4 +--- MathCore.WPF/ViewModels/ViewModel.Set.cs | 7 +++++++ MathCore.WPF/ViewModels/ViewModel.SetValue.cs | 13 +++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/MathCore.WPF/ViewModels/ViewModel.PropertyChangedEventsSuppressor.cs b/MathCore.WPF/ViewModels/ViewModel.PropertyChangedEventsSuppressor.cs index 071fdc6..5581cb4 100644 --- a/MathCore.WPF/ViewModels/ViewModel.PropertyChangedEventsSuppressor.cs +++ b/MathCore.WPF/ViewModels/ViewModel.PropertyChangedEventsSuppressor.cs @@ -53,9 +53,7 @@ public void Dispose() } } - /// - /// Возвращает или создает объект, подавляющий события изменения свойств в течение указанного таймаута. - /// + /// Возвращает или создает объект, подавляющий события изменения свойств в течение указанного таймаута /// Время, в течение которого будут подавляться события изменения свойств. /// Объект, подавляющий события изменения свойств. public PropertyChangedEventsSuppressor SuppressPropertyChanges(TimeSpan RegistrationTimeout = default) diff --git a/MathCore.WPF/ViewModels/ViewModel.Set.cs b/MathCore.WPF/ViewModels/ViewModel.Set.cs index bcd4e41..1e0cbf3 100644 --- a/MathCore.WPF/ViewModels/ViewModel.Set.cs +++ b/MathCore.WPF/ViewModels/ViewModel.Set.cs @@ -209,6 +209,13 @@ public static bool Set( return true; } + /// Метод установки значения свойства, осуществляющий генерацию события изменения свойства + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Значение свойства, которое надо установить + /// Метод уведомления об изменении значения свойства + /// Имя свойства + /// Истина, если значение свойства установлено успешно public static bool Set( ref T? field, T? value, diff --git a/MathCore.WPF/ViewModels/ViewModel.SetValue.cs b/MathCore.WPF/ViewModels/ViewModel.SetValue.cs index d04e824..aad0cfe 100644 --- a/MathCore.WPF/ViewModels/ViewModel.SetValue.cs +++ b/MathCore.WPF/ViewModels/ViewModel.SetValue.cs @@ -6,6 +6,12 @@ namespace MathCore.WPF.ViewModels; public partial class ViewModel { + /// Установить значение свойства с уведомлением об изменении + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Новое значение свойства + /// Имя свойства + /// Результат установки значения [NotifyPropertyChangedInvocator] protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, [CallerMemberName] string PropertyName = null!) { @@ -16,6 +22,13 @@ protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nam return new(true, old_value, value, this); } + /// Установить значение свойства с проверкой и уведомлением об изменении + /// Тип значения свойства + /// Ссылка на поле, хранящее значение свойства + /// Новое значение свойства + /// Проверка возможности установки значения + /// Имя свойства + /// Результат установки значения [NotifyPropertyChangedInvocator] protected virtual SetValueResult SetValue([Attributes.NotNullIfNotNull(nameof(value))] ref T? field, T? value, Func value_checker, [CallerMemberName] string PropertyName = null!) { From 4bb4b03fe9d48132adfad97bb11e8608e0c9791a Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 16:59:49 +0300 Subject: [PATCH 14/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B8=20XML-=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8=20=D0=BA?= =?UTF-8?q?=20ValidationRules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В репозиторий добавлен README.MD с подробным описанием всех правил валидации, их параметров и примерами использования в XAML. В исходные файлы правил (StringLength, ValueGreaterThan, ValueLessThan, ValueInInterval) добавлены XML-комментарии к классам, свойствам, конструкторам и методам Validate для улучшения автодокументации и удобства использования в XAML. --- MathCore.WPF/ValidationRules/README.MD | 331 ++++++++++++++++++ MathCore.WPF/ValidationRules/StringLength.cs | 15 +- .../ValidationRules/ValueGreaterThan.cs | 12 +- .../ValidationRules/ValueInInterval.cs | 10 + MathCore.WPF/ValidationRules/ValueLessThan.cs | 12 +- 5 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 MathCore.WPF/ValidationRules/README.MD diff --git a/MathCore.WPF/ValidationRules/README.MD b/MathCore.WPF/ValidationRules/README.MD new file mode 100644 index 0000000..7a678e3 --- /dev/null +++ b/MathCore.WPF/ValidationRules/README.MD @@ -0,0 +1,331 @@ +# Правила валидации + +В каталоге `ValidationRules` находятся правила валидации для WPF. Все правила используются в `ValidationRules` коллекции привязок. + +## Общие параметры + +Некоторые правила наследуются от базовых классов: + +- `ValueValidation`: параметр `ErrorMessage` для сообщения об ошибке. +- `NullValueValidation`: параметры `AllowNull` и `NullReferenceMessage` для управления `null`. +- `FormattedValueValidation`: параметр `FormatErrorMessage` для ошибок формата. + +## StringLength + +Проверяет длину строки. + +Параметры: +- `Length` — эталонная длина. +- `Equal` — разрешить равенство длины. +- `Less` — разрешить длину меньше эталонной. +- `Gatherer` — разрешить длину больше эталонной. +- `AllowNull` — разрешить `null`. +- `AllowNotString` — разрешить нестроковые значения. + +Пример: + +```xml + + + + + + + + + +``` + +## RegExp + +Проверяет строку на соответствие регулярному выражению. + +Параметры: +- `Expression` — регулярное выражение. +- `AllowNotString` — разрешить нестроковые значения. +- `NotStringErrorMessage` — сообщение для нестроковых значений. +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для несовпадения. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## NotNull + +Проверяет, что значение не `null`. + +Параметры: +- `ErrorMessage` — сообщение об ошибке. + +Пример: + +```xml + + + + + + + + + +``` + +## IsDouble + +Проверяет, что значение может быть преобразовано в `double`. + +Параметры: +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## IsInteger + +Проверяет, что значение может быть преобразовано в `int`. + +Параметры: +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## IsNumeric + +Проверяет, что значение может быть преобразовано в `double` или `int`. + +Параметры: +- `IntegerOnly` — проверять только целые числа. +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## NotNaN + +Проверяет, что значение — `double` и оно не `NaN`. + +Параметры: +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## ValueGreaterThan + +Проверяет, что значение больше указанного. + +Параметры: +- `Value` — эталонное значение. +- `IsEqual` — разрешить равенство. +- `ErrorMessage` — сообщение об ошибке. + +Пример: + +```xml + + + + + + + + + +``` + +## ValueLessThan + +Проверяет, что значение меньше указанного. + +Параметры: +- `Value` — эталонное значение. +- `IsEquals` — разрешить равенство. +- `ErrorMessage` — сообщение об ошибке. + +Пример: + +```xml + + + + + + + + + +``` + +## ValueInInterval + +Проверяет, что значение находится внутри интервала. + +Параметры: +- `Min` — минимальная граница. +- `Max` — максимальная граница. +- `MinEquals` — разрешить равенство `Min`. +- `MaxEquals` — разрешить равенство `Max`. +- `ErrorMessage` — сообщение об ошибке. + +Пример: + +```xml + + + + + + + + + +``` + +## DirectoryExist + +Проверяет существование директории. + +Параметры: +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## FileExist + +Проверяет существование файла. + +Параметры: +- `AllowNull` — разрешить `null`. +- `NullReferenceMessage` — сообщение для `null`. +- `FormatErrorMessage` — сообщение для ошибок формата. +- `ErrorMessage` — сообщение по умолчанию. + +Пример: + +```xml + + + + + + + + + +``` + +## PathExist + +Проверяет существование пути в файловой системе. + +Параметры: +- `AllowNull` — разрешить `null`. +- `ErrorMessage` — сообщение об ошибке. + +Пример: + +```xml + + + + + + + + + +``` + +## Подключение пространства имён + +Примеры выше используют префикс `vr`, подключаемый так: + +```xml +xmlns:vr="clr-namespace:MathCore.WPF.ValidationRules" +``` diff --git a/MathCore.WPF/ValidationRules/StringLength.cs b/MathCore.WPF/ValidationRules/StringLength.cs index cba754e..91a1703 100644 --- a/MathCore.WPF/ValidationRules/StringLength.cs +++ b/MathCore.WPF/ValidationRules/StringLength.cs @@ -11,26 +11,39 @@ namespace MathCore.WPF.ValidationRules; +/// Проверка длины строки public class StringLength : ValidationRule { + /// Разрешить пустое значение public bool AllowNull { get; set; } + /// Разрешить значения, не являющиеся строкой public bool AllowNotString { get; set; } + /// Эталонная длина строки [ConstructorArgument(nameof(Length))] public int Length { get; set; } + /// Разрешить равенство длины эталонной public bool Equal { get; set; } = true; + /// Разрешить длину меньше эталонной public bool Less { get; set; } + /// Разрешить длину больше эталонной public bool Gatherer { get; set; } + /// Инициализация нового экземпляра public StringLength() { } + /// Инициализация нового экземпляра + /// Эталонная длина строки public StringLength(int Length) => this.Length = Length; - /// + /// Проверка длины строки + /// Проверяемое значение + /// Сведения о текущей культуре + /// Результат проверки на соответствие длины public override ValidationResult Validate(object? value, CultureInfo c) { var valid = ValidationResult.ValidResult; diff --git a/MathCore.WPF/ValidationRules/ValueGreaterThan.cs b/MathCore.WPF/ValidationRules/ValueGreaterThan.cs index d18f0d2..1b672a7 100644 --- a/MathCore.WPF/ValidationRules/ValueGreaterThan.cs +++ b/MathCore.WPF/ValidationRules/ValueGreaterThan.cs @@ -11,20 +11,30 @@ namespace MathCore.WPF.ValidationRules; +/// Проверка значения больше заданного public class ValueGreaterThan : ValidationRule { + /// Эталонное значение [ConstructorArgument(nameof(Value))] public double Value { get; set; } + /// Разрешить равенство эталонному значению public bool IsEqual { get; set; } + /// Сообщение об ошибке проверки public string? ErrorMessage { get; set; } + /// Инициализация нового экземпляра public ValueGreaterThan() { } + /// Инициализация нового экземпляра + /// Эталонное значение public ValueGreaterThan(double value) => Value = value; - /// + /// Проверка значения на превышение эталонного + /// Проверяемое значение + /// Сведения о текущей культуре + /// Результат проверки на превышение или равенство public override ValidationResult Validate(object? value, CultureInfo c) { if (value is null) return new(false, "Значение не указано"); diff --git a/MathCore.WPF/ValidationRules/ValueInInterval.cs b/MathCore.WPF/ValidationRules/ValueInInterval.cs index 067b822..e51c702 100644 --- a/MathCore.WPF/ValidationRules/ValueInInterval.cs +++ b/MathCore.WPF/ValidationRules/ValueInInterval.cs @@ -8,18 +8,28 @@ namespace MathCore.WPF.ValidationRules; +/// Проверка значения на принадлежность интервалу public class ValueInInterval : ValidationRule { + /// Минимальная граница интервала public double Min { get; set; } = double.NegativeInfinity; + /// Максимальная граница интервала public double Max { get; set; } = double.PositiveInfinity; + /// Разрешить равенство минимальной границе public bool MinEquals { get; set; } = true; + /// Разрешить равенство максимальной границе public bool MaxEquals { get; set; } = true; + /// Сообщение об ошибке проверки public string? ErrorMessage { get; set; } + /// Проверка значения на принадлежность интервалу + /// Проверяемое значение + /// Сведения о текущей культуре + /// Результат проверки на принадлежность интервалу /// public override ValidationResult Validate(object? value, CultureInfo c) { diff --git a/MathCore.WPF/ValidationRules/ValueLessThan.cs b/MathCore.WPF/ValidationRules/ValueLessThan.cs index 8ef27c9..fc292af 100644 --- a/MathCore.WPF/ValidationRules/ValueLessThan.cs +++ b/MathCore.WPF/ValidationRules/ValueLessThan.cs @@ -11,20 +11,30 @@ namespace MathCore.WPF.ValidationRules; +/// Проверка значения меньше заданного public class ValueLessThan : ValidationRule { + /// Эталонное значение [ConstructorArgument(nameof(Value))] public double Value { get; set; } + /// Разрешить равенство эталонному значению public bool IsEquals { get; set; } + /// Сообщение об ошибке проверки public string? ErrorMessage { get; set; } + /// Инициализация нового экземпляра public ValueLessThan() { } + /// Инициализация нового экземпляра + /// Эталонное значение public ValueLessThan(double value) => Value = value; - /// + /// Проверка значения на меньшее или равное эталонному + /// Проверяемое значение + /// Сведения о текущей культуре + /// Результат проверки на меньшее или равное значение public override ValidationResult Validate(object? value, CultureInfo CultureInfo) { if (value is null) return new(false, "Значение не указано"); From 3a83dabfb467214fae8970036c364c3985b5620e Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 17:11:08 +0300 Subject: [PATCH 15/33] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3,=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Taskbar/Taskbar.cs | 32 +- MathCore.WPF/UIEvents/ModelEventArgs.cs | 12 +- MathCore.WPF/pInvoke/SC.cs | 37 +- MathCore.WPF/pInvoke/User32.cs | 89 ++++- MathCore.WPF/pInvoke/WM.cs | 509 ++++++++++++------------ 5 files changed, 380 insertions(+), 299 deletions(-) diff --git a/MathCore.WPF/Taskbar/Taskbar.cs b/MathCore.WPF/Taskbar/Taskbar.cs index c974f76..3f8a77c 100644 --- a/MathCore.WPF/Taskbar/Taskbar.cs +++ b/MathCore.WPF/Taskbar/Taskbar.cs @@ -19,16 +19,16 @@ private enum ABS private const string ClassName = "Shell_TrayWnd"; private static AppBarData _AppBarData; - /// Static initializer of the class. + /// Статический инициализатор класса static Taskbar() => _AppBarData = new() { cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), - hWnd = User32.FindWindow(ClassName, null) + hWnd = User32.FindWindow(ClassName, null) }; - /// Gets a value indicating whether the taskbar is always on top of other windows. - /// true if the taskbar is always on top of other windows; otherwise, false. - /// This property always returns false on Windows 7 and newer. + /// Возвращает значение, указывающее, что панель задач всегда поверх других окон + /// true, если панель задач всегда поверх других окон; иначе false + /// Это свойство всегда возвращает false в Windows 7 и новее public static bool AlwaysOnTop { get @@ -38,8 +38,8 @@ public static bool AlwaysOnTop } } - /// Gets a value indicating whether the taskbar is automatically hidden when inactive. - /// true if the taskbar is set to auto-hide is enabled; otherwise, false. + /// Возвращает значение, указывающее, что панель задач автоматически скрывается при неактивности + /// true, если включено автоскрытие панели задач; иначе false public static bool AutoHide { get @@ -49,7 +49,7 @@ public static bool AutoHide } } - /// Gets the current display bounds of the taskbar. + /// Возвращает текущие границы отображения панели задач public static Rectangle CurrentBounds { get @@ -61,7 +61,7 @@ public static Rectangle CurrentBounds } } - /// Gets the display bounds when the taskbar is fully visible. + /// Возвращает границы отображения при полностью видимой панели задач public static Rectangle DisplayBounds => RefreshBoundsAndPosition() ? Rectangle.FromLTRB( @@ -71,24 +71,24 @@ public static Rectangle CurrentBounds _AppBarData.rect.Bottom) : CurrentBounds; - /// Gets the taskbar's window handle. + /// Возвращает дескриптор окна панели задач public static IntPtr Handle => _AppBarData.hWnd; - /// Gets the taskbar's position on the screen. + /// Возвращает положение панели задач на экране public static TaskbarPosition Position => RefreshBoundsAndPosition() ? (TaskbarPosition)_AppBarData.uEdge : TaskbarPosition.Unknown; private const int SW_HIDE = 0; - /// Hides the taskbar. - public static void Hide() => User32.ShowWindow(Handle, SW_HIDE); + /// Скрывает панель задач + public static void Hide() => _ = User32.ShowWindow(Handle, SW_HIDE); private const int SW_SHOW = 1; - /// Shows the taskbar. - public static void Show() => User32.ShowWindow(Handle, SW_SHOW); + /// Показывает панель задач + public static void Show() => _ = User32.ShowWindow(Handle, SW_SHOW); private static bool RefreshBoundsAndPosition() => - //! SHAppBarMessage returns IntPtr.Zero **if it fails** + //! SHAppBarMessage возвращает IntPtr.Zero при ошибке Shell32.SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref _AppBarData) != IntPtr.Zero; } \ No newline at end of file diff --git a/MathCore.WPF/UIEvents/ModelEventArgs.cs b/MathCore.WPF/UIEvents/ModelEventArgs.cs index 4129c2e..66d96bc 100644 --- a/MathCore.WPF/UIEvents/ModelEventArgs.cs +++ b/MathCore.WPF/UIEvents/ModelEventArgs.cs @@ -1,14 +1,8 @@ namespace MathCore.WPF.UIEvents; -public class ModelEventArgs : EventArgs +public class ModelEventArgs(object? Model, object? Parameter) : EventArgs { - public object? Model { get; } + public object? Model { get; } = Model; - public object? Parameter { get; } - - public ModelEventArgs(object? Model, object? Parameter) - { - this.Model = Model; - this.Parameter = Parameter; - } + public object? Parameter { get; } = Parameter; } \ No newline at end of file diff --git a/MathCore.WPF/pInvoke/SC.cs b/MathCore.WPF/pInvoke/SC.cs index a698e90..eceb2d1 100644 --- a/MathCore.WPF/pInvoke/SC.cs +++ b/MathCore.WPF/pInvoke/SC.cs @@ -2,43 +2,66 @@ namespace MathCore.WPF.pInvoke; -/// System Command +/// Системные команды [SuppressMessage("ReSharper", "InconsistentNaming"), SuppressMessage("ReSharper", "IdentifierTypo"), SuppressMessage("ReSharper", "UnusedMember.Global")] internal enum SC : uint { + /// Изменить размер окна SIZE = 0xF000, + /// Переместить окно MOVE = 0xF010, + /// Свернуть окно MINIMIZE = 0xF020, + /// Развернуть окно MAXIMIZE = 0xF030, + /// Активировать следующее окно NEXTWINDOW = 0xF040, + /// Активировать предыдущее окно PREVWINDOW = 0xF050, + /// Закрыть окно CLOSE = 0xF060, + /// Вертикальная прокрутка VSCROLL = 0xF070, + /// Горизонтальная прокрутка HSCROLL = 0xF080, + /// Открыть системное меню мышью MOUSEMENU = 0xF090, + /// Открыть системное меню с клавиатуры KEYMENU = 0xF100, + /// Упорядочить значки ARRANGE = 0xF110, + /// Восстановить окно RESTORE = 0xF120, + /// Открыть список задач TASKLIST = 0xF130, + /// Запустить экранную заставку SCREENSAVE = 0xF140, + /// Обработать системную горячую клавишу HOTKEY = 0xF150, - #region WINVER >= 0x0400 - Win95 - //#if(WINVER >= 0x0400) //Win95 + #region WINVER >= 0x0400 - Windows 95 + //#if(WINVER >= 0x0400) //Windows 95 + /// Команда по умолчанию DEFAULT = 0xF160, + /// Управление питанием монитора MONITORPOWER = 0xF170, + /// Контекстная справка CONTEXTHELP = 0xF180, + /// Разделитель меню SEPARATOR = 0xF00F, - //#endif /* WINVER >= 0x0400 */ + //#endif /* WINVER >= 0x0400 */ #endregion - #region WINVER >= 0x0600 - Vista - //#if(WINVER >= 0x0600) //Vista + #region WINVER >= 0x0600 - Windows Vista + //#if(WINVER >= 0x0600) //Windows Vista + /// Флаг безопасного режима ISSECURE = 0x00000001, - //#endif /* WINVER >= 0x0600 */ + //#endif /* WINVER >= 0x0600 */ #endregion + /// Синоним MINIMIZE [Obsolete] ICON = MINIMIZE, + /// Синоним MAXIMIZE [Obsolete] ZOOM = MAXIMIZE, } \ No newline at end of file diff --git a/MathCore.WPF/pInvoke/User32.cs b/MathCore.WPF/pInvoke/User32.cs index 2beb6b1..820586e 100644 --- a/MathCore.WPF/pInvoke/User32.cs +++ b/MathCore.WPF/pInvoke/User32.cs @@ -11,67 +11,114 @@ internal static class User32 { private const string FileName = "user32.dll"; + /// Отправляет сообщение окну + /// Дескриптор окна + /// Код сообщения + /// Дополнительный параметр сообщения + /// Дополнительный параметр сообщения + /// Результат обработки сообщения [DllImport(FileName, CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(this IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + + /// Отправляет сообщение окну + /// Дескриптор окна + /// Код сообщения + /// Дополнительный параметр сообщения + /// Дополнительный параметр сообщения + /// Результат обработки сообщения [DllImport(FileName, CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(this IntPtr hWnd, WM Msg, IntPtr wParam, IntPtr lParam); + /// Отправляет сообщение окну + /// Окно-получатель + /// Код сообщения + /// Дополнительный параметр сообщения + /// Дополнительный параметр сообщения + /// Результат обработки сообщения public static IntPtr SendMessage(this Window window, uint Msg, IntPtr wParam, IntPtr lParam) => SendMessage(window.GetWindowHandle(), Msg, wParam, lParam); + /// Отправляет сообщение окну + /// Окно-получатель + /// Код сообщения + /// Дополнительный параметр сообщения + /// Дополнительный параметр сообщения + /// Результат обработки сообщения public static IntPtr SendMessage(this Window window, WM Msg, IntPtr wParam, IntPtr lParam) => SendMessage(window.GetWindowHandle(), Msg, wParam, lParam); + /// Отправляет сообщение окну + /// Окно-получатель + /// Код сообщения + /// Дополнительный параметр сообщения + /// Дополнительный параметр сообщения + /// Результат обработки сообщения public static IntPtr SendMessage(this Window window, WM Msg, SC wParam, IntPtr lParam = default) => SendMessage(window.GetWindowHandle(), (uint)Msg, (IntPtr)wParam, lParam == default ? (IntPtr)' ' : lParam); + /// Получает сведения о мониторе + /// Дескриптор монитора + /// Структура с информацией о мониторе + /// true, если вызов успешен; иначе false [DllImport(FileName)] public static extern bool GetMonitorInfo(this IntPtr hMonitor, MonitorInfo lpmi); + /// Получает текущую позицию курсора + /// Координаты курсора + /// true, если вызов успешен; иначе false [DllImport(FileName)] public static extern bool GetCursorPos(ref System.Windows.Point lpPoint); + /// Возвращает монитор, связанный с окном + /// Дескриптор окна + /// Флаги поиска монитора + /// Дескриптор монитора [DllImport(FileName)] public static extern IntPtr MonitorFromWindow(this IntPtr handle, int flags); - /// Define a system-wide hot key. + /// Регистрирует системную горячую клавишу /// - /// A handle to the window that will receive WM_HOTKEY messages generated by the - /// hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the - /// message queue of the calling thread and must be processed in the message loop. + /// Дескриптор окна, которое будет получать сообщения WM_HOTKEY, сгенерированные горячей клавишей + /// Если параметр равен NULL, сообщения WM_HOTKEY отправляются в очередь сообщений вызывающего потока и должны обрабатываться в цикле сообщений /// /// - /// The identifier of the hot key. If the hWnd parameter is NULL, then the hot - /// key is associated with the current thread rather than with a particular - /// window. + /// Идентификатор горячей клавиши + /// Если параметр hWnd равен NULL, горячая клавиша связывается с текущим потоком, а не с конкретным окном /// /// - /// The keys that must be pressed in combination with the key specified by the - /// uVirtKey parameter in order to generate the WM_HOTKEY message. The fsModifiers - /// parameter can be a combination of the following values. - /// MOD_ALT 0x0001 - /// MOD_CONTROL 0x0002 - /// MOD_SHIFT 0x0004 - /// MOD_WIN 0x0008 + /// Клавиши-модификаторы, которые должны быть нажаты вместе с клавишей, указанной в параметре key, чтобы сгенерировать сообщение WM_HOTKEY + /// Возможные значения: MOD_ALT (0x0001), MOD_CONTROL (0x0002), MOD_SHIFT (0x0004), MOD_WIN (0x0008) /// - /// The virtual-key code of the hot key. + /// Виртуальный код клавиши горячей комбинации + /// true, если регистрация успешна; иначе false [DllImport(FileName, CharSet = CharSet.Auto, SetLastError = true)] public static extern bool RegisterHotKey(IntPtr hWnd, int id, ModifierKeys modifiers, Keys key); - /// Frees a hot key previously registered by the calling thread. + /// Освобождает ранее зарегистрированную горячую клавишу /// - /// A handle to the window associated with the hot key to be freed. This parameter - /// should be NULL if the hot key is not associated with a window. - /// - /// - /// The identifier of the hot key to be freed. + /// Дескриптор окна, связанного с освобождаемой горячей клавишей + /// Если горячая клавиша не связана с окном, параметр должен быть NULL /// + /// Идентификатор освобождаемой горячей клавиши + /// true, если операция успешна; иначе false [DllImport(FileName, CharSet = CharSet.Auto, SetLastError = true)] public static extern bool UnregisterHotKey(IntPtr hWnd, int id); + /// Показывает или скрывает окно + /// Дескриптор окна + /// Код команды отображения + /// Результат выполнения операции [DllImport(FileName)] public static extern int ShowWindow(IntPtr hwnd, int command); + /// Находит окно по классу и заголовку + /// Имя класса окна + /// Заголовок окна + /// Дескриптор найденного окна [DllImport(FileName, SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + /// Возвращает прямоугольник окна в экранных координатах + /// Дескриптор окна + /// Прямоугольник окна + /// true, если вызов успешен; иначе false [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowRect(IntPtr hWnd, ref Rect lpRect); diff --git a/MathCore.WPF/pInvoke/WM.cs b/MathCore.WPF/pInvoke/WM.cs index 09a9e11..60a1988 100644 --- a/MathCore.WPF/pInvoke/WM.cs +++ b/MathCore.WPF/pInvoke/WM.cs @@ -4,520 +4,537 @@ namespace MathCore.WPF.pInvoke; -/// Windows Messages +/// Сообщения Windows [SuppressMessage("ReSharper", "InconsistentNaming"), SuppressMessage("ReSharper", "UnusedMember.Global")] internal enum WM : uint { - /// The WM_NULL message performs no operation. An application sends the WM_NULL message if it wants to post a message that the recipient window will ignore. + /// Сообщение WM_NULL не выполняет никаких действий, приложение отправляет его, если хочет опубликовать сообщение, которое окно-получатель проигнорирует NULL = 0x0000, - /// The WM_CREATE message is sent when an application requests that a window be created by calling the CreateWindowEx or CreateWindow function. (The message is sent before the function returns.) The window procedure of the new window receives this message after the window is created, but before the window becomes visible. + /// Сообщение WM_CREATE отправляется, когда приложение запрашивает создание окна вызовом функций CreateWindowEx или CreateWindow, сообщение отправляется до возврата из функции, процедура окна нового окна получает это сообщение после создания окна, но до того, как окно станет видимым CREATE = 0x0001, /// - /// The WM_DESTROY message is sent when a window is being destroyed. It is sent to the window procedure of the window being destroyed after the window is removed from the screen. - /// This message is sent first to the window being destroyed and then to the child windows (if any) as they are destroyed. During the processing of the message, it can be assumed that all child windows still exist. - /// /// + /// Сообщение WM_DESTROY отправляется, когда окно уничтожается, оно отправляется процедуре окна уничтожаемого окна после удаления окна с экрана + /// Это сообщение сначала отправляется уничтожаемому окну, затем дочерним окнам (если они есть) по мере их уничтожения + /// Во время обработки сообщения можно считать, что все дочерние окна ещё существуют + /// DESTROY = 0x0002, - /// The WM_MOVE message is sent after a window has been moved. + /// Сообщение WM_MOVE отправляется после перемещения окна MOVE = 0x0003, - /// The WM_SIZE message is sent to a window after its size has changed. + /// Сообщение WM_SIZE отправляется окну после изменения его размеров SIZE = 0x0005, - /// The WM_ACTIVATE message is sent to both the window being activated and the window being deactivated. If the windows use the same input queue, the message is sent synchronously, first to the window procedure of the top-level window being deactivated, then to the window procedure of the top-level window being activated. If the windows use different input queues, the message is sent asynchronously, so the window is activated immediately. + /// Сообщение WM_ACTIVATE отправляется окну, которое активируется, и окну, которое деактивируется, при общей очереди ввода сообщение отправляется синхронно, сначала процедуре деактивируемого окна верхнего уровня, затем процедуре активируемого окна верхнего уровня, при разных очередях ввода сообщение отправляется асинхронно, и окно активируется немедленно ACTIVATE = 0x0006, - /// The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. + /// Сообщение WM_SETFOCUS отправляется окну после получения фокуса клавиатуры SETFOCUS = 0x0007, - /// The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard focus. + /// Сообщение WM_KILLFOCUS отправляется окну непосредственно перед потерей фокуса клавиатуры KILLFOCUS = 0x0008, - /// The WM_ENABLE message is sent when an application changes the enabled state of a window. It is sent to the window whose enabled state is changing. This message is sent before the EnableWindow function returns, but after the enabled state (WS_DISABLED style bit) of the window has changed. + /// Сообщение WM_ENABLE отправляется, когда приложение изменяет состояние доступности окна, оно отправляется окну, чьё состояние изменяется, сообщение отправляется до возврата из функции EnableWindow, но после изменения стиля WS_DISABLED ENABLE = 0x000A, - /// An application sends the WM_SETREDRAW message to a window to allow changes in that window to be redrawn or to prevent changes in that window from being redrawn. + /// Сообщение WM_SETREDRAW позволяет разрешить или запретить перерисовку изменений в окне SETREDRAW = 0x000B, - /// An application sends a WM_SETTEXT message to set the text of a window. + /// Сообщение WM_SETTEXT устанавливает текст окна SETTEXT = 0x000C, - /// An application sends a WM_GETTEXT message to copy the text that corresponds to a window into a buffer provided by the caller. + /// Сообщение WM_GETTEXT копирует текст окна в буфер, предоставленный вызывающим кодом GETTEXT = 0x000D, - /// An application sends a WM_GETTEXTLENGTH message to determine the length, in characters, of the text associated with a window. + /// Сообщение WM_GETTEXTLENGTH определяет длину текста, связанного с окном, в символах GETTEXTLENGTH = 0x000E, - /// The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window. The message is sent when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage function when the application obtains a WM_PAINT message by using the GetMessage or PeekMessage function. + /// Сообщение WM_PAINT отправляется, когда система или другое приложение запрашивает перерисовку части окна приложения, сообщение отправляется при вызове UpdateWindow или RedrawWindow, либо при обработке WM_PAINT, полученного через GetMessage или PeekMessage PAINT = 0x000F, - /// The WM_CLOSE message is sent as a signal that a window or an application should terminate. + /// Сообщение WM_CLOSE отправляется как сигнал о завершении окна или приложения CLOSE = 0x0010, /// - /// The WM_QUERYENDSESSION message is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero. - /// After processing this message, the system sends the WM_ENDSESSION message with the wParam parameter set to the results of the WM_QUERYENDSESSION message. + /// Сообщение WM_QUERYENDSESSION отправляется, когда пользователь выбирает завершение сеанса или приложение вызывает одну из функций завершения работы системы + /// Если какое-либо приложение возвращает ноль, сеанс не завершается, система прекращает отправку WM_QUERYENDSESSION, как только одно приложение возвращает ноль + /// После обработки этого сообщения система отправляет WM_ENDSESSION с параметром wParam, равным результату WM_QUERYENDSESSION /// QUERYENDSESSION = 0x0011, - /// The WM_QUERYOPEN message is sent to an icon when the user requests that the window be restored to its previous size and position. + /// Сообщение WM_QUERYOPEN отправляется значку, когда пользователь запрашивает восстановление окна до прежнего размера и положения QUERYOPEN = 0x0013, - /// The WM_ENDSESSION message is sent to an application after the system processes the results of the WM_QUERYENDSESSION message. The WM_ENDSESSION message informs the application whether the session is ending. + /// Сообщение WM_ENDSESSION отправляется приложению после обработки системой результатов WM_QUERYENDSESSION, оно сообщает приложению, завершается ли сеанс ENDSESSION = 0x0016, - /// The WM_QUIT message indicates a request to terminate an application and is generated when the application calls the PostQuitMessage function. It causes the GetMessage function to return zero. + /// Сообщение WM_QUIT указывает на запрос завершения приложения и генерируется при вызове PostQuitMessage, оно приводит к возврату нуля из GetMessage QUIT = 0x0012, - /// The WM_ERASEBKGND message is sent when the window background must be erased (for example, when a window is resized). The message is sent to prepare an invalidated portion of a window for painting. + /// Сообщение WM_ERASEBKGND отправляется, когда фон окна должен быть очищен, например при изменении размеров, сообщение подготавливает недействительную часть окна к рисованию ERASEBKGND = 0x0014, - /// This message is sent to all top-level windows when a change is made to a system color setting. + /// Это сообщение отправляется всем окнам верхнего уровня при изменении системных цветов SYSCOLORCHANGE = 0x0015, - /// The WM_SHOWWINDOW message is sent to a window when the window is about to be hidden or shown. + /// Сообщение WM_SHOWWINDOW отправляется окну перед скрытием или показом SHOWWINDOW = 0x0018, /// - /// An application sends the WM_WININICHANGE message to all top-level windows after making a change to the WIN.INI file. The SystemParametersInfo function sends this message after an application uses the function to change a setting in WIN.INI. - /// Note The WM_WININICHANGE message is provided only for compatibility with earlier versions of the system. Applications should use the WM_SETTINGCHANGE message. + /// Сообщение WM_WININICHANGE отправляется всем окнам верхнего уровня после изменения файла WIN.INI + /// Функция SystemParametersInfo отправляет это сообщение после изменения параметра в WIN.INI + /// Примечание: сообщение WM_WININICHANGE предоставляется только для совместимости с ранними версиями системы, приложениям следует использовать WM_SETTINGCHANGE /// WININICHANGE = 0x001A, /// - /// An application sends the WM_WININICHANGE message to all top-level windows after making a change to the WIN.INI file. The SystemParametersInfo function sends this message after an application uses the function to change a setting in WIN.INI. - /// Note The WM_WININICHANGE message is provided only for compatibility with earlier versions of the system. Applications should use the WM_SETTINGCHANGE message. + /// Сообщение WM_WININICHANGE отправляется всем окнам верхнего уровня после изменения файла WIN.INI + /// Функция SystemParametersInfo отправляет это сообщение после изменения параметра в WIN.INI + /// Примечание: сообщение WM_WININICHANGE предоставляется только для совместимости с ранними версиями системы, приложениям следует использовать WM_SETTINGCHANGE /// SETTINGCHANGE = WININICHANGE, - /// The WM_DEVMODECHANGE message is sent to all top-level windows whenever the user changes device-mode settings. + /// Сообщение WM_DEVMODECHANGE отправляется всем окнам верхнего уровня при изменении параметров режима устройства DEVMODECHANGE = 0x001B, - /// The WM_ACTIVATEAPP message is sent when a window belonging to a different application than the active window is about to be activated. The message is sent to the application whose window is being activated and to the application whose window is being deactivated. + /// Сообщение WM_ACTIVATEAPP отправляется, когда окно другого приложения собирается стать активным, сообщение отправляется приложению активируемого окна и приложению деактивируемого окна ACTIVATEAPP = 0x001C, - /// An application sends the WM_FONTCHANGE message to all top-level windows in the system after changing the pool of font resources. + /// Сообщение WM_FONTCHANGE отправляется всем окнам верхнего уровня после изменения пула шрифтов FONTCHANGE = 0x001D, - /// A message that is sent whenever there is a change in the system time. + /// Сообщение отправляется при изменении системного времени TIMECHANGE = 0x001E, - /// The WM_CANCELMODE message is sent to cancel certain modes, such as mouse capture. For example, the system sends this message to the active window when a dialog box or message box is displayed. Certain functions also send this message explicitly to the specified window regardless of whether it is the active window. For example, the EnableWindow function sends this message when disabling the specified window. + /// Сообщение WM_CANCELMODE отправляется для отмены режимов, например захвата мыши, система отправляет его активному окну при показе диалогового окна или окна сообщений, некоторые функции отправляют его явно указанному окну независимо от активности, например EnableWindow при отключении окна CANCELMODE = 0x001F, - /// The WM_SETCURSOR message is sent to a window if the mouse causes the cursor to move within a window and mouse input is not captured. + /// Сообщение WM_SETCURSOR отправляется окну, если мышь перемещает курсор в пределах окна и ввод мыши не захвачен SETCURSOR = 0x0020, - /// The WM_MOUSEACTIVATE message is sent when the cursor is in an inactive window and the user presses a mouse button. The parent window receives this message only if the child window passes it to the DefWindowProc function. + /// Сообщение WM_MOUSEACTIVATE отправляется, когда курсор находится в неактивном окне и пользователь нажимает кнопку мыши, родительское окно получает это сообщение только если дочернее окно передаёт его в DefWindowProc MOUSEACTIVATE = 0x0021, - /// The WM_CHILDACTIVATE message is sent to a child window when the user clicks the window's title bar or when the window is activated, moved, or sized. + /// Сообщение WM_CHILDACTIVATE отправляется дочернему окну, когда пользователь щёлкает по заголовку окна или когда окно активируется, перемещается или изменяет размер CHILDACTIVATE = 0x0022, - /// The WM_QUEUESYNC message is sent by a computer-based training (CBT) application to separate user-input messages from other messages sent through the WH_JOURNALPLAYBACK Hook procedure. + /// Сообщение WM_QUEUESYNC отправляется приложением CBT для отделения сообщений пользовательского ввода от других сообщений, отправляемых через процедуру перехвата WH_JOURNALPLAYBACK QUEUESYNC = 0x0023, - /// The WM_GETMINMAXINFO message is sent to a window when the size or position of the window is about to change. An application can use this message to override the window's default maximized size and position, or its default minimum or maximum tracking size. + /// Сообщение WM_GETMINMAXINFO отправляется окну, когда размер или положение окна собираются измениться, приложение может использовать это сообщение для переопределения размеров и положения максимизации или минимального и максимального размера отслеживания GETMINMAXINFO = 0x0024, - /// Windows NT 3.51 and earlier: The WM_PAINTICON message is sent to a minimized window when the icon is to be painted. This message is not sent by newer versions of Microsoft Windows, except in unusual circumstances explained in the Remarks. + /// Windows NT 3.51 и более ранние: сообщение WM_PAINTICON отправляется минимизированному окну для рисования значка, это сообщение не отправляется в новых версиях Windows, кроме редких случаев, описанных в примечаниях PAINTICON = 0x0026, - /// Windows NT 3.51 and earlier: The WM_ICONERASEBKGND message is sent to a minimized window when the background of the icon must be filled before painting the icon. A window receives this message only if a class icon is defined for the window; otherwise, WM_ERASEBKGND is sent. This message is not sent by newer versions of Windows. + /// Windows NT 3.51 и более ранние: сообщение WM_ICONERASEBKGND отправляется минимизированному окну, когда фон значка должен быть заполнен перед рисованием значка, окно получает это сообщение только если для класса окна определён значок, иначе отправляется WM_ERASEBKGND, это сообщение не отправляется в новых версиях Windows ICONERASEBKGND = 0x0027, - /// The WM_NEXTDLGCTL message is sent to a dialog box procedure to set the keyboard focus to a different control in the dialog box. + /// Сообщение WM_NEXTDLGCTL отправляется процедуре диалогового окна для установки фокуса клавиатуры на другую кнопку диалога NEXTDLGCTL = 0x0028, - /// The WM_SPOOLERSTATUS message is sent from Print Manager whenever a job is added to or removed from the Print Manager queue. + /// Сообщение WM_SPOOLERSTATUS отправляется диспетчером печати при добавлении или удалении задания из очереди печати SPOOLERSTATUS = 0x002A, - /// The WM_DRAWITEM message is sent to the parent window of an owner-drawn button, combo box, list box, or menu when a visual aspect of the button, combo box, list box, or menu has changed. + /// Сообщение WM_DRAWITEM отправляется родительскому окну кнопки, комбинированного списка, списка или меню с пользовательской отрисовкой при изменении визуального аспекта элемента управления DRAWITEM = 0x002B, - /// The WM_MEASUREITEM message is sent to the owner window of a combo box, list box, list view control, or menu item when the control or menu is created. + /// Сообщение WM_MEASUREITEM отправляется окну-владельцу комбинированного списка, списка, списка представлений или пункта меню при создании элемента управления или меню MEASUREITEM = 0x002C, - /// Sent to the owner of a list box or combo box when the list box or combo box is destroyed or when items are removed by the LB_DELETESTRING, LB_RESETCONTENT, CB_DELETESTRING, or CB_RESETCONTENT message. The system sends a WM_DELETEITEM message for each deleted item. The system sends the WM_DELETEITEM message for any deleted list box or combo box item with nonzero item data. + /// Сообщение WM_DELETEITEM отправляется владельцу списка или комбинированного списка при уничтожении или удалении элементов посредством LB_DELETESTRING, LB_RESETCONTENT, CB_DELETESTRING или CB_RESETCONTENT, система отправляет WM_DELETEITEM для каждого удалённого элемента, если данные элемента не равны нулю DELETEITEM = 0x002D, - /// Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_KEYDOWN message. + /// Отправляется списком с флагом LBS_WANTKEYBOARDINPUT владельцу в ответ на WM_KEYDOWN VKEYTOITEM = 0x002E, - /// Sent by a list box with the LBS_WANTKEYBOARDINPUT style to its owner in response to a WM_CHAR message. + /// Отправляется списком с флагом LBS_WANTKEYBOARDINPUT владельцу в ответ на WM_CHAR CHARTOITEM = 0x002F, - /// An application sends a WM_SETFONT message to specify the font that a control is to use when drawing text. + /// Сообщение WM_SETFONT задаёт шрифт, используемый элементом управления при рисовании текста SETFONT = 0x0030, - /// An application sends a WM_GETFONT message to a control to retrieve the font with which the control is currently drawing its text. + /// Сообщение WM_GETFONT запрашивает у элемента управления шрифт, которым он в данный момент рисует текст GETFONT = 0x0031, - /// An application sends a WM_SETHOTKEY message to a window to associate a hot key with the window. When the user presses the hot key, the system activates the window. + /// Сообщение WM_SETHOTKEY связывает горячую клавишу с окном, при нажатии горячей клавиши система активирует окно SETHOTKEY = 0x0032, - /// An application sends a WM_GETHOTKEY message to determine the hot key associated with a window. + /// Сообщение WM_GETHOTKEY определяет горячую клавишу, связанную с окном GETHOTKEY = 0x0033, - /// The WM_QUERYDRAGICON message is sent to a minimized (iconic) window. The window is about to be dragged by the user but does not have an icon defined for its class. An application can return a handle to an icon or cursor. The system displays this cursor or icon while the user drags the icon. + /// Сообщение WM_QUERYDRAGICON отправляется минимизированному окну, когда пользователь начинает перетаскивание, но у окна нет определённого значка класса, приложение может вернуть дескриптор значка или курсора, который будет отображаться при перетаскивании QUERYDRAGICON = 0x0037, - /// The system sends the WM_COMPAREITEM message to determine the relative position of a new item in the sorted list of an owner-drawn combo box or list box. Whenever the application adds a new item, the system sends this message to the owner of a combo box or list box created with the CBS_SORT or LBS_SORT style. + /// Система отправляет сообщение WM_COMPAREITEM для определения относительной позиции нового элемента в отсортированном списке комбинированного списка или списка с пользовательской отрисовкой, когда приложение добавляет новый элемент, система отправляет это сообщение владельцу элемента управления, созданного с CBS_SORT или LBS_SORT COMPAREITEM = 0x0039, /// - /// Active Accessibility sends the WM_GETOBJECT message to obtain information about an accessible object contained in a server application. - /// Applications never send this message directly. It is sent only by Active Accessibility in response to calls to AccessibleObjectFromPoint, AccessibleObjectFromEvent, or AccessibleObjectFromWindow. However, server applications handle this message. + /// Active Accessibility отправляет сообщение WM_GETOBJECT для получения сведений об объекте доступности, содержащемся в серверном приложении + /// Приложения никогда не отправляют это сообщение напрямую, оно отправляется только Active Accessibility в ответ на вызовы AccessibleObjectFromPoint, AccessibleObjectFromEvent или AccessibleObjectFromWindow + /// Серверные приложения обрабатывают это сообщение /// GETOBJECT = 0x003D, - /// The WM_COMPACTING message is sent to all top-level windows when the system detects more than 12.5 percent of system time over a 30- to 60-second interval is being spent compacting memory. This indicates that system memory is low. + /// Сообщение WM_COMPACTING отправляется всем окнам верхнего уровня, когда система обнаруживает, что более 12,5 процента времени в интервале 30–60 секунд тратится на уплотнение памяти, что указывает на нехватку памяти COMPACTING = 0x0041, - /// WM_COMMNOTIFY is Obsolete for Win32-Based Applications + /// WM_COMMNOTIFY устарело для Win32-приложений [Obsolete("Obsolete for Win32-Based Applications")] COMMNOTIFY = 0x0044, - /// The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in the Z order is about to change as a result of a call to the SetWindowPos function or another window-management function. + /// Сообщение WM_WINDOWPOSCHANGING отправляется окну, размеры, положение или место в Z-порядке которого собираются измениться в результате вызова SetWindowPos или другой функции управления окнами WINDOWPOSCHANGING = 0x0046, - /// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function. + /// Сообщение WM_WINDOWPOSCHANGED отправляется окну, размеры, положение или место в Z-порядке которого изменились в результате вызова SetWindowPos или другой функции управления окнами WINDOWPOSCHANGED = 0x0047, /// - /// Notifies applications that the system, typically a battery-powered personal computer, is about to enter a suspended mode. - /// Use: POWERBROADCAST + /// Уведомляет приложения о том, что система, обычно работающая от батареи, собирается перейти в режим приостановки + /// Использование: POWERBROADCAST /// [Obsolete("Use: POWERBROADCAST")] POWER = 0x0048, - /// An application sends the WM_COPYDATA message to pass data to another application. + /// Сообщение WM_COPYDATA отправляется приложением для передачи данных другому приложению COPYDATA = 0x004A, - /// The WM_CANCELJOURNAL message is posted to an application when a user cancels the application's journaling activities. The message is posted with a NULL window handle. + /// Сообщение WM_CANCELJOURNAL отправляется приложению, когда пользователь отменяет Journaling активности приложения, сообщение отправляется с дескриптором окна NULL CANCELJOURNAL = 0x004B, - /// Sent by a common control to its parent window when an event has occurred or the control requires some information. + /// Отправляется элементом управления родительскому окну при наступлении события или при необходимости получить дополнительные сведения NOTIFY = 0x004E, - /// The WM_INPUTLANGCHANGEREQUEST message is posted to the window with the focus when the user chooses a new input language, either with the hotkey (specified in the Keyboard control panel application) or from the indicator on the system taskbar. An application can accept the change by passing the message to the DefWindowProc function or reject the change (and prevent it from taking place) by returning immediately. + /// Сообщение WM_INPUTLANGCHANGEREQUEST публикуется в окне с фокусом, когда пользователь выбирает новый язык ввода с помощью горячей клавиши или индикатора на панели задач, приложение может принять изменение, передав сообщение в DefWindowProc, или отклонить его, немедленно вернувшись INPUTLANGCHANGEREQUEST = 0x0050, - /// The WM_INPUTLANGCHANGE message is sent to the topmost affected window after an application's input language has been changed. You should make any application-specific settings and pass the message to the DefWindowProc function, which passes the message to all first-level child windows. These child windows can pass the message to DefWindowProc to have it pass the message to their child windows, and so on. + /// Сообщение WM_INPUTLANGCHANGE отправляется верхнему затронутому окну после изменения языка ввода приложения, следует выполнить специфичные настройки приложения и передать сообщение в DefWindowProc, который передаст его дочерним окнам первого уровня, а те — своим дочерним окнам INPUTLANGCHANGE = 0x0051, - /// Sent to an application that has initiated a training card with Microsoft Windows Help. The message informs the application when the user clicks an authorable button. An application initiates a training card by specifying the HELP_TCARD command in a call to the WinHelp function. + /// Отправляется приложению, инициировавшему обучающую карточку Windows Help, сообщение информирует приложение о нажатии на авторизуемую кнопку, приложение инициирует карточку, указав команду HELP_TCARD при вызове WinHelp TCARD = 0x0052, - /// Indicates that the user pressed the F1 key. If a menu is active when F1 is pressed, WM_HELP is sent to the window associated with the menu; otherwise, WM_HELP is sent to the window that has the keyboard focus. If no window has the keyboard focus, WM_HELP is sent to the currently active window. + /// Сообщение WM_HELP указывает, что пользователь нажал клавишу F1, если активное меню существует, WM_HELP отправляется окну, связанному с меню, иначе — окну с фокусом клавиатуры, при отсутствии фокуса — активному окну HELP = 0x0053, - /// The WM_USERCHANGED message is sent to all windows after the user has logged on or off. When the user logs on or off, the system updates the user-specific settings. The system sends this message immediately after updating the settings. + /// Сообщение WM_USERCHANGED отправляется всем окнам после входа или выхода пользователя, система обновляет пользовательские параметры и отправляет сообщение сразу после обновления USERCHANGED = 0x0054, - /// Determines if a window accepts ANSI or Unicode structures in the WM_NOTIFY notification message. WM_NOTIFYFORMAT messages are sent from a common control to its parent window and from the parent window to the common control. + /// Определяет, принимает ли окно ANSI или Unicode структуры в уведомлении WM_NOTIFY, сообщения WM_NOTIFYFORMAT отправляются от общего элемента управления родителю и от родителя общему элементу управления NOTIFYFORMAT = 0x0055, - /// The WM_CONTEXTMENU message notifies a window that the user clicked the right mouse button (right-clicked) in the window. + /// Сообщение WM_CONTEXTMENU уведомляет окно о щелчке правой кнопкой мыши в окне CONTEXTMENU = 0x007B, - /// The WM_STYLECHANGING message is sent to a window when the SetWindowLong function is about to change one or more of the window's styles. + /// Сообщение WM_STYLECHANGING отправляется окну, когда функция SetWindowLong собирается изменить один или несколько стилей окна STYLECHANGING = 0x007C, - /// The WM_STYLECHANGED message is sent to a window after the SetWindowLong function has changed one or more of the window's styles + /// Сообщение WM_STYLECHANGED отправляется окну после того, как функция SetWindowLong изменила один или несколько стилей окна STYLECHANGED = 0x007D, - /// The WM_DISPLAYCHANGE message is sent to all windows when the display resolution has changed. + /// Сообщение WM_DISPLAYCHANGE отправляется всем окнам при изменении разрешения дисплея DISPLAYCHANGE = 0x007E, - /// The WM_GETICON message is sent to a window to retrieve a handle to the large or small icon associated with a window. The system displays the large icon in the ALT+TAB dialog, and the small icon in the window caption. + /// Сообщение WM_GETICON отправляется окну для получения дескриптора большого или малого значка, связанного с окном, большой значок отображается в диалоге ALT+TAB, малый — в заголовке окна GETICON = 0x007F, - /// An application sends the WM_SETICON message to associate a new large or small icon with a window. The system displays the large icon in the ALT+TAB dialog box, and the small icon in the window caption. + /// Сообщение WM_SETICON связывает новый большой или малый значок с окном, большой значок отображается в диалоге ALT+TAB, малый — в заголовке окна SETICON = 0x0080, - /// The WM_NCCREATE message is sent prior to the WM_CREATE message when a window is first created. + /// Сообщение WM_NCCREATE отправляется перед WM_CREATE при создании окна NCCREATE = 0x0081, /// - /// The WM_NCDESTROY message informs a window that its nonclient area is being destroyed. The DestroyWindow function sends the WM_NCDESTROY message to the window following the WM_DESTROY message. WM_DESTROY is used to free the allocated memory object associated with the window. - /// The WM_NCDESTROY message is sent after the child windows have been destroyed. In contrast, WM_DESTROY is sent before the child windows are destroyed. + /// Сообщение WM_NCDESTROY информирует окно о разрушении неклиентской области, функция DestroyWindow отправляет WM_NCDESTROY после WM_DESTROY + /// WM_DESTROY используется для освобождения выделенного объекта памяти, связанного с окном + /// WM_NCDESTROY отправляется после уничтожения дочерних окон, в отличие от WM_DESTROY, которое отправляется до уничтожения дочерних окон /// NCDESTROY = 0x0082, - /// The WM_NCCALCSIZE message is sent when the size and position of a window's client area must be calculated. By processing this message, an application can control the content of the window's client area when the size or position of the window changes. + /// Сообщение WM_NCCALCSIZE отправляется, когда необходимо вычислить размер и положение клиентской области окна, обработка сообщения позволяет приложению управлять содержимым клиентской области при изменении размера или положения окна NCCALCSIZE = 0x0083, - /// The WM_NCHITTEST message is sent to a window when the cursor moves, or when a mouse button is pressed or released. If the mouse is not captured, the message is sent to the window beneath the cursor. Otherwise, the message is sent to the window that has captured the mouse. + /// Сообщение WM_NCHITTEST отправляется окну при перемещении курсора или при нажатии и отпускании кнопки мыши, если мышь не захвачена, сообщение отправляется окну под курсором, иначе — окну, захватившему мышь NCHITTEST = 0x0084, - /// The WM_NCPAINT message is sent to a window when its frame must be painted. + /// Сообщение WM_NCPAINT отправляется окну, когда требуется нарисовать рамку NCPAINT = 0x0085, - /// The WM_NCACTIVATE message is sent to a window when its nonclient area needs to be changed to indicate an active or inactive state. + /// Сообщение WM_NCACTIVATE отправляется окну, когда его неклиентская область должна отобразить активное или неактивное состояние NCACTIVATE = 0x0086, - /// The WM_GETDLGCODE message is sent to the window procedure associated with a control. By default, the system handles all keyboard input to the control; the system interprets certain types of keyboard input as dialog box navigation keys. To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself. + /// Сообщение WM_GETDLGCODE отправляется процедуре окна, связанного с элементом управления, по умолчанию система обрабатывает весь ввод клавиатуры в элемент управления, система интерпретирует некоторые виды ввода как клавиши навигации диалога, чтобы переопределить это поведение, элемент управления может ответить на WM_GETDLGCODE, указав, какие типы ввода он хочет обрабатывать сам GETDLGCODE = 0x0087, - /// The WM_SYNCPAINT message is used to synchronize painting while avoiding linking independent GUI threads. + /// Сообщение WM_SYNCPAINT используется для синхронизации рисования, избегая связывания независимых потоков GUI SYNCPAINT = 0x0088, - /// The WM_NCMOUSEMOVE message is posted to a window when the cursor is moved within the nonclient area of the window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCMOUSEMOVE публикуется, когда курсор перемещается в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCMOUSEMOVE = 0x00A0, - /// The WM_NCLBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCLBUTTONDOWN публикуется, когда пользователь нажимает левую кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCLBUTTONDOWN = 0x00A1, - /// The WM_NCLBUTTONUP message is posted when the user releases the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCLBUTTONUP публикуется, когда пользователь отпускает левую кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCLBUTTONUP = 0x00A2, - /// The WM_NCLBUTTONDBLCLK message is posted when the user double-clicks the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCLBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает левой кнопкой мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCLBUTTONDBLCLK = 0x00A3, - /// The WM_NCRBUTTONDOWN message is posted when the user presses the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCRBUTTONDOWN публикуется, когда пользователь нажимает правую кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCRBUTTONDOWN = 0x00A4, - /// The WM_NCRBUTTONUP message is posted when the user releases the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCRBUTTONUP публикуется, когда пользователь отпускает правую кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCRBUTTONUP = 0x00A5, - /// The WM_NCRBUTTONDBLCLK message is posted when the user double-clicks the right mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCRBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает правой кнопкой мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCRBUTTONDBLCLK = 0x00A6, - /// The WM_NCMBUTTONDOWN message is posted when the user presses the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCMBUTTONDOWN публикуется, когда пользователь нажимает среднюю кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCMBUTTONDOWN = 0x00A7, - /// The WM_NCMBUTTONUP message is posted when the user releases the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCMBUTTONUP публикуется, когда пользователь отпускает среднюю кнопку мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCMBUTTONUP = 0x00A8, - /// The WM_NCMBUTTONDBLCLK message is posted when the user double-clicks the middle mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCMBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает средней кнопкой мыши, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCMBUTTONDBLCLK = 0x00A9, - /// The WM_NCXBUTTONDOWN message is posted when the user presses the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCXBUTTONDOWN публикуется, когда пользователь нажимает первую или вторую X-кнопку, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCXBUTTONDOWN = 0x00AB, - /// The WM_NCXBUTTONUP message is posted when the user releases the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCXBUTTONUP публикуется, когда пользователь отпускает первую или вторую X-кнопку, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCXBUTTONUP = 0x00AC, - /// The WM_NCXBUTTONDBLCLK message is posted when the user double-clicks the first or second X button while the cursor is in the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. + /// Сообщение WM_NCXBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает первой или второй X-кнопкой, находясь в неклиентской области окна, сообщение публикуется окну, которое содержит курсор, если окно захватило мышь, сообщение не публикуется NCXBUTTONDBLCLK = 0x00AD, - /// The WM_INPUT_DEVICE_CHANGE message is sent to the window that registered to receive raw input. A window receives this message through its WindowProc function. + /// Сообщение WM_INPUT_DEVICE_CHANGE отправляется окну, зарегистрированному для получения необработанного ввода, окно получает это сообщение через функцию WindowProc INPUT_DEVICE_CHANGE = 0x00FE, - /// The WM_INPUT message is sent to the window that is getting raw input. + /// Сообщение WM_INPUT отправляется окну, которое получает необработанный ввод INPUT = 0x00FF, - /// This message filters for keyboard messages. + /// Это сообщение фильтрует клавиатурные сообщения KEYFIRST = 0x0100, - /// The WM_KEYDOWN message is posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when the ALT key is not pressed. + /// Сообщение WM_KEYDOWN публикуется в окне с фокусом клавиатуры, когда нажата несистемная клавиша, несистемная клавиша — это клавиша, нажатая при ненажатой клавише ALT KEYDOWN = 0x0100, - /// The WM_KEYUP message is posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus. + /// Сообщение WM_KEYUP публикуется в окне с фокусом клавиатуры, когда отпущена несистемная клавиша, несистемная клавиша — это клавиша, нажатая при ненажатой клавише ALT, или клавиша, нажатая при наличии фокуса у окна KEYUP = 0x0101, - /// The WM_CHAR message is posted to the window with the keyboard focus when a WM_KEYDOWN message is translated by the TranslateMessage function. The WM_CHAR message contains the character code of the key that was pressed. + /// Сообщение WM_CHAR публикуется в окне с фокусом клавиатуры, когда сообщение WM_KEYDOWN преобразуется функцией TranslateMessage, WM_CHAR содержит код символа нажатой клавиши CHAR = 0x0102, - /// The WM_DEADCHAR message is posted to the window with the keyboard focus when a WM_KEYUP message is translated by the TranslateMessage function. WM_DEADCHAR specifies a character code generated by a dead key. A dead key is a key that generates a character, such as the umlaut (double-dot), that is combined with another character to form a composite character. For example, the umlaut-O character (Ö) is generated by typing the dead key for the umlaut character, and then typing the O key. + /// Сообщение WM_DEADCHAR публикуется в окне с фокусом клавиатуры, когда сообщение WM_KEYUP преобразуется функцией TranslateMessage, WM_DEADCHAR задаёт код символа, сгенерированного мёртвой клавишей, мёртвая клавиша создаёт символ, который объединяется с другим символом в составной символ, например символ Ö создаётся вводом мёртвой клавиши диакритики и затем клавиши O DEADCHAR = 0x0103, - /// The WM_SYSKEYDOWN message is posted to the window with the keyboard focus when the user presses the F10 key (which activates the menu bar) or holds down the ALT key and then presses another key. It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYDOWN message is sent to the active window. The window that receives the message can distinguish between these two contexts by checking the context code in the lParam parameter. + /// Сообщение WM_SYSKEYDOWN публикуется в окне с фокусом клавиатуры, когда пользователь нажимает F10 или удерживает ALT и нажимает другую клавишу, оно также возникает, когда ни одно окно не имеет фокуса клавиатуры, в этом случае WM_SYSKEYDOWN отправляется активному окну, окно может различить контексты по коду контекста в параметре lParam SYSKEYDOWN = 0x0104, - /// The WM_SYSKEYUP message is posted to the window with the keyboard focus when the user releases a key that was pressed while the ALT key was held down. It also occurs when no window currently has the keyboard focus; in this case, the WM_SYSKEYUP message is sent to the active window. The window that receives the message can distinguish between these two contexts by checking the context code in the lParam parameter. + /// Сообщение WM_SYSKEYUP публикуется в окне с фокусом клавиатуры, когда пользователь отпускает клавишу, нажатую при удержании ALT, оно также возникает, когда ни одно окно не имеет фокуса клавиатуры, в этом случае WM_SYSKEYUP отправляется активному окну, окно может различить контексты по коду контекста в параметре lParam SYSKEYUP = 0x0105, - /// The WM_SYSCHAR message is posted to the window with the keyboard focus when a WM_SYSKEYDOWN message is translated by the TranslateMessage function. It specifies the character code of a system character key — that is, a character key that is pressed while the ALT key is down. + /// Сообщение WM_SYSCHAR публикуется в окне с фокусом клавиатуры, когда сообщение WM_SYSKEYDOWN преобразуется функцией TranslateMessage, оно задаёт код символа системной клавиши, то есть символа, набранного при удержании ALT SYSCHAR = 0x0106, - /// The WM_SYSDEADCHAR message is sent to the window with the keyboard focus when a WM_SYSKEYDOWN message is translated by the TranslateMessage function. WM_SYSDEADCHAR specifies the character code of a system dead key — that is, a dead key that is pressed while holding down the ALT key. + /// Сообщение WM_SYSDEADCHAR отправляется в окно с фокусом клавиатуры, когда сообщение WM_SYSKEYDOWN преобразуется функцией TranslateMessage, WM_SYSDEADCHAR задаёт код системной мёртвой клавиши, то есть мёртвой клавиши, нажатой при удержании ALT SYSDEADCHAR = 0x0107, /// - /// The WM_UNICHAR message is posted to the window with the keyboard focus when a WM_KEYDOWN message is translated by the TranslateMessage function. The WM_UNICHAR message contains the character code of the key that was pressed. - /// The WM_UNICHAR message is equivalent to WM_CHAR, but it uses Unicode Transformation Format (UTF)-32, whereas WM_CHAR uses UTF-16. It is designed to send or post Unicode characters to ANSI windows and it can can handle Unicode Supplementary Plane characters. + /// Сообщение WM_UNICHAR публикуется в окне с фокусом клавиатуры, когда сообщение WM_KEYDOWN преобразуется функцией TranslateMessage + /// Сообщение WM_UNICHAR эквивалентно WM_CHAR, но использует Unicode Transformation Format (UTF)-32, тогда как WM_CHAR использует UTF-16 + /// Оно предназначено для отправки или публикации символов Unicode в ANSI-окна и может обрабатывать символы дополнительной плоскости Unicode /// UNICHAR = 0x0109, - /// This message filters for keyboard messages. + /// Это сообщение фильтрует клавиатурные сообщения KEYLAST = 0x0108, - /// Sent immediately before the IME generates the composition string as a result of a keystroke. A window receives this message through its WindowProc function. + /// Отправляется непосредственно перед тем, как IME сформирует строку композиции в результате нажатия клавиши, окно получает это сообщение через функцию WindowProc IME_STARTCOMPOSITION = 0x010D, - /// Sent to an application when the IME ends composition. A window receives this message through its WindowProc function. + /// Отправляется приложению, когда IME завершает композицию, окно получает это сообщение через функцию WindowProc IME_ENDCOMPOSITION = 0x010E, - /// Sent to an application when the IME changes composition status as a result of a keystroke. A window receives this message through its WindowProc function. + /// Отправляется приложению, когда IME изменяет состояние композиции в результате нажатия клавиши, окно получает это сообщение через функцию WindowProc IME_COMPOSITION = 0x010F, + /// Последнее сообщение диапазона IME IME_KEYLAST = 0x010F, - /// The WM_INITDIALOG message is sent to the dialog box procedure immediately before a dialog box is displayed. Dialog box procedures typically use this message to initialize controls and carry out any other initialization tasks that affect the appearance of the dialog box. + /// Сообщение WM_INITDIALOG отправляется процедуре диалогового окна непосредственно перед отображением диалогового окна, процедуры диалога обычно используют это сообщение для инициализации элементов управления и других задач инициализации, влияющих на внешний вид диалога INITDIALOG = 0x0110, - /// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated. + /// Сообщение WM_COMMAND отправляется, когда пользователь выбирает команду из меню, когда элемент управления отправляет уведомление родительскому окну, или когда происходит обработка сочетания клавиш COMMAND = 0x0111, - /// A window receives this message when the user chooses a command from the Window menu, clicks the maximize button, minimize button, restore button, close button, or moves the form. You can stop the form from moving by filtering this out. + /// Окно получает это сообщение, когда пользователь выбирает команду из меню Window, нажимает кнопки максимизации, минимизации, восстановления или закрытия, либо перемещает форму, можно предотвратить перемещение формы, отфильтровав это сообщение SYSCOMMAND = 0x0112, - /// The WM_TIMER message is posted to the installing thread's message queue when a timer expires. The message is posted by the GetMessage or PeekMessage function. + /// Сообщение WM_TIMER публикуется в очередь сообщений потока установки, когда истекает таймер, сообщение публикуется функцией GetMessage или PeekMessage TIMER = 0x0113, - /// The WM_HSCROLL message is sent to a window when a scroll event occurs in the window's standard horizontal scroll bar. This message is also sent to the owner of a horizontal scroll bar control when a scroll event occurs in the control. + /// Сообщение WM_HSCROLL отправляется окну при событии прокрутки в стандартной горизонтальной полосе прокрутки, это сообщение также отправляется владельцу элемента управления горизонтальной полосы прокрутки при событии прокрутки в элементе управления HSCROLL = 0x0114, - /// The WM_VSCROLL message is sent to a window when a scroll event occurs in the window's standard vertical scroll bar. This message is also sent to the owner of a vertical scroll bar control when a scroll event occurs in the control. + /// Сообщение WM_VSCROLL отправляется окну при событии прокрутки в стандартной вертикальной полосе прокрутки, это сообщение также отправляется владельцу элемента управления вертикальной полосы прокрутки при событии прокрутки в элементе управления VSCROLL = 0x0115, - /// The WM_INITMENU message is sent when a menu is about to become active. It occurs when the user clicks an item on the menu bar or presses a menu key. This allows the application to modify the menu before it is displayed. + /// Сообщение WM_INITMENU отправляется, когда меню собирается стать активным, оно происходит, когда пользователь щёлкает пункт меню или нажимает клавишу меню, это позволяет приложению изменить меню перед отображением INITMENU = 0x0116, - /// The WM_INITMENUPOPUP message is sent when a drop-down menu or submenu is about to become active. This allows an application to modify the menu before it is displayed, without changing the entire menu. + /// Сообщение WM_INITMENUPOPUP отправляется, когда раскрывающееся меню или подменю собирается стать активным, это позволяет приложению изменить меню перед отображением, не изменяя всё меню INITMENUPOPUP = 0x0117, - /// The WM_MENUSELECT message is sent to a menu's owner window when the user selects a menu item. + /// Сообщение WM_MENUSELECT отправляется окну-владельцу меню, когда пользователь выбирает пункт меню MENUSELECT = 0x011F, - /// The WM_MENUCHAR message is sent when a menu is active and the user presses a key that does not correspond to any mnemonic or accelerator key. This message is sent to the window that owns the menu. + /// Сообщение WM_MENUCHAR отправляется, когда меню активно и пользователь нажимает клавишу, которая не соответствует мнемонике или ускорителю, это сообщение отправляется окну-владельцу меню MENUCHAR = 0x0120, - /// The WM_ENTERIDLE message is sent to the owner window of a modal dialog box or menu that is entering an idle state. A modal dialog box or menu enters an idle state when no messages are waiting in its queue after it has processed one or more previous messages. + /// Сообщение WM_ENTERIDLE отправляется окну-владельцу модального диалога или меню, когда оно входит в состояние простоя, модальный диалог или меню входит в состояние простоя, когда в очереди сообщений нет сообщений после обработки одного или нескольких предыдущих сообщений ENTERIDLE = 0x0121, - /// The WM_MENURBUTTONUP message is sent when the user releases the right mouse button while the cursor is on a menu item. + /// Сообщение WM_MENURBUTTONUP отправляется, когда пользователь отпускает правую кнопку мыши над пунктом меню MENURBUTTONUP = 0x0122, - /// The WM_MENUDRAG message is sent to the owner of a drag-and-drop menu when the user drags a menu item. + /// Сообщение WM_MENUDRAG отправляется владельцу меню перетаскивания, когда пользователь перетаскивает пункт меню MENUDRAG = 0x0123, - /// The WM_MENUGETOBJECT message is sent to the owner of a drag-and-drop menu when the mouse cursor enters a menu item or moves from the center of the item to the top or bottom of the item. + /// Сообщение WM_MENUGETOBJECT отправляется владельцу меню перетаскивания, когда курсор входит в пункт меню или перемещается от центра пункта к его верхней или нижней части MENUGETOBJECT = 0x0124, - /// The WM_UNINITMENUPOPUP message is sent when a drop-down menu or submenu has been destroyed. + /// Сообщение WM_UNINITMENUPOPUP отправляется, когда раскрывающееся меню или подменю уничтожено UNINITMENUPOPUP = 0x0125, - /// The WM_MENUCOMMAND message is sent when the user makes a selection from a menu. + /// Сообщение WM_MENUCOMMAND отправляется, когда пользователь делает выбор в меню MENUCOMMAND = 0x0126, - /// An application sends the WM_CHANGEUISTATE message to indicate that the user interface (UI) state should be changed. + /// Сообщение WM_CHANGEUISTATE отправляется приложением, чтобы указать, что состояние пользовательского интерфейса должно измениться CHANGEUISTATE = 0x0127, - /// An application sends the WM_UPDATEUISTATE message to change the user interface (UI) state for the specified window and all its child windows. + /// Сообщение WM_UPDATEUISTATE отправляется приложением, чтобы изменить состояние пользовательского интерфейса для указанного окна и всех его дочерних окон UPDATEUISTATE = 0x0128, - /// An application sends the WM_QUERYUISTATE message to retrieve the user interface (UI) state for a window. + /// Сообщение WM_QUERYUISTATE отправляется приложением, чтобы получить состояние пользовательского интерфейса окна QUERYUISTATE = 0x0129, - /// The WM_CTLCOLORMSGBOX message is sent to the owner window of a message box before Windows draws the message box. By responding to this message, the owner window can set the text and background colors of the message box by using the given display device context handle. + /// Сообщение WM_CTLCOLORMSGBOX отправляется окну-владельцу окна сообщений перед тем, как Windows рисует окно сообщений, отвечая на это сообщение, владелец может установить цвета текста и фона окна сообщений, используя заданный дескриптор контекста устройства CTLCOLORMSGBOX = 0x0132, - /// An edit control that is not read-only or disabled sends the WM_CTLCOLOREDIT message to its parent window when the control is about to be drawn. By responding to this message, the parent window can use the specified device context handle to set the text and background colors of the edit control. + /// Элемент управления редактированием, который не является только для чтения и не отключён, отправляет WM_CTLCOLOREDIT родительскому окну перед рисованием, родительское окно может использовать заданный дескриптор контекста устройства, чтобы установить цвета текста и фона элемента управления CTLCOLOREDIT = 0x0133, - /// Sent to the parent window of a list box before the system draws the list box. By responding to this message, the parent window can set the text and background colors of the list box by using the specified display device context handle. + /// Сообщение WM_CTLCOLORLISTBOX отправляется родительскому окну списка перед тем, как система рисует список, отвечая на это сообщение, родительское окно может использовать заданный дескриптор контекста устройства, чтобы установить цвета текста и фона списка CTLCOLORLISTBOX = 0x0134, - /// The WM_CTLCOLORBTN message is sent to the parent window of a button before drawing the button. The parent window can change the button's text and background colors. However, only owner-drawn buttons respond to the parent window processing this message. + /// Сообщение WM_CTLCOLORBTN отправляется родительскому окну кнопки перед рисованием кнопки, родительское окно может изменить цвета текста и фона кнопки, однако только кнопки с пользовательской отрисовкой реагируют на обработку этого сообщения родительским окном CTLCOLORBTN = 0x0135, - /// The WM_CTLCOLORDLG message is sent to a dialog box before the system draws the dialog box. By responding to this message, the dialog box can set its text and background colors using the specified display device context handle. + /// Сообщение WM_CTLCOLORDLG отправляется диалоговому окну перед тем, как система рисует диалог, отвечая на это сообщение, диалог может установить цвета текста и фона, используя заданный дескриптор контекста устройства CTLCOLORDLG = 0x0136, - /// The WM_CTLCOLORSCROLLBAR message is sent to the parent window of a scroll bar control when the control is about to be drawn. By responding to this message, the parent window can use the display context handle to set the background color of the scroll bar control. + /// Сообщение WM_CTLCOLORSCROLLBAR отправляется родительскому окну элемента управления полосы прокрутки перед рисованием элемента управления, отвечая на это сообщение, родительское окно может использовать дескриптор контекста отображения для установки цвета фона полосы прокрутки CTLCOLORSCROLLBAR = 0x0137, - /// A static control, or an edit control that is read-only or disabled, sends the WM_CTLCOLORSTATIC message to its parent window when the control is about to be drawn. By responding to this message, the parent window can use the specified device context handle to set the text and background colors of the static control. + /// Статический элемент управления или элемент редактирования только для чтения или отключённый отправляет WM_CTLCOLORSTATIC родительскому окну перед рисованием, отвечая на это сообщение, родительское окно может использовать заданный дескриптор контекста устройства для установки цветов текста и фона статического элемента CTLCOLORSTATIC = 0x0138, - /// Use WM_MOUSEFIRST to specify the first mouse message. Use the PeekMessage() Function. + /// Используйте WM_MOUSEFIRST для указания первого сообщения мыши, используйте функцию PeekMessage() MOUSEFIRST = 0x0200, - /// The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_MOUSEMOVE публикуется в окне при перемещении курсора, если мышь не захвачена, сообщение публикуется окну, содержащему курсор, иначе — окну, захватившему мышь MOUSEMOVE = 0x0200, - /// The WM_LBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_LBUTTONDOWN публикуется, когда пользователь нажимает левую кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь LBUTTONDOWN = 0x0201, - /// The WM_LBUTTONUP message is posted when the user releases the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_LBUTTONUP публикуется, когда пользователь отпускает левую кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь LBUTTONUP = 0x0202, - /// The WM_LBUTTONDBLCLK message is posted when the user double-clicks the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_LBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает левой кнопкой мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь LBUTTONDBLCLK = 0x0203, - /// The WM_RBUTTONDOWN message is posted when the user presses the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_RBUTTONDOWN публикуется, когда пользователь нажимает правую кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь RBUTTONDOWN = 0x0204, - /// The WM_RBUTTONUP message is posted when the user releases the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_RBUTTONUP публикуется, когда пользователь отпускает правую кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь RBUTTONUP = 0x0205, - /// The WM_RBUTTONDBLCLK message is posted when the user double-clicks the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_RBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает правой кнопкой мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь RBUTTONDBLCLK = 0x0206, - /// The WM_MBUTTONDOWN message is posted when the user presses the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_MBUTTONDOWN публикуется, когда пользователь нажимает среднюю кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь MBUTTONDOWN = 0x0207, - /// The WM_MBUTTONUP message is posted when the user releases the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_MBUTTONUP публикуется, когда пользователь отпускает среднюю кнопку мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь MBUTTONUP = 0x0208, - /// The WM_MBUTTONDBLCLK message is posted when the user double-clicks the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_MBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает средней кнопкой мыши в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь MBUTTONDBLCLK = 0x0209, - /// The WM_MOUSEWHEEL message is sent to the focus window when the mouse wheel is rotated. The DefWindowProc function propagates the message to the window's parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until it finds a window that processes it. + /// Сообщение WM_MOUSEWHEEL отправляется окну с фокусом при вращении колеса мыши, функция DefWindowProc распространяет сообщение на родительское окно, внутреннее перенаправление сообщения не требуется, поскольку DefWindowProc передаёт его по цепочке родителей, пока не найдёт окно, обрабатывающее его MOUSEWHEEL = 0x020A, - /// The WM_XBUTTONDOWN message is posted when the user presses the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_XBUTTONDOWN публикуется, когда пользователь нажимает первую или вторую X-кнопку в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь XBUTTONDOWN = 0x020B, - /// The WM_XBUTTONUP message is posted when the user releases the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_XBUTTONUP публикуется, когда пользователь отпускает первую или вторую X-кнопку в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь XBUTTONUP = 0x020C, - /// The WM_XBUTTONDBLCLK message is posted when the user double-clicks the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. + /// Сообщение WM_XBUTTONDBLCLK публикуется, когда пользователь дважды щёлкает первой или второй X-кнопкой в клиентской области окна, если мышь не захвачена, сообщение публикуется окну под курсором, иначе — окну, захватившему мышь XBUTTONDBLCLK = 0x020D, - /// The WM_MOUSEHWHEEL message is sent to the focus window when the mouse's horizontal scroll wheel is tilted or rotated. The DefWindowProc function propagates the message to the window's parent. There should be no internal forwarding of the message, since DefWindowProc propagates it up the parent chain until it finds a window that processes it. + /// Сообщение WM_MOUSEHWHEEL отправляется окну с фокусом, когда горизонтальное колесо мыши наклоняется или вращается, функция DefWindowProc распространяет сообщение на родительское окно, внутреннее перенаправление сообщения не требуется, поскольку DefWindowProc передаёт его по цепочке родителей, пока не найдёт окно, обрабатывающее его MOUSEHWHEEL = 0x020E, - /// Use WM_MOUSELAST to specify the last mouse message. Used with PeekMessage() Function. + /// Используйте WM_MOUSELAST для указания последнего сообщения мыши, используется с функцией PeekMessage() MOUSELAST = 0x020E, - /// The WM_PARENTNOTIFY message is sent to the parent of a child window when the child window is created or destroyed, or when the user clicks a mouse button while the cursor is over the child window. When the child window is being created, the system sends WM_PARENTNOTIFY just before the CreateWindow or CreateWindowEx function that creates the window returns. When the child window is being destroyed, the system sends the message before any processing to destroy the window takes place. + /// Сообщение WM_PARENTNOTIFY отправляется родителю дочернего окна при создании или уничтожении дочернего окна или при щелчке кнопки мыши над дочерним окном, при создании дочернего окна система отправляет WM_PARENTNOTIFY непосредственно перед возвратом из CreateWindow или CreateWindowEx, при уничтожении дочернего окна система отправляет сообщение до начала обработки уничтожения PARENTNOTIFY = 0x0210, - /// The WM_ENTERMENULOOP message informs an application's main window procedure that a menu modal loop has been entered. + /// Сообщение WM_ENTERMENULOOP информирует процедуру главного окна приложения о входе в модальный цикл меню ENTERMENULOOP = 0x0211, - /// The WM_EXITMENULOOP message informs an application's main window procedure that a menu modal loop has been exited. + /// Сообщение WM_EXITMENULOOP информирует процедуру главного окна приложения о выходе из модального цикла меню EXITMENULOOP = 0x0212, - /// The WM_NEXTMENU message is sent to an application when the right or left arrow key is used to switch between the menu bar and the system menu. + /// Сообщение WM_NEXTMENU отправляется приложению, когда правая или левая стрелка используется для переключения между строкой меню и системным меню NEXTMENU = 0x0213, - /// The WM_SIZING message is sent to a window that the user is resizing. By processing this message, an application can monitor the size and position of the drag rectangle and, if needed, change its size or position. + /// Сообщение WM_SIZING отправляется окну, размер которого изменяет пользователь, обработка сообщения позволяет приложению отслеживать размер и положение прямоугольника перетаскивания и при необходимости изменять его SIZING = 0x0214, - /// The WM_CAPTURECHANGED message is sent to the window that is losing the mouse capture. + /// Сообщение WM_CAPTURECHANGED отправляется окну, которое теряет захват мыши CAPTURECHANGED = 0x0215, - /// The WM_MOVING message is sent to a window that the user is moving. By processing this message, an application can monitor the position of the drag rectangle and, if needed, change its position. + /// Сообщение WM_MOVING отправляется окну, которое перемещает пользователь, обработка сообщения позволяет приложению отслеживать положение прямоугольника перетаскивания и при необходимости изменять его MOVING = 0x0216, - /// Notifies applications that a power-management event has occurred. + /// Уведомляет приложения о событии управления питанием POWERBROADCAST = 0x0218, - /// Notifies an application of a change to the hardware configuration of a device or the computer. + /// Уведомляет приложение об изменении аппаратной конфигурации устройства или компьютера DEVICECHANGE = 0x0219, - /// An application sends the WM_MDICREATE message to a multiple-document interface (MDI) client window to create an MDI child window. + /// Сообщение WM_MDICREATE отправляется клиентскому окну множественного интерфейса документов (MDI) для создания дочернего окна MDI MDICREATE = 0x0220, - /// An application sends the WM_MDIDESTROY message to a multiple-document interface (MDI) client window to close an MDI child window. + /// Сообщение WM_MDIDESTROY отправляется клиентскому окну MDI для закрытия дочернего окна MDI MDIDESTROY = 0x0221, - /// An application sends the WM_MDIACTIVATE message to a multiple-document interface (MDI) client window to instruct the client window to activate a different MDI child window. + /// Сообщение WM_MDIACTIVATE отправляется клиентскому окну MDI, чтобы указать клиентскому окну активировать другое дочернее окно MDI MDIACTIVATE = 0x0222, - /// An application sends the WM_MDIRESTORE message to a multiple-document interface (MDI) client window to restore an MDI child window from maximized or minimized size. + /// Сообщение WM_MDIRESTORE отправляется клиентскому окну MDI, чтобы восстановить дочернее окно MDI из максимизированного или минимизированного состояния MDIRESTORE = 0x0223, - /// An application sends the WM_MDINEXT message to a multiple-document interface (MDI) client window to activate the next or previous child window. + /// Сообщение WM_MDINEXT отправляется клиентскому окну MDI, чтобы активировать следующее или предыдущее дочернее окно MDINEXT = 0x0224, - /// An application sends the WM_MDIMAXIMIZE message to a multiple-document interface (MDI) client window to maximize an MDI child window. The system resizes the child window to make its client area fill the client window. The system places the child window's window menu icon in the rightmost position of the frame window's menu bar, and places the child window's restore icon in the leftmost position. The system also appends the title bar text of the child window to that of the frame window. + /// Сообщение WM_MDIMAXIMIZE отправляется клиентскому окну MDI, чтобы максимизировать дочернее окно MDI, система изменяет размер дочернего окна так, чтобы его клиентская область заполняла клиентское окно, система помещает значок меню дочернего окна в правую позицию строки меню рамочного окна, а значок восстановления — в левую позицию, также система добавляет текст заголовка дочернего окна к заголовку рамочного окна MDIMAXIMIZE = 0x0225, - /// An application sends the WM_MDITILE message to a multiple-document interface (MDI) client window to arrange all of its MDI child windows in a tile format. + /// Сообщение WM_MDITILE отправляется клиентскому окну MDI, чтобы упорядочить все дочерние окна в виде плитки MDITILE = 0x0226, - /// An application sends the WM_MDICASCADE message to a multiple-document interface (MDI) client window to arrange all its child windows in a cascade format. + /// Сообщение WM_MDICASCADE отправляется клиентскому окну MDI, чтобы упорядочить все дочерние окна каскадом MDICASCADE = 0x0227, - /// An application sends the WM_MDIICONARRANGE message to a multiple-document interface (MDI) client window to arrange all minimized MDI child windows. It does not affect child windows that are not minimized. + /// Сообщение WM_MDIICONARRANGE отправляется клиентскому окну MDI, чтобы упорядочить все минимизированные дочерние окна MDI, оно не влияет на дочерние окна, которые не минимизированы MDIICONARRANGE = 0x0228, - /// An application sends the WM_MDIGETACTIVE message to a multiple-document interface (MDI) client window to retrieve the handle to the active MDI child window. + /// Сообщение WM_MDIGETACTIVE отправляется клиентскому окну MDI, чтобы получить дескриптор активного дочернего окна MDI MDIGETACTIVE = 0x0229, - /// An application sends the WM_MDISETMENU message to a multiple-document interface (MDI) client window to replace the entire menu of an MDI frame window, to replace the window menu of the frame window, or both. + /// Сообщение WM_MDISETMENU отправляется клиентскому окну MDI, чтобы заменить всё меню рамочного окна MDI, заменить меню окна рамки или оба MDISETMENU = 0x0230, /// - /// The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns. - /// The system sends the WM_ENTERSIZEMOVE message regardless of whether the dragging of full windows is enabled. + /// Сообщение WM_ENTERSIZEMOVE отправляется один раз окну после входа в модальный цикл перемещения или изменения размера + /// Окно входит в этот цикл, когда пользователь щёлкает заголовок окна или границу изменения размера, или когда окно передаёт WM_SYSCOMMAND в DefWindowProc и параметр wParam указывает значение SC_MOVE или SC_SIZE + /// Операция завершается, когда DefWindowProc возвращается, система отправляет WM_ENTERSIZEMOVE независимо от того, включено ли перетаскивание полных окон /// ENTERSIZEMOVE = 0x0231, - /// The WM_EXITSIZEMOVE message is sent one time to a window, after it has exited the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns. + /// Сообщение WM_EXITSIZEMOVE отправляется один раз окну после выхода из модального цикла перемещения или изменения размера, окно входит в этот цикл, когда пользователь щёлкает заголовок окна или границу изменения размера, или когда окно передаёт WM_SYSCOMMAND в DefWindowProc и параметр wParam указывает значение SC_MOVE или SC_SIZE, операция завершается, когда DefWindowProc возвращается EXITSIZEMOVE = 0x0232, - /// Sent when the user drops a file on the window of an application that has registered itself as a recipient of dropped files. + /// Отправляется, когда пользователь перетаскивает файл в окно приложения, зарегистрированного как получатель перетаскиваемых файлов DROPFILES = 0x0233, - /// An application sends the WM_MDIREFRESHMENU message to a multiple-document interface (MDI) client window to refresh the window menu of the MDI frame window. + /// Сообщение WM_MDIREFRESHMENU отправляется клиентскому окну MDI для обновления меню окна рамки MDI MDIREFRESHMENU = 0x0234, - /// Sent to an application when a window is activated. A window receives this message through its WindowProc function. + /// Отправляется приложению при активации окна, окно получает это сообщение через функцию WindowProc IME_SETCONTEXT = 0x0281, - /// Sent to an application to notify it of changes to the IME window. A window receives this message through its WindowProc function. + /// Отправляется приложению для уведомления об изменениях окна IME, окно получает это сообщение через функцию WindowProc IME_NOTIFY = 0x0282, - /// Sent by an application to direct the IME window to carry out the requested command. The application uses this message to control the IME window that it has created. To send this message, the application calls the SendMessage function with the following parameters. + /// Отправляется приложением для выполнения требуемой команды окна IME, приложение использует это сообщение для управления окном IME, созданным приложением, чтобы отправить сообщение, приложение вызывает SendMessage с соответствующими параметрами IME_CONTROL = 0x0283, - /// Sent to an application when the IME window finds no space to extend the area for the composition window. A window receives this message through its WindowProc function. + /// Отправляется приложению, когда окно IME не может найти место для расширения области окна композиции, окно получает это сообщение через функцию WindowProc IME_COMPOSITIONFULL = 0x0284, - /// Sent to an application when the operating system is about to change the current IME. A window receives this message through its WindowProc function. + /// Отправляется приложению, когда операционная система собирается изменить текущую IME, окно получает это сообщение через функцию WindowProc IME_SELECT = 0x0285, - /// Sent to an application when the IME gets a character of the conversion result. A window receives this message through its WindowProc function. + /// Отправляется приложению, когда IME получает символ результата преобразования, окно получает это сообщение через функцию WindowProc IME_CHAR = 0x0286, - /// Sent to an application to provide commands and request information. A window receives this message through its WindowProc function. + /// Отправляется приложению для предоставления команд и запроса информации, окно получает это сообщение через функцию WindowProc IME_REQUEST = 0x0288, - /// Sent to an application by the IME to notify the application of a key press and to keep message order. A window receives this message through its WindowProc function. + /// Отправляется приложению IME для уведомления о нажатии клавиши и сохранения порядка сообщений, окно получает это сообщение через функцию WindowProc IME_KEYDOWN = 0x0290, - /// Sent to an application by the IME to notify the application of a key release and to keep message order. A window receives this message through its WindowProc function. + /// Отправляется приложению IME для уведомления об отпускании клавиши и сохранения порядка сообщений, окно получает это сообщение через функцию WindowProc IME_KEYUP = 0x0291, - /// The WM_MOUSEHOVER message is posted to a window when the cursor hovers over the client area of the window for the period of time specified in a prior call to TrackMouseEvent. + /// Сообщение WM_MOUSEHOVER публикуется, когда курсор задерживается над клиентской областью окна в течение времени, заданного предыдущим вызовом TrackMouseEvent MOUSEHOVER = 0x02A1, - /// The WM_MOUSELEAVE message is posted to a window when the cursor leaves the client area of the window specified in a prior call to TrackMouseEvent. + /// Сообщение WM_MOUSELEAVE публикуется, когда курсор покидает клиентскую область окна, указанного в предыдущем вызове TrackMouseEvent MOUSELEAVE = 0x02A3, - /// The WM_NCMOUSEHOVER message is posted to a window when the cursor hovers over the nonclient area of the window for the period of time specified in a prior call to TrackMouseEvent. + /// Сообщение WM_NCMOUSEHOVER публикуется, когда курсор задерживается над неклиентской областью окна в течение времени, заданного предыдущим вызовом TrackMouseEvent NCMOUSEHOVER = 0x02A0, - /// The WM_NCMOUSELEAVE message is posted to a window when the cursor leaves the nonclient area of the window specified in a prior call to TrackMouseEvent. + /// Сообщение WM_NCMOUSELEAVE публикуется, когда курсор покидает неклиентскую область окна, указанного в предыдущем вызове TrackMouseEvent NCMOUSELEAVE = 0x02A2, - /// The WM_WTSSESSION_CHANGE message notifies applications of changes in session state. + /// Сообщение WM_WTSSESSION_CHANGE уведомляет приложения об изменениях состояния сеанса WTSSESSION_CHANGE = 0x02B1, + /// Первое сообщение диапазона планшетных сообщений TABLET_FIRST = 0x02c0, + /// Последнее сообщение диапазона планшетных сообщений TABLET_LAST = 0x02df, - /// An application sends a WM_CUT message to an edit control or combo box to delete (cut) the current selection, if any, in the edit control and copy the deleted text to the clipboard in CF_TEXT format. + /// Сообщение WM_CUT отправляется приложением элементу редактирования или комбинированному списку для удаления текущего выделения и копирования удалённого текста в буфер обмена в формате CF_TEXT CUT = 0x0300, - /// An application sends the WM_COPY message to an edit control or combo box to copy the current selection to the clipboard in CF_TEXT format. + /// Сообщение WM_COPY отправляется приложением элементу редактирования или комбинированному списку для копирования текущего выделения в буфер обмена в формате CF_TEXT COPY = 0x0301, - /// An application sends a WM_PASTE message to an edit control or combo box to copy the current content of the clipboard to the edit control at the current caret position. Data is inserted only if the clipboard contains data in CF_TEXT format. + /// Сообщение WM_PASTE отправляется приложением элементу редактирования или комбинированному списку для вставки содержимого буфера обмена в текущую позицию каретки, данные вставляются только если буфер обмена содержит данные в формате CF_TEXT PASTE = 0x0302, - /// An application sends a WM_CLEAR message to an edit control or combo box to delete (clear) the current selection, if any, from the edit control. + /// Сообщение WM_CLEAR отправляется приложением элементу редактирования или комбинированному списку для удаления текущего выделения, если оно есть CLEAR = 0x0303, - /// An application sends a WM_UNDO message to an edit control to undo the last operation. When this message is sent to an edit control, the previously deleted text is restored or the previously added text is deleted. + /// Сообщение WM_UNDO отправляется элементу редактирования для отмены последней операции, при отправке этого сообщения в элемент редактирования ранее удалённый текст восстанавливается или ранее добавленный текст удаляется UNDO = 0x0304, - /// The WM_RENDERFORMAT message is sent to the clipboard owner if it has delayed rendering a specific clipboard format and if an application has requested data in that format. The clipboard owner must render data in the specified format and place it on the clipboard by calling the SetClipboardData function. + /// Сообщение WM_RENDERFORMAT отправляется владельцу буфера обмена, если он отложил рендеринг конкретного формата и приложение запросило данные в этом формате, владелец должен отрендерить данные и поместить их в буфер обмена, вызвав SetClipboardData RENDERFORMAT = 0x0305, - /// The WM_RENDERALLFORMATS message is sent to the clipboard owner before it is destroyed, if the clipboard owner has delayed rendering one or more clipboard formats. For the content of the clipboard to remain available to other applications, the clipboard owner must render data in all the formats it is capable of generating, and place the data on the clipboard by calling the SetClipboardData function. + /// Сообщение WM_RENDERALLFORMATS отправляется владельцу буфера обмена перед уничтожением, если владелец отложил рендеринг одного или нескольких форматов, чтобы содержимое буфера обмена оставалось доступным другим приложениям, владелец должен отрендерить данные во всех форматах, которые он способен создавать, и поместить данные в буфер обмена, вызвав SetClipboardData RENDERALLFORMATS = 0x0306, - /// The WM_DESTROYCLIPBOARD message is sent to the clipboard owner when a call to the EmptyClipboard function empties the clipboard. + /// Сообщение WM_DESTROYCLIPBOARD отправляется владельцу буфера обмена, когда вызов EmptyClipboard очищает буфер обмена DESTROYCLIPBOARD = 0x0307, - /// The WM_DRAWCLIPBOARD message is sent to the first window in the clipboard viewer chain when the content of the clipboard changes. This enables a clipboard viewer window to display the new content of the clipboard. + /// Сообщение WM_DRAWCLIPBOARD отправляется первому окну в цепочке просмотра буфера обмена при изменении содержимого буфера обмена, это позволяет окну просмотра отображать новое содержимое буфера обмена DRAWCLIPBOARD = 0x0308, - /// The WM_PAINTCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and the clipboard viewer's client area needs repainting. + /// Сообщение WM_PAINTCLIPBOARD отправляется владельцу буфера обмена окном просмотра буфера обмена, когда буфер обмена содержит данные в формате CF_OWNERDISPLAY и клиентская область окна просмотра нуждается в перерисовке PAINTCLIPBOARD = 0x0309, - /// The WM_VSCROLLCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and an event occurs in the clipboard viewer's vertical scroll bar. The owner should scroll the clipboard image and update the scroll bar values. + /// Сообщение WM_VSCROLLCLIPBOARD отправляется владельцу буфера обмена окном просмотра буфера обмена, когда буфер обмена содержит данные в формате CF_OWNERDISPLAY и происходит событие в вертикальной полосе прокрутки окна просмотра, владелец должен прокрутить изображение буфера обмена и обновить значения полосы прокрутки VSCROLLCLIPBOARD = 0x030A, - /// The WM_SIZECLIPBOARD message is sent to the clipboard owner by a clipboard viewer window when the clipboard contains data in the CF_OWNERDISPLAY format and the clipboard viewer's client area has changed size. + /// Сообщение WM_SIZECLIPBOARD отправляется владельцу буфера обмена окном просмотра буфера обмена, когда буфер обмена содержит данные в формате CF_OWNERDISPLAY и размер клиентской области окна просмотра изменился SIZECLIPBOARD = 0x030B, - /// The WM_ASKCBFORMATNAME message is sent to the clipboard owner by a clipboard viewer window to request the name of a CF_OWNERDISPLAY clipboard format. + /// Сообщение WM_ASKCBFORMATNAME отправляется владельцу буфера обмена окном просмотра буфера обмена для запроса имени формата CF_OWNERDISPLAY ASKCBFORMATNAME = 0x030C, - /// The WM_CHANGECBCHAIN message is sent to the first window in the clipboard viewer chain when a window is being removed from the chain. + /// Сообщение WM_CHANGECBCHAIN отправляется первому окну в цепочке просмотра буфера обмена, когда окно удаляется из цепочки CHANGECBCHAIN = 0x030D, - /// The WM_HSCROLLCLIPBOARD message is sent to the clipboard owner by a clipboard viewer window. This occurs when the clipboard contains data in the CF_OWNERDISPLAY format and an event occurs in the clipboard viewer's horizontal scroll bar. The owner should scroll the clipboard image and update the scroll bar values. + /// Сообщение WM_HSCROLLCLIPBOARD отправляется владельцу буфера обмена окном просмотра буфера обмена, это происходит, когда буфер обмена содержит данные в формате CF_OWNERDISPLAY и происходит событие в горизонтальной полосе прокрутки окна просмотра, владелец должен прокрутить изображение буфера обмена и обновить значения полосы прокрутки HSCROLLCLIPBOARD = 0x030E, - /// This message informs a window that it is about to receive the keyboard focus, giving the window the opportunity to realize its logical palette when it receives the focus. + /// Это сообщение информирует окно о том, что оно скоро получит фокус клавиатуры, давая окну возможность реализовать логическую палитру при получении фокуса QUERYNEWPALETTE = 0x030F, - /// The WM_PALETTEISCHANGING message informs applications that an application is going to realize its logical palette. + /// Сообщение WM_PALETTEISCHANGING информирует приложения о том, что приложение собирается реализовать свою логическую палитру PALETTEISCHANGING = 0x0310, /// - /// This message is sent by the OS to all top-level and overlapped windows after the window with the keyboard focus realizes its logical palette. - /// This message enables windows that do not have the keyboard focus to realize their logical palettes and update their client areas. + /// Это сообщение отправляется ОС всем окнам верхнего уровня и перекрывающимся окнам после того, как окно с фокусом клавиатуры реализовало свою логическую палитру + /// Это сообщение позволяет окнам без фокуса клавиатуры реализовать свои логические палитры и обновить клиентские области /// PALETTECHANGED = 0x0311, - /// The WM_HOTKEY message is posted when the user presses a hot key registered by the RegisterHotKey function. The message is placed at the top of the message queue associated with the thread that registered the hot key. + /// Сообщение WM_HOTKEY публикуется при нажатии горячей клавиши, зарегистрированной функцией RegisterHotKey, сообщение помещается в начало очереди сообщений потока, который зарегистрировал горячую клавишу HOTKEY = 0x0312, - /// The WM_PRINT message is sent to a window to request that it draw itself in the specified device context, most commonly in a printer device context. + /// Сообщение WM_PRINT отправляется окну для запроса рисования в указанном контексте устройства, чаще всего в контексте устройства принтера PRINT = 0x0317, - /// The WM_PRINTCLIENT message is sent to a window to request that it draw its client area in the specified device context, most commonly in a printer device context. + /// Сообщение WM_PRINTCLIENT отправляется окну для запроса рисования его клиентской области в указанном контексте устройства, чаще всего в контексте устройства принтера PRINTCLIENT = 0x0318, - /// The WM_APPCOMMAND message notifies a window that the user generated an application command event, for example, by clicking an application command button using the mouse or typing an application command key on the keyboard. + /// Сообщение WM_APPCOMMAND уведомляет окно о том, что пользователь сгенерировал событие команды приложения, например нажатием кнопки команды приложения мышью или вводом команды приложения с клавиатуры APPCOMMAND = 0x0319, - /// The WM_THEMECHANGED message is broadcast to every window following a theme change event. Examples of theme change events are the activation of a theme, the deactivation of a theme, or a transition from one theme to another. + /// Сообщение WM_THEMECHANGED транслируется всем окнам после изменения темы, примеры событий изменения темы — активация темы, деактивация темы или переход от одной темы к другой THEMECHANGED = 0x031A, - /// Sent when the contents of the clipboard have changed. + /// Отправляется при изменении содержимого буфера обмена CLIPBOARDUPDATE = 0x031D, - /// The system will send a window the WM_DWMCOMPOSITIONCHANGED message to indicate that the availability of desktop composition has changed. + /// Система отправляет окну сообщение WM_DWMCOMPOSITIONCHANGED, чтобы указать, что доступность композиции рабочего стола изменилась DWMCOMPOSITIONCHANGED = 0x031E, - /// WM_DWMNCRENDERINGCHANGED is called when the non-client area rendering status of a window has changed. Only windows that have set the flag DWM_BLURBEHIND.fTransitionOnMaximized to true will get this message. + /// WM_DWMNCRENDERINGCHANGED вызывается, когда изменился статус отрисовки неклиентской области окна, это сообщение получают только окна, установившие флаг DWM_BLURBEHIND.fTransitionOnMaximized в true DWMNCRENDERINGCHANGED = 0x031F, - /// Sent to all top-level windows when the colorization color has changed. + /// Отправляется всем окнам верхнего уровня при изменении цвета колоризации DWMCOLORIZATIONCOLORCHANGED = 0x0320, - /// WM_DWMWINDOWMAXIMIZEDCHANGE will let you know when a DWM composed window is maximized. You also have to register for this message as well. You'd have other windowd go opaque when this message is sent. + /// WM_DWMWINDOWMAXIMIZEDCHANGE уведомляет о максимизации окна с композицией DWM, также требуется регистрация на это сообщение, другие окна должны стать непрозрачными при отправке этого сообщения DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - /// Sent to request extended title bar information. A window receives this message through its WindowProc function. + /// Отправляется для запроса расширенной информации о строке заголовка, окно получает это сообщение через функцию WindowProc GETTITLEBARINFOEX = 0x033F, + /// Первое сообщение диапазона Handheld HANDHELDFIRST = 0x0358, + /// Последнее сообщение диапазона Handheld HANDHELDLAST = 0x035F, + /// Первое сообщение диапазона AFX AFXFIRST = 0x0360, + /// Последнее сообщение диапазона AFX AFXLAST = 0x037F, + /// Первое сообщение диапазона PENWIN PENWINFIRST = 0x0380, + /// Последнее сообщение диапазона PENWIN PENWINLAST = 0x038F, - /// The WM_APP constant is used by applications to help define private messages, usually of the form WM_APP+X, where X is an integer value. + /// Константа WM_APP используется приложениями для определения частных сообщений, обычно в виде WM_APP+X, где X — целое значение APP = 0x8000, - /// The WM_USER constant is used by applications to help define private messages for use by private window classes, usually of the form WM_USER+X, where X is an integer value. + /// Константа WM_USER используется приложениями для определения частных сообщений для закрытых классов окон, обычно в виде WM_USER+X, где X — целое значение USER = 0x0400, - /// An application sends the WM_CPL_LAUNCH message to Windows Control Panel to request that a Control Panel application be started. + /// Сообщение WM_CPL_LAUNCH отправляется приложением Windows Control Panel для запроса запуска приложения панели управления CPL_LAUNCH = USER + 0x1000, - /// The WM_CPL_LAUNCHED message is sent when a Control Panel application, started by the WM_CPL_LAUNCH message, has closed. The WM_CPL_LAUNCHED message is sent to the window identified by the wParam parameter of the WM_CPL_LAUNCH message that started the application. + /// Сообщение WM_CPL_LAUNCHED отправляется, когда приложение панели управления, запущенное сообщением WM_CPL_LAUNCH, завершено, сообщение WM_CPL_LAUNCHED отправляется окну, заданному параметром wParam сообщения WM_CPL_LAUNCH, которое запустило приложение CPL_LAUNCHED = USER + 0x1001, - /// WM_SYSTIMER is a well-known yet still undocumented message. Windows uses WM_SYSTIMER for internal actions like scrolling. + /// WM_SYSTIMER — это известное, но всё ещё недокументированное сообщение, Windows использует WM_SYSTIMER для внутренних действий, таких как прокрутка SYSTIMER = 0x118, - /// The accessibility state has changed. + /// Состояние доступности изменилось HSHELL_ACCESSIBILITYSTATE = 11, - /// The shell should activate its main window. + /// Оболочка должна активировать своё главное окно HSHELL_ACTIVATESHELLWINDOW = 3, /// - /// The user completed an input event (for example, pressed an application command button on the mouse or an application command key on the keyboard), and the application did not handle the WM_APPCOMMAND message generated by that input. - /// If the Shell procedure handles the WM_COMMAND message, it should not call CallNextHookEx. See the Return Value section for more information. + /// Пользователь завершил событие ввода, например нажал кнопку команды приложения на мыши или клавишу команды приложения на клавиатуре, а приложение не обработало WM_APPCOMMAND, сгенерированное этим вводом + /// Если процедура оболочки обрабатывает WM_COMMAND, она не должна вызывать CallNextHookEx, см. раздел Return Value для дополнительной информации /// HSHELL_APPCOMMAND = 12, - /// A window is being minimized or maximized. The system needs the coordinates of the minimized rectangle for the window. + /// Окно минимизируется или максимизируется, системе нужны координаты прямоугольника минимизированного окна HSHELL_GETMINRECT = 5, - /// Keyboard language was changed or a new keyboard layout was loaded. + /// Язык клавиатуры был изменён или загружена новая раскладка HSHELL_LANGUAGE = 8, - /// The title of a window in the task bar has been redrawn. + /// Заголовок окна на панели задач был перерисован HSHELL_REDRAW = 6, - /// The user has selected the task list. A shell application that provides a task list should return TRUE to prevent Windows from starting its task list. + /// Пользователь выбрал список задач, приложение оболочки, предоставляющее список задач, должно вернуть TRUE, чтобы предотвратить запуск списка задач Windows HSHELL_TASKMAN = 7, - /// A top-level, unowned window has been created. The window exists when the system calls this hook. + /// Создано окно верхнего уровня без владельца, окно существует, когда система вызывает этот перехват HSHELL_WINDOWCREATED = 1, - /// A top-level, unowned window is about to be destroyed. The window still exists when the system calls this hook. + /// Окно верхнего уровня без владельца собирается быть уничтожено, окно ещё существует, когда система вызывает этот перехват HSHELL_WINDOWDESTROYED = 2, - /// The activation has changed to a different top-level, unowned window. + /// Активация изменилась на другое окно верхнего уровня без владельца HSHELL_WINDOWACTIVATED = 4, - /// A top-level window is being replaced. The window exists when the system calls this hook. + /// Окно верхнего уровня заменяется, окно существует, когда система вызывает этот перехват HSHELL_WINDOWREPLACED = 13 } \ No newline at end of file From 90656920f647b9065967d39083132450036d964f Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 17:25:11 +0300 Subject: [PATCH 16/33] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20FileDialogEx=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20WPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Существенно расширена структура FileDialogEx: добавлены новые свойства для гибкой настройки диалогов открытия/сохранения файлов, реализованы fluent-методы и фабрики, поддержка популярных фильтров, методы для работы с содержимым файлов и универсальные обработчики. Вынесена общая логика настройки диалогов, добавлены подробные XML-документации. Улучшена пригодность для повторного использования и расширены сценарии применения в WPF. --- MathCore.WPF/FileDialogEx.cs | 412 +++++++++++++++++++++++++++++++++-- 1 file changed, 389 insertions(+), 23 deletions(-) diff --git a/MathCore.WPF/FileDialogEx.cs b/MathCore.WPF/FileDialogEx.cs index e3a85c6..5873560 100644 --- a/MathCore.WPF/FileDialogEx.cs +++ b/MathCore.WPF/FileDialogEx.cs @@ -5,14 +5,21 @@ namespace MathCore.WPF; +/// Построитель диалоговых окон выбора файла для открытия или сохранения public readonly ref struct FileDialogEx { + /// Элемент фильтра файлов в диалоговом окне public readonly struct FileFilterItem : IEquatable { + /// Название фильтра, отображаемое пользователю public string Title { get; init; } + /// Коллекция расширений файлов для фильтрации public IEnumerable? Values { get; init; } + /// Инициализирует новый элемент фильтра файлов + /// Название фильтра + /// Массив расширений файлов public FileFilterItem(string Title, params string[] Value) { this.Title = Title; @@ -20,6 +27,8 @@ public FileFilterItem(string Title, params string[] Value) Values = Value.Distinct(); } + /// Добавляет строковое представление фильтра к StringBuilder + /// StringBuilder для добавления строки фильтра internal void AppendTo(StringBuilder filter) { filter.Append(Title).Append(" ("); @@ -43,7 +52,7 @@ static void AppendFilterText(StringBuilder str, IEnumerable vvv) } if (any) - str.Length--; + str.Length--; // Удаление последнего разделителя else str.Append("*.*"); } @@ -51,6 +60,9 @@ static void AppendFilterText(StringBuilder str, IEnumerable vvv) filter.Append('|'); } + /// Определяет, равен ли текущий элемент фильтра указанному + /// Элемент фильтра для сравнения + /// true, если элементы равны; иначе false public bool Equals(FileFilterItem other) => Title == other.Title && Values.SequenceEqual(other.Values, StringComparer.OrdinalIgnoreCase); public override bool Equals(object? obj) => obj is FileFilterItem item && Equals(item); @@ -68,85 +80,255 @@ public override int GetHashCode() } } + /// Создает построитель диалога открытия файла + /// Новый экземпляр FileDialogEx для открытия файла public static FileDialogEx OpenFile() => new() { IsSaveFileDialog = false }; + + /// Создает построитель диалога открытия файла с указанным заголовком + /// Заголовок диалогового окна + /// Новый экземпляр FileDialogEx для открытия файла public static FileDialogEx OpenFile(string Title) => new() { IsSaveFileDialog = false, Title = Title }; + /// Создает построитель диалога сохранения файла + /// Новый экземпляр FileDialogEx для сохранения файла public static FileDialogEx CreateFile() => new() { IsSaveFileDialog = true }; + + /// Создает построитель диалога сохранения файла с указанным заголовком + /// Заголовок диалогового окна + /// Новый экземпляр FileDialogEx для сохранения файла public static FileDialogEx CreateFile(string Title) => new() { IsSaveFileDialog = true, Title = Title }; + /// Создает новый построитель диалога файла + /// Новый экземпляр FileDialogEx public static FileDialogEx New() => new(); + + /// Создает новый построитель диалога файла с указанным заголовком + /// Заголовок диалогового окна + /// Новый экземпляр FileDialogEx public static FileDialogEx New(string Title) => new() { Title = Title }; + /// Определяет, является ли диалог диалогом сохранения файла public bool IsSaveFileDialog { get; init; } + /// Заголовок диалогового окна public string Title { get; init; } + /// Определяет, следует ли восстанавливать текущую директорию при закрытии диалога public bool? RestoreDirectory { get; init; } + /// Начальная директория, отображаемая в диалоге public string? InitialDirectory { get; init; } + /// Определяет, следует ли проверять существование выбранного файла public bool? CheckFileExists { get; init; } + /// Определяет, установлен ли флажок "Только для чтения" public bool? ReadOnlyChecked { get; init; } + /// Коллекция фильтров файлов для диалога + public IEnumerable? Filter { get; init; } + + /// Определяет, можно ли выбрать несколько файлов одновременно + public bool? Multiselect { get; init; } + + /// Имя файла по умолчанию + public string? DefaultFileName { get; init; } + + /// Расширение файла по умолчанию (без точки) + public string? DefaultExtension { get; init; } + + /// Индекс выбранного фильтра файлов (начиная с 1) + public int? FilterIndex { get; init; } + + /// Определяет, следует ли проверять существование пути к файлу + public bool? CheckPathExists { get; init; } + + /// Определяет, следует ли автоматически добавлять расширение к имени файла + public bool? AddExtension { get; init; } + + /// Определяет, следует ли разыменовывать ярлыки (.lnk файлы) + public bool? DereferenceLinks { get; init; } + + /// Определяет, следует ли проверять корректность имен файлов + public bool? ValidateNames { get; init; } + + /// Запрашивает подтверждение при перезаписи существующего файла (только для SaveFileDialog) + public bool? OverwritePrompt { get; init; } + + /// Запрашивает подтверждение при создании нового файла (только для SaveFileDialog) + public bool? CreatePrompt { get; init; } + public FileDialogEx() { } + /// Устанавливает заголовок диалогового окна + /// Новый заголовок + /// Новый экземпляр FileDialogEx с обновленным заголовком public FileDialogEx SetTitle(string Title) => this with { Title = Title }; - public IEnumerable? Filter { get; init; } + /// Устанавливает начальную директорию + /// Путь к начальной директории + /// Новый экземпляр FileDialogEx с установленной начальной директорией + public FileDialogEx WithInitialDirectory(string Path) => this with { InitialDirectory = Path }; + + /// Включает восстановление текущей директории при закрытии диалога + /// Значение флага восстановления + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithRestoreDirectory(bool Restore = true) => this with { RestoreDirectory = Restore }; + + /// Включает проверку существования выбранного файла + /// Значение флага проверки + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithCheckFileExists(bool Check = true) => this with { CheckFileExists = Check }; + + /// Включает проверку существования пути к файлу + /// Значение флага проверки + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithCheckPathExists(bool Check = true) => this with { CheckPathExists = Check }; + + /// Устанавливает флажок "Только для чтения" + /// Значение флажка + /// Новый экземпляр FileDialogEx с установленным флажком + public FileDialogEx WithReadOnlyChecked(bool ReadOnly = true) => this with { ReadOnlyChecked = ReadOnly }; + + /// Включает возможность множественного выбора файлов + /// Значение флага множественного выбора + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx EnableMultiselect(bool Enable = true) => this with { Multiselect = Enable }; + + /// Устанавливает имя файла по умолчанию + /// Имя файла + /// Новый экземпляр FileDialogEx с установленным именем файла + public FileDialogEx WithDefaultFileName(string FileName) => this with { DefaultFileName = FileName }; + + /// Устанавливает расширение файла по умолчанию + /// Расширение файла (без точки) + /// Новый экземпляр FileDialogEx с установленным расширением + public FileDialogEx WithDefaultExtension(string Extension) => this with { DefaultExtension = Extension.TrimStart('.') }; + + /// Устанавливает индекс активного фильтра + /// Индекс фильтра (начиная с 1) + /// Новый экземпляр FileDialogEx с установленным индексом фильтра + public FileDialogEx WithFilterIndex(int Index) => this with { FilterIndex = Index }; + + /// Включает автоматическое добавление расширения к имени файла + /// Значение флага + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithAddExtension(bool Add = true) => this with { AddExtension = Add }; + + /// Включает разыменование ярлыков + /// Значение флага + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithDereferenceLinks(bool Dereference = true) => this with { DereferenceLinks = Dereference }; + + /// Включает проверку корректности имен файлов + /// Значение флага + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithValidateNames(bool Validate = true) => this with { ValidateNames = Validate }; + + /// Включает запрос подтверждения при перезаписи файла + /// Значение флага + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithOverwritePrompt(bool Prompt = true) => this with { OverwritePrompt = Prompt }; + + /// Включает запрос подтверждения при создании нового файла + /// Значение флага + /// Новый экземпляр FileDialogEx с установленным флагом + public FileDialogEx WithCreatePrompt(bool Prompt = true) => this with { CreatePrompt = Prompt }; #if NET5_0_OR_GREATER + /// Добавляет новый фильтр файлов к диалогу + /// Название фильтра + /// Массив расширений файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром public FileDialogEx AddFilter(string Name, params string[] Ext) => Filter is { } filter ? this with { Filter = filter.Append(new(Name, Ext)) } : this with { Filter = new FileFilterItem[] { new(Name, Ext) } }; #else + /// Добавляет новый фильтр файлов к диалогу + /// Название фильтра + /// Массив расширений файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром public FileDialogEx AddFilter(string Name, params string[] Ext) => Filter is { } filter ? this with { Filter = filter.AppendLast(new FileFilterItem(Name, Ext)) } : this with { Filter = [new(Name, Ext)] }; #endif + /// Добавляет фильтр "Все файлы" к диалогу, если он еще не добавлен + /// Новый экземпляр FileDialogEx с добавленным фильтром всех файлов public FileDialogEx AddFilterAllFiles() => Filter is null || Filter.Last().Title != "Все файлы" ? AddFilter("Все файлы", "*.*") : this; - public OpenFileDialog CreateOpenFileDialog() - { - var dialog = new OpenFileDialog(); + /// Добавляет фильтр для текстовых файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddTextFilesFilter() => AddFilter("Текстовые файлы", "*.txt"); - if (Title is not null) - dialog.Title = Title; + /// Добавляет фильтр для файлов изображений + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddImageFilesFilter() => AddFilter("Изображения", "*.png", "*.jpg", "*.jpeg", "*.bmp", "*.gif", "*.tif", "*.tiff", "*.ico"); - if (Filter is { } filter) - { - var filter_str = new StringBuilder(); + /// Добавляет фильтр для JSON файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddJsonFilesFilter() => AddFilter("JSON файлы", "*.json"); - foreach (var item in filter) - item.AppendTo(filter_str); + /// Добавляет фильтр для XML файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddXmlFilesFilter() => AddFilter("XML файлы", "*.xml"); - filter_str.Length--; + /// Добавляет фильтр для CSV файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddCsvFilesFilter() => AddFilter("CSV файлы", "*.csv"); - dialog.Filter = filter_str.ToString(); - } + /// Добавляет фильтр для документов Microsoft Word + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddWordFilesFilter() => AddFilter("Документы Word", "*.docx", "*.doc"); - if (RestoreDirectory is { } restore_directory) - dialog.RestoreDirectory = restore_directory; + /// Добавляет фильтр для документов Microsoft Excel + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddExcelFilesFilter() => AddFilter("Документы Excel", "*.xlsx", "*.xls"); - if (InitialDirectory is { Length: > 0 } initial_directory) - dialog.InitialDirectory = initial_directory; + /// Добавляет фильтр для PDF файлов + /// Новый экземпляр FileDialogEx с добавленным фильтром + public FileDialogEx AddPdfFilesFilter() => AddFilter("PDF файлы", "*.pdf"); - if (CheckFileExists is { } check_file_exists) - dialog.CheckFileExists = check_file_exists; + /// Создает и настраивает экземпляр OpenFileDialog + /// Настроенный экземпляр OpenFileDialog + public OpenFileDialog CreateOpenFileDialog() + { + var dialog = new OpenFileDialog(); + + ApplyCommonSettings(dialog); if (ReadOnlyChecked is { } read_only_checked) dialog.ReadOnlyChecked = read_only_checked; + if (Multiselect is { } multiselect) + dialog.Multiselect = multiselect; + return dialog; } + /// Создает и настраивает экземпляр SaveFileDialog + /// Настроенный экземпляр SaveFileDialog public SaveFileDialog CreateSaveFileDialog() { var dialog = new SaveFileDialog(); + ApplyCommonSettings(dialog); + + if (OverwritePrompt is { } overwrite_prompt) + dialog.OverwritePrompt = overwrite_prompt; + + if (CreatePrompt is { } create_prompt) + dialog.CreatePrompt = create_prompt; + + return dialog; + } + + /// Применяет общие настройки к диалогу + /// Диалог для настройки + private void ApplyCommonSettings(FileDialog dialog) + { if (Title is not null) dialog.Title = Title; @@ -157,23 +339,52 @@ public SaveFileDialog CreateSaveFileDialog() foreach (var item in filter) item.AppendTo(filter_str); - filter_str.Length--; + filter_str.Length--; // Удаление последнего разделителя dialog.Filter = filter_str.ToString(); } + if (FilterIndex is { } filter_index) + dialog.FilterIndex = filter_index; + if (RestoreDirectory is { } restore_directory) dialog.RestoreDirectory = restore_directory; if (InitialDirectory is { Length: > 0 } initial_directory) dialog.InitialDirectory = initial_directory; + if (DefaultFileName is { Length: > 0 } default_file_name) + dialog.FileName = default_file_name; + + if (DefaultExtension is { Length: > 0 } default_extension) + dialog.DefaultExt = default_extension; + if (CheckFileExists is { } check_file_exists) dialog.CheckFileExists = check_file_exists; - return dialog; + if (CheckPathExists is { } check_path_exists) + dialog.CheckPathExists = check_path_exists; + + if (AddExtension is { } add_extension) + dialog.AddExtension = add_extension; + + if (DereferenceLinks is { } dereference_links) + dialog.DereferenceLinks = dereference_links; + + if (ValidateNames is { } validate_names) + dialog.ValidateNames = validate_names; } + /// Отображает диалог и возвращает полный путь к выбранному файлу + /// Полный путь к выбранному файлу или null, если диалог был отменен + /// + /// + /// var file_name = FileDialogEx.OpenFile("Выберите файл") + /// .AddFilter("Текстовые файлы", "*.txt") + /// .AddFilterAllFiles() + /// .GetFileName(); + /// + /// public string? GetFileName() { FileDialog dialog = IsSaveFileDialog ? CreateSaveFileDialog() : CreateOpenFileDialog(); @@ -184,11 +395,166 @@ public SaveFileDialog CreateSaveFileDialog() return dialog.FileName; } + /// Отображает диалог и возвращает массив путей к выбранным файлам + /// Массив путей к выбранным файлам или null, если диалог был отменен + /// Работает только для диалога открытия файла с включенным Multiselect + public string[]? GetFileNames() + { + if (IsSaveFileDialog) + return GetFileName() is { } file_name ? [file_name] : null; + + var dialog = CreateOpenFileDialog(); + + if (dialog.ShowDialog() != true) + return null; + + return dialog.FileNames; + } + + /// Отображает диалог и возвращает FileInfo для выбранного файла + /// FileInfo выбранного файла или null, если диалог был отменен public FileInfo? GetFileInfo() => GetFileName() is { } file_name ? new(file_name) : null; + /// Отображает диалог и возвращает массив FileInfo для выбранных файлов + /// Массив FileInfo выбранных файлов или null, если диалог был отменен + public FileInfo[]? GetFileInfos() => GetFileNames() is { } file_names + ? file_names.Select(f => new FileInfo(f)).ToArray() + : null; + + /// Отображает диалог и открывает поток для выбранного файла + /// FileStream для выбранного файла или null, если диалог был отменен + /// Для диалога сохранения создается новый файл, для диалога открытия - открывается для чтения public FileStream? OpenFileStream() => GetFileName() is { } file_name ? IsSaveFileDialog ? File.Create(file_name) : File.OpenRead(file_name) : null; + + /// Отображает диалог и читает весь текст из выбранного файла + /// Кодировка текста (по умолчанию UTF-8) + /// Содержимое файла в виде строки или null, если диалог был отменен + public string? ReadAllText(Encoding? Encoding = null) => GetFileName() is { } file_name + ? Encoding is null + ? File.ReadAllText(file_name) + : File.ReadAllText(file_name, Encoding) + : null; + + /// Отображает диалог и читает все строки из выбранного файла + /// Кодировка текста (по умолчанию UTF-8) + /// Массив строк файла или null, если диалог был отменен + public string[]? ReadAllLines(Encoding? Encoding = null) => GetFileName() is { } file_name + ? Encoding is null + ? File.ReadAllLines(file_name) + : File.ReadAllLines(file_name, Encoding) + : null; + + /// Отображает диалог и читает все байты из выбранного файла + /// Массив байтов файла или null, если диалог был отменен + public byte[]? ReadAllBytes() => GetFileName() is { } file_name ? File.ReadAllBytes(file_name) : null; + + /// Отображает диалог и записывает текст в выбранный файл + /// Содержимое для записи + /// Кодировка текста (по умолчанию UTF-8) + /// true, если файл был успешно записан; false, если диалог был отменен + public bool WriteAllText(string Content, Encoding? Encoding = null) + { + if (GetFileName() is not { } file_name) + return false; + + if (Encoding is null) + File.WriteAllText(file_name, Content); + else + File.WriteAllText(file_name, Content, Encoding); + + return true; + } + + /// Отображает диалог и записывает массив строк в выбранный файл + /// Строки для записи + /// Кодировка текста (по умолчанию UTF-8) + /// true, если файл был успешно записан; false, если диалог был отменен + public bool WriteAllLines(string[] Lines, Encoding? Encoding = null) + { + if (GetFileName() is not { } file_name) + return false; + + if (Encoding is null) + File.WriteAllLines(file_name, Lines); + else + File.WriteAllLines(file_name, Lines, Encoding); + + return true; + } + + /// Отображает диалог и записывает байты в выбранный файл + /// Байты для записи + /// true, если файл был успешно записан; false, если диалог был отменен + public bool WriteAllBytes(byte[] Content) + { + if (GetFileName() is not { } file_name) + return false; + + File.WriteAllBytes(file_name, Content); + return true; + } + + /// Отображает диалог и выполняет действие с выбранным файлом + /// Тип возвращаемого значения + /// Функция для выполнения с путем к файлу + /// Результат выполнения функции или default, если диалог был отменен + /// + /// + /// var lines_count = FileDialogEx.OpenFile() + /// .AddTextFilesFilter() + /// .WithFile(path => File.ReadAllLines(path).Length); + /// + /// + public T? WithFile(Func Action) + { + if (Action is null) throw new ArgumentNullException(nameof(Action)); + + return GetFileName() is { } file_name ? Action(file_name) : default; + } + + /// Отображает диалог и выполняет действие с потоком выбранного файла + /// Тип возвращаемого значения + /// Функция для выполнения с потоком файла + /// Результат выполнения функции или default, если диалог был отменен + /// + /// + /// var content = FileDialogEx.OpenFile() + /// .AddTextFilesFilter() + /// .WithFileStream(stream => + /// { + /// using var reader = new StreamReader(stream); + /// return reader.ReadToEnd(); + /// }); + /// + /// + public T? WithFileStream(Func Action) + { + if (Action is null) throw new ArgumentNullException(nameof(Action)); + + if (OpenFileStream() is not { } stream) + return default; + + using (stream) + return Action(stream); + } + + /// Отображает диалог и выполняет действие для каждого выбранного файла + /// Действие для выполнения с каждым файлом + /// Количество обработанных файлов + public int ForEachFile(Action Action) + { + if (Action is null) throw new ArgumentNullException(nameof(Action)); + + if (GetFileNames() is not { } file_names) + return 0; + + foreach (var file_name in file_names) + Action(file_name); + + return file_names.Length; + } } From 026d93f819990f56ee9ede8b5abfa21cabe01c6a Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 17:27:14 +0300 Subject: [PATCH 17/33] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/Filter.cs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/MathCore.WPF/Filter.cs b/MathCore.WPF/Filter.cs index 0b3917a..1e278c2 100644 --- a/MathCore.WPF/Filter.cs +++ b/MathCore.WPF/Filter.cs @@ -6,16 +6,13 @@ namespace MathCore.WPF; -// Defines SearchPredicate delegate alias as -/// -/// Provides a search/filter for items bind to an ItemsControl. -/// To use this control, simply place an ItemsControl object as the content -/// +/// Предоставляет возможность поиска/фильтрации элементов, привязанных к ItemsControl. Для использования этого элемента управления просто поместите объект ItemsControl в качестве содержимого [TemplatePart(Name = "PART_FilterBox")] public class Filter : HeaderedContentControl { static Filter() => DefaultStyleKeyProperty.OverrideMetadata(typeof(Filter), new FrameworkPropertyMetadata(typeof(Filter))); + /// Стратегия поиска по умолчанию, выполняющая текстовый поиск по содержимому элемента public static readonly Func ContentTextSearch = (element, pattern) => { if(string.IsNullOrEmpty(pattern)) return true; @@ -23,16 +20,27 @@ public class Filter : HeaderedContentControl return (container.Content?.ToString() ?? element.ToString()).ToLower().Contains(pattern.ToLower()); }; + /// Свойство зависимости для стиля текстового поля фильтра public static readonly DependencyProperty FilterBoxStyleProperty = DependencyProperty.Register(nameof(FilterBoxStyle), typeof(Style), typeof(Filter), new FrameworkPropertyMetadata(null, (_, _) => { })); + + /// Стиль текстового поля фильтра public Style FilterBoxStyle { get => (Style)GetValue(FilterBoxStyleProperty); set => SetValue(FilterBoxStyleProperty, value); } + /// Свойство зависимости для шаблона поиска public static readonly DependencyProperty PatternProperty = DependencyProperty.Register(nameof(Pattern), typeof(string), typeof(Filter), new FrameworkPropertyMetadata(string.Empty, (s, _) => ((Filter)s).View.Refresh())); + + /// Шаблон поиска для фильтрации элементов public string Pattern { get => (string)GetValue(PatternProperty); set => SetValue(PatternProperty, value); } + /// Свойство зависимости для стратегии поиска public static readonly DependencyProperty SearchStrategyProperty = DependencyProperty.Register(nameof(SearchStrategy), typeof(Func), typeof(Filter), new FrameworkPropertyMetadata(ContentTextSearch, OnSearchStrategyChanged)); + /// Стратегия поиска, определяющая, соответствует ли элемент заданному шаблону public Func SearchStrategy { get => (Func)GetValue(SearchStrategyProperty); set => SetValue(SearchStrategyProperty, value); } + /// Обработчик изменения стратегии поиска + /// Объект зависимости + /// Аргументы события изменения свойства private static void OnSearchStrategyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var f = (Filter)d; @@ -40,10 +48,15 @@ private static void OnSearchStrategyChanged(DependencyObject d, DependencyProper f.View.Filter = i => f.SearchStrategy(i, f.Pattern); } + /// Свойство зависимости для источника элементов private static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(Filter), new FrameworkPropertyMetadata(null, OnItemsSourceChanged)); + /// Источник элементов для фильтрации private IEnumerable ItemsSource { get => (IEnumerable)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } + /// Обработчик изменения источника элементов + /// Объект зависимости + /// Аргументы события изменения свойства private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if(e.OldValue is IEnumerable old) @@ -54,12 +67,18 @@ private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyC CollectionViewSource.GetDefaultView(@new).Filter = i => f.SearchStrategy(i, f.Pattern); } - private TextBox _FilterBox; + private TextBox _FilterBox; // Текстовое поле фильтра + /// Инициализирует новый экземпляр класса Filter public Filter() => FilterBoxStyle = (Style)TryFindResource(new ComponentResourceKey(typeof(Filter), "FilterBoxStyle")); + /// Представление коллекции для фильтрации private ICollectionView? View => CollectionViewSource.GetDefaultView(ItemsSource); + /// Вызывается при изменении содержимого элемента управления + /// Старое содержимое + /// Новое содержимое + /// Содержимое или шаблон содержимого должны быть типа ItemsControl protected override void OnContentChanged(object OldContent, object NewContent) { if(NewContent is not ItemsControl control) @@ -70,6 +89,8 @@ protected override void OnContentChanged(object OldContent, object NewContent) base.OnContentChanged(OldContent, NewContent); } + /// Вызывается при применении шаблона элемента управления + /// Шаблон элемента управления Filter должен содержать хотя бы один элемент TextBox с именем PART_FilterBox public override void OnApplyTemplate() { _FilterBox = Template.FindName("PART_FilterBox", this) as TextBox; From f867afc17d4e3fd2a1632e90ae974a2b039046f1 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 17:29:54 +0300 Subject: [PATCH 18/33] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20GIF-=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен метод ResetState для сброса состояния перед инициализацией. Анимация теперь создаётся только для анимированных GIF. Добавлены проверки на корректность источника, индекса кадра и состояния декодера. Использован pattern matching для безопасной работы с DependencyObject. Повышена надёжность и предотвращены ошибки при некорректных данных. --- MathCore.WPF/GIF.cs | 54 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/MathCore.WPF/GIF.cs b/MathCore.WPF/GIF.cs index a2fc31e..56a92d9 100644 --- a/MathCore.WPF/GIF.cs +++ b/MathCore.WPF/GIF.cs @@ -18,21 +18,37 @@ public class GIF : Image private void Initialize() { + ResetState(); + + if (string.IsNullOrWhiteSpace(GifSource)) return; + if (!Uri.TryCreate(GifSource, UriKind.RelativeOrAbsolute, out var gif_uri)) return; + // Инициализируем декодер по заданному источнику GIF - _GifDecoder = new(new Uri(GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + _GifDecoder = new(gif_uri, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + + if (_GifDecoder.Frames.Count == 0) return; - _Animation = new( - fromValue: 0, - toValue: _GifDecoder.Frames.Count - 1, - // ReSharper disable once PossibleLossOfFraction - duration: new(new(0, 0, 0, _GifDecoder.Frames.Count / 10, (int)(1000 * (_GifDecoder.Frames.Count / 10d - _GifDecoder.Frames.Count / 10))))) - { RepeatBehavior = RepeatBehavior.Forever }; // настраиваем бесконечную анимацию + if (_GifDecoder.Frames.Count > 1) + _Animation = new( + fromValue: 0, + toValue: _GifDecoder.Frames.Count - 1, + // ReSharper disable once PossibleLossOfFraction + duration: new(new(0, 0, 0, _GifDecoder.Frames.Count / 10, (int)(1000 * (_GifDecoder.Frames.Count / 10d - _GifDecoder.Frames.Count / 10))))) + { RepeatBehavior = RepeatBehavior.Forever }; // настраиваем бесконечную анимацию Source = _GifDecoder.Frames[0]; // устанавливаем первый кадр как источник изображения _IsInitialized = true; // помечаем как инициализированное } + private void ResetState() + { + _IsInitialized = false; + _GifDecoder = null; + _Animation = null; + Source = null; + } + static GIF() => VisibilityProperty.OverrideMetadata( typeof(GIF), @@ -40,10 +56,12 @@ static GIF() => private static void VisibilityPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { + if (sender is not GIF gif) return; + if ((Visibility)e.NewValue == Visibility.Visible) - ((GIF)sender).StartAnimation(); // если стало видимым — запускаем анимацию + gif.StartAnimation(); // если стало видимым — запускаем анимацию else - ((GIF)sender).StopAnimation(); // иначе — останавливаем + gif.StopAnimation(); // иначе — останавливаем } /// DependencyProperty для индекса кадра @@ -57,7 +75,12 @@ private static void VisibilityPropertyChanged(DependencyObject sender, Dependenc private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev) { if (obj is not GIF gif_image) return; - gif_image.Source = gif_image._GifDecoder.Frames[(int)ev.NewValue]; // меняем отображаемый кадр + if (gif_image._GifDecoder is null) return; + + var frame_index = (int)ev.NewValue; + if (frame_index < 0 || frame_index >= gif_image._GifDecoder.Frames.Count) return; + + gif_image.Source = gif_image._GifDecoder.Frames[frame_index]; // меняем отображаемый кадр } /// Определяет будет ли анимация запускаться автоматически @@ -77,8 +100,9 @@ public bool AutoStart private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { + if (sender is not GIF gif) return; if ((bool)e.NewValue) - (sender as GIF).StartAnimation(); // при установке true запускаем анимацию + gif.StartAnimation(); // при установке true запускаем анимацию } /// Путь или URI к GIF-изображению @@ -96,7 +120,11 @@ public string GifSource typeof(GIF), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged)); - private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) => (sender as GIF).Initialize(); // при смене источника — инициализируем + private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (sender is GIF gif) + gif.Initialize(); // при смене источника — инициализируем + } /// Запустить анимацию GIF public void StartAnimation() @@ -104,6 +132,8 @@ public void StartAnimation() if (!_IsInitialized) Initialize(); // ленивое создание ресурсов + if (!_IsInitialized || _Animation is null) return; + BeginAnimation(FrameIndexProperty, _Animation); // запускаем анимацию смены индекса кадра } From 79c7e42e7b3f52cac79c9e2c8ce9578219937bcb Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 17:32:44 +0300 Subject: [PATCH 19/33] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/LanguageManager.cs | 9 +- MathCore.WPF/NestedBinding.cs | 18 ++ MathCore.WPF/NestedBindingConverter.cs | 76 +++++ MathCore.WPF/NestedBindingNode.cs | 24 ++ MathCore.WPF/NestedBindingsTree.cs | 36 +++ MathCore.WPF/PasswordBoxHelper.cs | 29 +- MathCore.WPF/RadialProgressIndicator.cs | 380 ++++++++---------------- 7 files changed, 296 insertions(+), 276 deletions(-) diff --git a/MathCore.WPF/LanguageManager.cs b/MathCore.WPF/LanguageManager.cs index becfd42..58b331e 100644 --- a/MathCore.WPF/LanguageManager.cs +++ b/MathCore.WPF/LanguageManager.cs @@ -4,11 +4,12 @@ namespace MathCore.WPF; +/// Менеджер переключения языка ввода public class LanguageManager : DependencyObject { #region InputCulture - /// + /// Свойство зависимости для текущей культуры ввода public static readonly DependencyProperty InputCultureProperty = DependencyProperty.Register( nameof(InputCulture), @@ -17,7 +18,7 @@ public class LanguageManager : DependencyObject new(InputLanguageManager.Current.CurrentInputLanguage, (s, e) => ChangeCulture((CultureInfo)e.NewValue))); - /// + /// Текущая культура ввода public CultureInfo InputCulture { get => (CultureInfo)GetValue(InputCultureProperty); set => SetValue(InputCultureProperty, value); } #endregion @@ -25,8 +26,10 @@ public class LanguageManager : DependencyObject #region Singleton private static volatile LanguageManager __Manager; + private static readonly object __ManagerSyncRoot = new(); + /// Текущий экземпляр менеджера языка ввода public static LanguageManager Current { get @@ -46,5 +49,7 @@ public static LanguageManager Current private void OnLanguageChanged(object Sender, InputLanguageEventArgs E) => InputCulture = E.NewLanguage; + /// Изменить текущую культуру ввода + /// Новая культура ввода private static void ChangeCulture(CultureInfo culture) => InputLanguageManager.Current.CurrentInputLanguage = culture; } \ No newline at end of file diff --git a/MathCore.WPF/NestedBinding.cs b/MathCore.WPF/NestedBinding.cs index 419ad99..22b2595 100644 --- a/MathCore.WPF/NestedBinding.cs +++ b/MathCore.WPF/NestedBinding.cs @@ -6,6 +6,24 @@ namespace MathCore.WPF; /// Расширение разметки для создания вложенных привязок. +/// +/// Пример использования вложенных привязок в XAML: +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ]]> +/// В этом примере InnerConverter сначала преобразует Property2 и Property3, +/// затем OuterConverter преобразует Property1 и результат InnerConverter. +/// [Copyright("adeptuss", url = "https://habr.com/ru/post/277157/")] [ContentProperty(nameof(Bindings))] [MarkupExtensionReturnType(typeof(object))] diff --git a/MathCore.WPF/NestedBindingConverter.cs b/MathCore.WPF/NestedBindingConverter.cs index 48b2422..d45f14f 100644 --- a/MathCore.WPF/NestedBindingConverter.cs +++ b/MathCore.WPF/NestedBindingConverter.cs @@ -3,16 +3,85 @@ namespace MathCore.WPF; +/// Преобразователь значений для вложенных привязок +/// +/// Этот класс используется внутри для обработки дерева вложенных привязок. +/// Преобразователь рекурсивно обходит дерево и применяет соответствующие конвертеры к значениям. +/// +/// +/// Этот класс создаётся автоматически при использовании и не требует ручного создания. +/// Пример пользовательских конвертеров для использования с вложенными привязками: +/// v?.ToString() ?? "")); +/// } +/// +/// public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) +/// { +/// throw new NotSupportedException(); +/// } +/// } +/// +/// // Конвертер для математических операций +/// public class MathConverter : IMultiValueConverter +/// { +/// public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) +/// { +/// if (values.Length != 2) return 0; +/// var a = System.Convert.ToDouble(values[0]); +/// var b = System.Convert.ToDouble(values[1]); +/// return a + b; +/// } +/// +/// public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) +/// { +/// throw new NotSupportedException(); +/// } +/// } +/// ]]> +/// Использование в XAML: +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ]]> +/// В этом примере MathConverter сначала суммирует Value1 и Value2, затем StringConcatConverter объединяет FirstName с результатом суммы. +/// public class NestedBindingConverter(NestedBindingsTree tree) : IMultiValueConverter { + /// Дерево вложенных привязок private NestedBindingsTree Tree { get; } = tree; + /// Преобразует значения привязок в значение целевого типа + /// Массив значений, полученных от привязок + /// Целевой тип преобразования + /// Параметр преобразователя + /// Культура для преобразования + /// Преобразованное значение public object? Convert(object[]? values, Type TargetType, object? parameter, CultureInfo culture) { var value = GetTreeValue(Tree, values, TargetType, culture); return value; } + /// Рекурсивно вычисляет значение для узла дерева вложенных привязок + /// Узел дерева вложенных привязок + /// Массив значений, полученных от привязок + /// Целевой тип преобразования + /// Культура для преобразования + /// Преобразованное значение для данного узла дерева private static object? GetTreeValue(NestedBindingsTree tree, object[]? values, Type TargetType, CultureInfo culture) { var objects = new object[tree.Nodes.Count]; @@ -28,5 +97,12 @@ public class NestedBindingConverter(NestedBindingsTree tree) : IMultiValueConver return value; } + /// Обратное преобразование не поддерживается + /// Значение для обратного преобразования + /// Целевые типы + /// Параметр преобразователя + /// Культура для преобразования + /// Массив преобразованных значений + /// Обратное преобразование не поддерживается public object?[]? ConvertBack(object? value, Type[] TargetTypes, object? parameter, CultureInfo culture) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/MathCore.WPF/NestedBindingNode.cs b/MathCore.WPF/NestedBindingNode.cs index 9a2c58f..b9fac0b 100644 --- a/MathCore.WPF/NestedBindingNode.cs +++ b/MathCore.WPF/NestedBindingNode.cs @@ -1,8 +1,32 @@ namespace MathCore.WPF; +/// Узел дерева вложенных привязок, представляющий ссылку на значение в массиве привязок +/// +/// Этот класс используется внутри для построения дерева вложенных привязок. +/// Каждый узел хранит индекс значения в массиве, передаваемом в конвертер. +/// Класс создаётся автоматически и не предназначен для прямого использования. +/// +/// +/// Класс создаётся автоматически при обработке . +/// Например, при следующей структуре привязок: +/// +/// +/// +/// +/// +/// +/// +/// +/// ]]> +/// Будет создано дерево, где для каждой обычной привязки создаётся с соответствующим индексом. +/// public class NestedBindingNode(int index) { + /// Индекс значения в массиве значений привязок public int Index => index; + /// Возвращает строковое представление узла + /// Строковое представление индекса узла public override string ToString() => index.ToString(); } \ No newline at end of file diff --git a/MathCore.WPF/NestedBindingsTree.cs b/MathCore.WPF/NestedBindingsTree.cs index 785bd11..b1d276e 100644 --- a/MathCore.WPF/NestedBindingsTree.cs +++ b/MathCore.WPF/NestedBindingsTree.cs @@ -3,13 +3,49 @@ namespace MathCore.WPF; +/// Дерево вложенных привязок, представляющее иерархическую структуру конвертеров и привязок +/// +/// Этот класс используется внутри для построения дерева вложенных привязок. +/// Каждый узел дерева хранит конвертер и коллекцию дочерних узлов, которые могут быть как простыми +/// ссылками на значения (), так и вложенными деревьями (). +/// Класс создаётся автоматически и не предназначен для прямого использования. +/// +/// +/// Класс создаётся автоматически при обработке . +/// Например, при следующей XAML-разметке: +/// +/// +/// +/// +/// +/// +/// +/// +/// ]]> +/// Будет создано следующее дерево: +/// - Корневое дерево (NestedBindingsTree) с OuterConverter +/// - Узел (NestedBindingNode) с Index=0 для FirstName +/// - Узел (NestedBindingNode) с Index=1 для LastName +/// - Вложенное дерево (NestedBindingsTree) с InnerConverter +/// - Узел (NestedBindingNode) с Index=2 для Age +/// - Узел (NestedBindingNode) с Index=3 для Country +/// public class NestedBindingsTree() : NestedBindingNode(-1) { + /// Конвертер, используемый для преобразования значений дочерних узлов public IMultiValueConverter Converter { get; set; } + /// Параметр, передаваемый конвертеру public object ConverterParameter { get; set; } + /// Культура, используемая для преобразования значений public CultureInfo ConverterCulture { get; set; } + /// Коллекция дочерних узлов дерева + /// + /// Каждый элемент может быть либо (ссылка на значение привязки), + /// либо (вложенное поддерево с собственным конвертером). + /// public List Nodes { get; } = []; } \ No newline at end of file diff --git a/MathCore.WPF/PasswordBoxHelper.cs b/MathCore.WPF/PasswordBoxHelper.cs index 8972a58..b51f138 100644 --- a/MathCore.WPF/PasswordBoxHelper.cs +++ b/MathCore.WPF/PasswordBoxHelper.cs @@ -5,19 +5,18 @@ namespace MathCore.WPF; -/// -/// Creates a bindable attached property for the property. -/// +/// Создаёт привязываемое присоединённое свойство для public static class PasswordBoxHelper { - // an attached behavior won't work due to view model validation not picking up the right control to adorn + /// Присоединённое свойство для привязки защищённого пароля + // присоединённое поведение не подходит из-за неверной привязки к элементу для валидации // кратко по делу public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty .RegisterAttached( "ShadowSecurePassword", typeof(SecureString), typeof(PasswordBoxHelper), new FrameworkPropertyMetadata( - new SecureString(), + new SecureString(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)); @@ -28,15 +27,21 @@ public static class PasswordBoxHelper typeof(PasswordBoxHelper), new()); + /// Устанавливает значение защищённого пароля + /// Элемент для установки значения + /// Значение защищённого пароля public static void SetSecurePassword(PasswordBox element, SecureString SecureString) => element.SetValue(SecurePasswordBindingProperty, SecureString); + /// Возвращает значение защищённого пароля + /// Элемент для чтения значения + /// Значение защищённого пароля public static SecureString GetSecurePassword(PasswordBox element) => (SecureString)element.GetValue(SecurePasswordBindingProperty)!; private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - // we'll need to hook up to one of the element's events - // in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property - // don't be tempted to use the Unloaded event as that will be fired even when the control is still alive and well (e.g. switching tabs in a tab control) + // требуется подписка на событие элемента для обновления значения // кратко по делу + // обработчик обёрнут в объект присоединённого свойства ради корректной сборки мусора // кратко по делу + // событие Unloaded использовать нельзя, оно срабатывает при переключении вкладок // кратко по делу var password_box = (PasswordBox)d; if (password_box.GetValue(__PasswordBindingMarshallerProperty) is not PasswordBindingMarshaller binding_marshaller) { @@ -47,7 +52,7 @@ private static void AttachedPropertyValueChanged(DependencyObject d, DependencyP binding_marshaller.UpdatePasswordBox(e.NewValue as SecureString); } - /// Encapsulated event logic + /// Инкапсулированная логика обработки событий private class PasswordBindingMarshaller { private readonly PasswordBox _PasswordBox; @@ -67,10 +72,10 @@ public void UpdatePasswordBox(SecureString NewPassword) _IsMarshalling = true; try { - // setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property + // установка SecurePassword не обновляет визуальное значение, требуется Password // кратко по делу _PasswordBox.Password = SecureStringToString(NewPassword); - // you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying) + // можно использовать копирование, но выигрыш по безопасности минимален // кратко по делу //newPassword.CopyInto(_passwordBox.SecurePassword); } finally @@ -95,7 +100,7 @@ private static string SecureStringToString(SecureString value) private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e) { - // copy the password into the attached property + // копирование пароля в присоединённое свойство // кратко по делу if (_IsMarshalling) return; diff --git a/MathCore.WPF/RadialProgressIndicator.cs b/MathCore.WPF/RadialProgressIndicator.cs index 97382a9..8d66f90 100644 --- a/MathCore.WPF/RadialProgressIndicator.cs +++ b/MathCore.WPF/RadialProgressIndicator.cs @@ -7,6 +7,7 @@ namespace MathCore.WPF; +/// Индикатор радиального прогресса public class RadialProgressIndicator : FrameworkElement { #region Fields @@ -36,7 +37,7 @@ public class RadialProgressIndicator : FrameworkElement #region Constructors - /// Static meta data registrations + /// Регистрация метаданных зависимых свойств static RadialProgressIndicator() => IsEnabledProperty.OverrideMetadata( typeof(RadialProgressIndicator), @@ -44,7 +45,7 @@ static RadialProgressIndicator() => false, (o, e) => (o as RadialProgressIndicator)?.OnIsEnabledChanged((bool)e.OldValue, (bool)e.NewValue))); - /// Initializes a new instance of + /// Инициализирует новый экземпляр public RadialProgressIndicator() { _IsListening = false; @@ -58,7 +59,7 @@ public RadialProgressIndicator() #region Foreground - /// Dependency property for Foreground + /// Зависимое свойство для Foreground public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner( typeof(RadialProgressIndicator), @@ -67,7 +68,7 @@ public RadialProgressIndicator() FrameworkPropertyMetadataOptions.Inherits, (o, e) => (o as RadialProgressIndicator)?.OnForegroundChanged((Brush)e.NewValue))); - /// Foreground property + /// Свойство Foreground public Brush Foreground { get => (Brush)GetValue(ForegroundProperty); @@ -78,7 +79,7 @@ public Brush Foreground #region ActiveForeground - /// Dependency property for ActiveForeground + /// Зависимое свойство для ActiveForeground public static readonly DependencyProperty ActiveForegroundProperty = DependencyProperty.Register( nameof(ActiveForeground), @@ -89,7 +90,7 @@ public Brush Foreground FrameworkPropertyMetadataOptions.AffectsRender, (o, e) => (o as RadialProgressIndicator)?.OnActiveForegroundChanged((Brush)e.NewValue))); - /// ActiveForeground property. + /// Свойство ActiveForeground public Brush ActiveForeground { get => (Brush)GetValue(ForegroundProperty); @@ -100,7 +101,7 @@ public Brush ActiveForeground #region CurrentValue - /// Dependency property for CurrentValue. + /// Зависимое свойство для CurrentValue public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register( nameof(CurrentValue), @@ -112,7 +113,7 @@ public Brush ActiveForeground (o, _) => (o as RadialProgressIndicator)?.OnCurrentValueChanged(), (_, e) => DoubleUtil.LessThan((double)e, 0) ? 0 : (DoubleUtil.GreaterThan((double)e, 100) ? 100 : (double)e))); - /// Current value property. + /// Свойство CurrentValue public double CurrentValue { get => (double)GetValue(CurrentValueProperty); @@ -123,16 +124,8 @@ public double CurrentValue #region Overrides - /// - /// When overridden in a derived class, participates in rendering operations - /// that are directed by the layout system. The rendering instructions for this - /// element are not used directly when this method is invoked, and are instead - /// preserved for later asynchronous use by layout and drawing. - /// - /// - /// The drawing instructions for a specific element. This context is provided - /// to the layout system. - /// + /// Участвует в операциях отрисовки, выполняемых системой компоновки, сохраняя инструкции для последующего асинхронного использования + /// Контекст рисования для элемента, предоставленный системой компоновки protected override void OnRender(DrawingContext DrawingContext) { base.OnRender(DrawingContext); @@ -155,13 +148,9 @@ protected override void OnRender(DrawingContext DrawingContext) } } - // Details of the old and new size involved in the change. - /// - /// Raises the System.Windows.FrameworkElement.SizeChanged event, using the specified - /// information as part of the eventual event data. - /// - /// - /// + // Детали изменения старого и нового размера // кратко по делу + /// Вызывает событие System.Windows.FrameworkElement.SizeChanged, используя указанные данные + /// Данные об изменении размера protected override void OnRenderSizeChanged(SizeChangedInfo SizeInfo) { base.OnRenderSizeChanged(SizeInfo); @@ -223,12 +212,10 @@ private void CreateGeomerty() if (DoubleUtil.IsZero(_Radius)) return; - // Current value geometry - _CurrentGeometry = _Center.CreatePath(CurrentValue.Angle(), _Radius - 14, _Radius - 20); + _CurrentGeometry = _Center.CreatePath(CurrentValue.Angle(), _Radius - 14, _Radius - 20); // геометрия текущего значения // кратко по делу _CurrentGeometry.Freeze(); - // Border geometry - _BorderGeometry = _Center.Create(4, 2, _Radius - 4, _Radius - 12); + _BorderGeometry = _Center.Create(4, 2, _Radius - 4, _Radius - 12); // геометрия границы // кратко по делу _BorderGeometry.Freeze(); } @@ -307,6 +294,7 @@ private void OnActiveForegroundChanged(Brush NewValue) #endregion Property Changes } +/// Методы расширения для построения геометрии file static class GeometryExtensions { #region Static @@ -317,11 +305,9 @@ file static class GeometryExtensions #region Methods - /// Easing in angle by delta proportionally 5 percent towards 360. - /// - /// The angle to start. - /// - /// Increased angle eased in by delta proportionally 5 percent towards 360. + /// Плавно увеличивает угол, приближая его к 360 на 5 процентов + /// Начальный угол + /// Увеличенный угол public static double EaseAngle(this double angle) { var sign = Sign(angle); @@ -335,27 +321,15 @@ public static double EaseAngle(this double angle) return sign < 0 ? sign * normalized_angle : normalized_angle; } - /// - /// Increases the angle by the delta and ensure the final result is in - /// -360 to 360 degrees. - /// - /// - /// The angle in degrees to increase. - /// - /// - /// The delta angle in degree to increase by. - /// - /// - /// The angle increased by delta and ensure the final result is in - /// -360 to 360 degrees. - /// + /// Увеличивает угол на дельту и нормализует результат в диапазоне от -360 до 360 + /// Угол в градусах для увеличения + /// Дельта угла в градусах + /// Нормализованный угол после увеличения public static double Angle(this double angle, double delta) => (angle.Normalize() + delta).Normalize(); - /// Converts the percent from 0 to 100 into proportional angle from 0 to 360. - /// - /// The percent to convert. - /// - /// The converted angle from 0 to 360 proportional to 0 to 100 percent. + /// Преобразует процент от 0 до 100 в пропорциональный угол от 0 до 360 + /// Процент для преобразования + /// Угол от 0 до 360 пропорционально значению процента public static double Angle(this double percent) { if (DoubleUtil.LessThan(percent, 0) || DoubleUtil.GreaterThan(percent, 100)) @@ -364,20 +338,12 @@ public static double Angle(this double percent) return __FullCircleInDegrees / 100 * percent; } - /// Creates a circle path for the specified location, angle in degrees, circle radius and inner radius. - /// - /// The start location. - /// - /// - /// The angle in degrees. - /// - /// - /// The radius. - /// - /// - /// Inner radius. - /// - /// The circle path for the specified location, angle in degrees, circle radius and inner radius. + /// Создаёт дугу окружности для заданной точки, угла, радиуса и внутреннего радиуса + /// Начальная точка + /// Угол в градусах + /// Радиус + /// Внутренний радиус + /// Геометрия дуги окружности public static PathGeometry CreatePath(this Point location, double angle, double radius, double InnerRadius) { if (DoubleUtil.LessThan(radius, 0)) @@ -413,13 +379,13 @@ public static PathGeometry CreatePath(this Point location, double angle, double return new() { Figures = [new(location, segments, true)] }; } - /// Creates a circle path spilits into the given number of sigments. - /// The start location. - /// Number of sigments. - /// Sigment distance between each other in degrees. - /// The radius. - /// The inner radius. - /// The combined path geomerty of the circle spilits into the number of segments. + /// Создаёт путь окружности, разделённой на заданное число сегментов + /// Начальная точка + /// Количество сегментов + /// Зазор между сегментами в градусах + /// Радиус + /// Внутренний радиус + /// Суммарная геометрия окружности, разделённой на сегменты public static PathGeometry Create( this Point point, int segments, @@ -455,14 +421,10 @@ public static PathGeometry Create( return path_geometry; } - /// Gets the vector point for the specified angle in degrees and radius. - /// - /// The angle in degrees. - /// - /// - /// The radius. - /// - /// The vector point for the specified angle in degrees and radius. + /// Возвращает точку вектора для заданного угла и радиуса + /// Угол в градусах + /// Радиус + /// Точка вектора public static Point ConvertRadianToCartesian(this double angle, double radius) { if (DoubleUtil.LessThan(radius, 0)) @@ -474,11 +436,9 @@ public static Point ConvertRadianToCartesian(this double angle, double radius) return new(x, y); } - /// Normalizes the specified angle in degrees to angles between 0 to 360; - /// - /// The angle to normalize. - /// - /// Normalized angle in degrees from 0 to 360 for the specified + /// Нормализует угол в градусах к диапазону от 0 до 360 + /// Угол для нормализации + /// Нормализованный угол от 0 до 360 public static double Normalize(this double angle) { var remainder = angle % __FullCircleInDegrees; @@ -492,57 +452,30 @@ public static double Normalize(this double angle) return remainder; } - /// Impelement the EaseIn style of exponential animation which is one of exponential growth. - /// - /// Time we've been running from 0 to 1. - /// - /// - /// Start value. - /// - /// - /// Delta between start value and the end value we want. - /// - /// - /// The rate of exponental growth. - /// - /// The result value. + /// Реализует EaseIn для экспоненциальной анимации роста + /// Доля времени от 0 до 1 + /// Начальное значение + /// Дельта между начальным и конечным значениями + /// Показатель экспоненциального роста + /// Результирующее значение public static double EaseIn(this double TimeFraction, double start, double delta, double power) => Pow(TimeFraction, power) * delta + start; - /// Impelement the EaseOut style of exponential animation which is one of exponential decay. - /// - /// Time we've been running from 0 to 1. - /// - /// - /// Start value. - /// - /// - /// Delta between start value and the end value we want. - /// - /// - /// The rate of exponental decay. - /// - /// The result value. + /// Реализует EaseOut для экспоненциальной анимации затухания + /// Доля времени от 0 до 1 + /// Начальное значение + /// Дельта между начальным и конечным значениями + /// Показатель экспоненциального затухания + /// Результирующее значение public static double EaseOut(this double TimeFraction, double start, double delta, double power) => Pow(TimeFraction, 1 / power) * delta + start; - /// - /// Impelement the EaseInOut style of exponential animation which is one of exponential growth - /// for the first half of the animation and one of exponential decay for the second half. - /// - /// - /// Time we've been running from 0 to 1. - /// - /// - /// Start value. - /// - /// - /// Delta between start value and the end value we want. - /// - /// - /// The rate of exponental growth/decay. - /// - /// The result value. + /// Реализует EaseInOut для экспоненциальной анимации роста и затухания + /// Доля времени от 0 до 1 + /// Начальное значение + /// Дельта между начальным и конечным значениями + /// Показатель экспоненциального роста и затухания + /// Результирующее значение public static double EaseInOut(this double TimeFraction, double start, double delta, double power) => TimeFraction <= 0.5 ? EaseOut(TimeFraction * 2, start, delta / 2, power) @@ -551,6 +484,7 @@ public static double EaseInOut(this double TimeFraction, double start, double de #endregion } +/// Вспомогательные методы сравнения значений double file static class DoubleUtil { #region Types @@ -569,179 +503,101 @@ private struct NanUnion #region Static - // Const values come from sdk\inc\crt\float.h + // Константы взяты из sdk\inc\crt\float.h // кратко по делу private const double __DoubleEpsilon = 2.2204460492503131e-016; /* smallest such that 1.0+DoubleEpsilon != 1.0 */ #endregion #region Methods - /// - /// AreClose - Returns whether or not two doubles are "close". That is, whether or - /// not they are within epsilon of each other. Note that this epsilon is proportional - /// to the numbers themselves to that AreClose survives scalar multiplication. - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// bool - the result of the AreClose comparision. - /// The first double to compare. - /// The second double to compare. + /// Возвращает признак того, что два значения double близки друг к другу + /// Результат сравнения + /// Первое значение для сравнения + /// Второе значение для сравнения public static bool AreClose(double value1, double value2) { - //in case they are Infinities (then epsilon check does not work) + // в случае бесконечностей проверка эпсилон не работает // кратко по делу // ReSharper disable CompareOfFloatsByEqualityOperator if (value1 == value2) return true; // ReSharper restore CompareOfFloatsByEqualityOperator - // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DoubleEpsilon + // вычисляет (|value1-value2| / (|value1| + |value2| + 10.0)) < DoubleEpsilon // кратко по делу var eps = (Abs(value1) + Abs(value2) + 10.0) * __DoubleEpsilon; var delta = value1 - value2; return (-eps < delta) && (eps > delta); } - /// - /// Compares two Size instances for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first size to compare - /// The second size to compare - /// Whether or not the two Size instances are equal + /// Сравнивает два значения Size с учётом погрешности + /// Первый размер для сравнения + /// Второй размер для сравнения + /// Признак равенства размеров public static bool AreClose(Size size1, Size size2) => AreClose(size1.Width, size2.Width) && AreClose(size1.Height, size2.Height); - // The Point, Size, Rect and Matrix class have moved to WinCorLib. However, we provide - // internal AreClose methods for our own use here. - - /// - /// Compares two points for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first point to compare - /// The second point to compare - /// Whether or not the two points are equal + // Классы Point, Size, Rect и Matrix перемещены в WinCorLib // кратко по делу + + /// Сравнивает две точки с учётом погрешности + /// Первая точка для сравнения + /// Вторая точка для сравнения + /// Признак равенства точек public static bool AreClose(Point point1, Point point2) => AreClose(point1.X, point2.X) && AreClose(point1.Y, point2.Y); - /// - /// Compares two Vector instances for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first Vector to compare - /// The second Vector to compare - /// Whether or not the two Vector instances are equal + /// Сравнивает два значения Vector с учётом погрешности + /// Первый вектор для сравнения + /// Второй вектор для сравнения + /// Признак равенства векторов public static bool AreClose(Vector vector1, Vector vector2) => AreClose(vector1.X, vector2.X) && AreClose(vector1.Y, vector2.Y); - /// - /// LessThan - Returns whether or not the first double is less than the second double. - /// That is, whether or not the first is strictly less than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// bool - the result of the LessThan comparision. - /// The first double to compare. - /// The second double to compare. + /// Возвращает признак того, что первое значение меньше второго и не близко к нему + /// Результат сравнения + /// Первое значение для сравнения + /// Второе значение для сравнения public static bool LessThan(double value1, double value2) => (value1 < value2) && !AreClose(value1, value2); - /// - /// GreaterThan - Returns whether or not the first double is greater than the second double. - /// That is, whether or not the first is strictly greater than *and* not within epsilon of - /// the other number. Note that this epsilon is proportional to the numbers themselves - /// to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// bool - the result of the GreaterThan comparision. - /// The first double to compare. - /// The second double to compare. + /// Возвращает признак того, что первое значение больше второго и не близко к нему + /// Результат сравнения + /// Первое значение для сравнения + /// Второе значение для сравнения public static bool GreaterThan(double value1, double value2) => (value1 > value2) && !AreClose(value1, value2); - /// - /// LessThanOrClose - Returns whether or not the first double is less than or close to - /// the second double. That is, whether or not the first is strictly less than or within - /// epsilon of the other number. Note that this epsilon is proportional to the numbers - /// themselves to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// bool - the result of the LessThanOrClose comparision. - /// The first double to compare. - /// The second double to compare. + /// Возвращает признак того, что первое значение меньше второго или близко к нему + /// Результат сравнения + /// Первое значение для сравнения + /// Второе значение для сравнения public static bool LessThanOrClose(double value1, double value2) => (value1 < value2) || AreClose(value1, value2); - /// - /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to - /// the second double. That is, whether or not the first is strictly greater than or within - /// epsilon of the other number. Note that this epsilon is proportional to the numbers - /// themselves to that AreClose survives scalar multiplication. Note, - /// There are plenty of ways for this to return false even for numbers which - /// are theoretically identical, so no code calling this should fail to work if this - /// returns false. This is important enough to repeat: - /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be - /// used for optimizations *only*. - /// - /// bool - the result of the GreaterThanOrClose comparision. - /// The first double to compare. - /// The second double to compare. + /// Возвращает признак того, что первое значение больше второго или близко к нему + /// Результат сравнения + /// Первое значение для сравнения + /// Второе значение для сравнения public static bool GreaterThanOrClose(double value1, double value2) => (value1 > value2) || AreClose(value1, value2); - /// - /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), - /// but this is faster. - /// - /// bool - the result of the AreClose comparision. - /// The double to compare to 1. + /// Возвращает признак того, что значение близко к 1 + /// Результат сравнения + /// Значение для сравнения с 1 public static bool IsOne(double value) => Abs(value - 1.0) < 10.0 * __DoubleEpsilon; - /// - /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), - /// but this is faster. - /// - /// bool - the result of the AreClose comparision. - /// The double to compare to 0. + /// Возвращает признак того, что значение близко к 0 + /// Результат сравнения + /// Значение для сравнения с 0 public static bool IsZero(double value) => Abs(value) < 10.0 * __DoubleEpsilon; - /// Test to see if a double is a finite number (is not NaN or Infinity). - /// - /// The value to test. - /// - /// Whether or not the value is a finite number. + /// Проверяет, что значение является конечным числом + /// Значение для проверки + /// Признак конечного числа public static bool IsFinite(double value) => !double.IsNaN(value) && !double.IsInfinity(value); - /// Test to see if a double a valid size value (is finite and > 0). - /// - /// The value to test. - /// - /// Whether or not the value is a valid size value. + /// Проверяет, что значение допустимо для размера + /// Значение для проверки + /// Признак допустимого значения размера public static bool IsValidSize(double value) => IsFinite(value) && GreaterThanOrClose(value, 0); - /// - /// Checks whether the double value is not a valid number or not. The standard CLR double.IsNaN() - /// function is approximately 100 times slower than this, so please make sure to use DoubleUtil.IsNaN() - /// in performance sensitive code. - /// - /// - /// The double value to check for. - /// - /// True if is not a number. Otherwise true. + /// Проверяет, что значение является нечисловым + /// Значение для проверки + /// True, если значение не является числом public static bool IsNaN(double value) { var t = new NanUnion { DoubleValue = value }; From dcb21d01115d884f3ba4a7641758a7ed8705b5f9 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 20:25:08 +0300 Subject: [PATCH 20/33] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=B8?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2=20=D0=B8=20TextBox,=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20NotifyPropertyChangedEx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены расширения для OpenFileDialog и FileDialogEx с поддержкой заголовков. В TextBoxEx исправлен расчет значения и добавлена attached property EnterCommit для обновления источника по Enter. Добавлен новый файл NotifyPropertyChangedEx с методами подписки на изменения свойств INotifyPropertyChanged. --- .../Extensions/NotifyPropertyChangedEx.cs | 37 ++++++++++++++++ .../Extensions/OpenFileDialogExtensions.cs | 29 +++++++++---- MathCore.WPF/FileDialogEx.cs | 6 +-- MathCore.WPF/TextBoxEx.cs | 42 ++++++++++++++++++- 4 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 MathCore.WPF/Extensions/NotifyPropertyChangedEx.cs diff --git a/MathCore.WPF/Extensions/NotifyPropertyChangedEx.cs b/MathCore.WPF/Extensions/NotifyPropertyChangedEx.cs new file mode 100644 index 0000000..2e9a192 --- /dev/null +++ b/MathCore.WPF/Extensions/NotifyPropertyChangedEx.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; + +namespace MathCore.WPF.Extensions; + +public static class NotifyPropertyChangedEx +{ + public static T OnPropertyChanged(this T obj, string PropertyName, Action action) + { + if (obj is INotifyPropertyChanged propertyChanged) + propertyChanged.PropertyChanged += (sender, args) => + { + if (args.PropertyName == PropertyName) + action((T)sender!); + }; + return obj; + } + + public static T OnPropertyChanged(this T obj, string PropertyName, Func condition, Action action) + { + if (obj is INotifyPropertyChanged propertyChanged) + propertyChanged.PropertyChanged += (sender, args) => + { + var value = (T)sender!; + if (args.PropertyName == PropertyName && condition(value)) + action(value); + }; + return obj; + } + + public static T OnPropertyChanged(this T obj, string PropertyName, PropertyChangedEventHandler PropertyChangedHandler) + where T : class, INotifyPropertyChanged + { + obj.SubscribeTo(PropertyName, PropertyChangedHandler); + + return obj; + } +} \ No newline at end of file diff --git a/MathCore.WPF/Extensions/OpenFileDialogExtensions.cs b/MathCore.WPF/Extensions/OpenFileDialogExtensions.cs index d75f1d1..f141bb3 100644 --- a/MathCore.WPF/Extensions/OpenFileDialogExtensions.cs +++ b/MathCore.WPF/Extensions/OpenFileDialogExtensions.cs @@ -2,22 +2,33 @@ // ReSharper disable MemberCanBePrivate.Global // ReSharper disable once CheckNamespace +using MathCore.WPF; + namespace Microsoft.Win32; public static class OpenFileDialogExtensions { - public static string? GetFileName(this OpenFileDialog d) => d.ShowDialog() == true ? d.FileName : null; - public static string? GetFileName(this OpenFileDialog d, System.Windows.Window owner) => d.ShowDialog(owner) == true ? d.FileName : null; - - public static System.IO.FileInfo? GetFileInfo(this OpenFileDialog dialog) + extension(OpenFileDialog) { - var file = dialog.GetFileName(); - return string.IsNullOrWhiteSpace(file) ? null : new(file); + public static FileDialogEx Open(string Title) => FileDialogEx.OpenFile(Title); } - public static System.IO.FileInfo? GetFileInfo(this OpenFileDialog dialog, System.Windows.Window owner) + extension(OpenFileDialog dialog) { - var file = dialog.GetFileName(owner); - return string.IsNullOrWhiteSpace(file) ? null : new(file); + public string? GetFileName() => dialog.ShowDialog() == true ? dialog.FileName : null; + + public string? GetFileName(System.Windows.Window owner) => dialog.ShowDialog(owner) == true ? dialog.FileName : null; + + public System.IO.FileInfo? GetFileInfo() + { + var file = dialog.GetFileName(); + return string.IsNullOrWhiteSpace(file) ? null : new(file); + } + + public System.IO.FileInfo? GetFileInfo(System.Windows.Window owner) + { + var file = dialog.GetFileName(owner); + return string.IsNullOrWhiteSpace(file) ? null : new(file); + } } } \ No newline at end of file diff --git a/MathCore.WPF/FileDialogEx.cs b/MathCore.WPF/FileDialogEx.cs index 5873560..8f2606a 100644 --- a/MathCore.WPF/FileDialogEx.cs +++ b/MathCore.WPF/FileDialogEx.cs @@ -83,7 +83,7 @@ public override int GetHashCode() /// Создает построитель диалога открытия файла /// Новый экземпляр FileDialogEx для открытия файла public static FileDialogEx OpenFile() => new() { IsSaveFileDialog = false }; - + /// Создает построитель диалога открытия файла с указанным заголовком /// Заголовок диалогового окна /// Новый экземпляр FileDialogEx для открытия файла @@ -92,7 +92,7 @@ public override int GetHashCode() /// Создает построитель диалога сохранения файла /// Новый экземпляр FileDialogEx для сохранения файла public static FileDialogEx CreateFile() => new() { IsSaveFileDialog = true }; - + /// Создает построитель диалога сохранения файла с указанным заголовком /// Заголовок диалогового окна /// Новый экземпляр FileDialogEx для сохранения файла @@ -101,7 +101,7 @@ public override int GetHashCode() /// Создает новый построитель диалога файла /// Новый экземпляр FileDialogEx public static FileDialogEx New() => new(); - + /// Создает новый построитель диалога файла с указанным заголовком /// Заголовок диалогового окна /// Новый экземпляр FileDialogEx diff --git a/MathCore.WPF/TextBoxEx.cs b/MathCore.WPF/TextBoxEx.cs index 4ea12a9..c51bde4 100644 --- a/MathCore.WPF/TextBoxEx.cs +++ b/MathCore.WPF/TextBoxEx.cs @@ -202,7 +202,7 @@ private static void OnMouseWheel(object Sender, MouseWheelEventArgs E) if (Keyboard.IsKeyDown(Key.LeftCtrl) && GetMouseWheelIncrementCtrlRatio(text_block) is not decimal.Zero and not 1m and var ratio) delta = Keyboard.IsKeyDown(Key.LeftShift) ? delta / ratio : delta * ratio; - var new_value = (value + delta); + var new_value = value + delta; var new_value_text = new_value.ToString(CultureInfo.InvariantCulture); text_block.Text = new_value_text; } @@ -260,4 +260,44 @@ private static void OnTextBoxGotKeyboardFocus(object sender, RoutedEventArgs e) public static bool GetAutoSelectAll(DependencyObject d) => (bool)d.GetValue(AutoSelectAllProperty); #endregion + + #region Attached property EnterCommit : bool - Обновлять источник по Enter + + /// Обновлять источник привязки при нажатии Enter + public static readonly DependencyProperty EnterCommitProperty = + DependencyProperty.RegisterAttached( + "EnterCommit", + typeof(bool), + typeof(TextBoxEx), + new(false, OnEnterCommitChanged)); + + /// Обработчик изменения свойства EnterCommit + private static void OnEnterCommitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not UIElement element) return; + + if ((bool)e.NewValue) + element.KeyDown += OnEnterCommitKeyDown; + else + element.KeyDown -= OnEnterCommitKeyDown; + } + + /// Обработчик события нажатия клавиши для EnterCommit + private static void OnEnterCommitKeyDown(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) return; + if (e.Source is not TextBox text_box) return; + + var binding = text_box.GetBindingExpression(TextBox.TextProperty); + binding?.UpdateSource(); + } + + /// Установить значение свойства EnterCommit + [AttachedPropertyBrowsableForType(typeof(UIElement))] + public static void SetEnterCommit(DependencyObject d, bool value) => d.SetValue(EnterCommitProperty, value); + + /// Получить значение свойства EnterCommit + public static bool GetEnterCommit(DependencyObject d) => (bool)d.GetValue(EnterCommitProperty); + + #endregion } From e6d5c91e9ba91ff80259cb12f21dccbb9e7b1750 Mon Sep 17 00:00:00 2001 From: Infarh Date: Fri, 13 Feb 2026 20:26:12 +0300 Subject: [PATCH 21/33] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20NuGet-=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=B0=D1=85=20WPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обновлены версии System.Drawing.Common (10.0.3), MSTest.TestAdapter и MSTest.TestFramework (4.1.0), а также Microsoft.Extensions.Hosting (10.0.3) в соответствующих проектах решения. --- MathCore.WPF/MathCore.WPF.csproj | 2 +- Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj | 4 ++-- Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 5e463aa..d162d1a 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -63,7 +63,7 @@ - + diff --git a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj index d51b56f..4751f19 100644 --- a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj +++ b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj @@ -11,8 +11,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj index 439c910..bab2833 100644 --- a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj +++ b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj @@ -27,7 +27,7 @@ - + From 33ea4cc1781f90012a7500232421b60fae8ed9d6 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 00:23:47 +0300 Subject: [PATCH 22/33] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20MathCore=20=D0=B4=D0=BE=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=81=D0=B8=D0=B8=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обновлён пакет MathCore в проекте MathCore.WPF.csproj с версии 0.0.94.3 до 0.1.0 для использования последних изменений и улучшений библиотеки. --- MathCore.WPF/MathCore.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index d162d1a..638269d 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -40,7 +40,7 @@ - + From 53e778e2d5ca1c9d2bfedff74f86bedde581e752 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 00:27:06 +0300 Subject: [PATCH 23/33] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0?= =?UTF-8?q?=20net4.7-windows=20=D0=B8=D0=B7=20=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Из файла MathCore.WPF.csproj убрана целевая платформа net4.7-windows, сборка для этой версии .NET Framework больше не поддерживается. --- MathCore.WPF/MathCore.WPF.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 638269d..3d31890 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -5,7 +5,6 @@ net10.0-windows; net8.0-windows; net4.8-windows; - net4.7-windows; net4.6.1-windows; true From ebe95a00930c0d57f8ed783f2a82165c6e3b8b4c Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 02:18:49 +0300 Subject: [PATCH 24/33] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B8,=20=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 2 + .github/prompts/documentPublicApi.prompt.md | 41 ++++++++++ MathCore.WPF/Taskbar/Taskbar.cs | 84 ++++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .github/prompts/documentPublicApi.prompt.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2b1a433..b0997a3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,6 +13,8 @@ - Каждый тег XML‑комментария располагай на отдельной строке - Порядок тегов: `` → `` → `` → `` → `` → `` - Для сложных публичных метдов генерируй блок с простым примером использования кода внутри тега `` +- Примеры использования кода внутри тега `` должны быть лаконичными и демонстрировать только ключевые моменты использования метода, без избыточных деталей +- Примеры кода нужно заворачивать в тег `` внутри тега ``, чтобы обеспечить правильное форматирование и подсветку синтаксиса, а также размещать в элементе `` что бы не использовать экранирование xml-символов Примеры: - `Краткое описание сущности` diff --git a/.github/prompts/documentPublicApi.prompt.md b/.github/prompts/documentPublicApi.prompt.md new file mode 100644 index 0000000..96c507b --- /dev/null +++ b/.github/prompts/documentPublicApi.prompt.md @@ -0,0 +1,41 @@ +--- +name: documentPublicApi +--- +# Документирование публичного API + +Проверь и дополни файл полной XML-документацией для типа (класса, интерфейса, структуры) и всех его публичных членов. + +## Документирование класса/типа + +Добавь комментарий к самому классу/типу с: +- `` — краткое описание назначения и функциональности +- `` — дополнительная информация о возможностях и ограничениях (опционально) +- `` — практический пример, демонстрирующий основные сценарии использования класса + +## Документирование публичных членов + +Убедись, что все публичные члены задокументированы — методы, свойства, события, события должны иметь полный набор тегов: +- `` — краткое описание (одна строка, без точки в конце) +- `` — для параметров методов (если они есть) +- `` — для возвращаемого значения +- `` — для свойств, описывающее значение +- `` — для исключений, которые может выбросить метод +- `` — дополнительная информация (опционально) +- `` — примеры использования для сложных публичных методов + +## Требования к комментариям + +1. **Язык:** все комментарии на русском языке — переведи существующие английские комментарии +2. **Примеры использования:** + - Добавь блок `` с практическим примером кода + - Обёрнь код в `` + - Для класса: покажи типичные сценарии использования его основной функциональности + - Для методов: продемонстрируй типичный вызов и результат +3. **Структура:** + - Порядок тегов: `` → `` → `` → `` → `` → `` → `` + - Каждый тег на отдельной строке + - Одинарное предложение в одной строке без точки в конце внутри тега + +## Минимизация изменений + +Изменяй только документацию — не переписывай существующий код, не добавляй новые члены класса, не рефакторь реализацию. diff --git a/MathCore.WPF/Taskbar/Taskbar.cs b/MathCore.WPF/Taskbar/Taskbar.cs index 3f8a77c..aa79a37 100644 --- a/MathCore.WPF/Taskbar/Taskbar.cs +++ b/MathCore.WPF/Taskbar/Taskbar.cs @@ -7,6 +7,28 @@ namespace MathCore.WPF.Taskbar; +/// Предоставляет функциональность для работы с панелью задач Windows +/// +/// Класс позволяет получить информацию о состоянии, размерах и положении панели задач, +/// а также управлять её видимостью. Все члены класса статические. +/// +/// +/// +/// [Copyright("franzalex", url = "https://gist.github.com/franzalex/e747e6b318ab8f328aa02301f25ec534")] public static class Taskbar { @@ -22,13 +44,21 @@ private enum ABS /// Статический инициализатор класса static Taskbar() => _AppBarData = new() { - cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)), + cbSize = (uint)Marshal.SizeOf(), hWnd = User32.FindWindow(ClassName, null) }; /// Возвращает значение, указывающее, что панель задач всегда поверх других окон /// true, если панель задач всегда поверх других окон; иначе false /// Это свойство всегда возвращает false в Windows 7 и новее + /// + /// + /// public static bool AlwaysOnTop { get @@ -40,6 +70,14 @@ public static bool AlwaysOnTop /// Возвращает значение, указывающее, что панель задач автоматически скрывается при неактивности /// true, если включено автоскрытие панели задач; иначе false + /// + /// + /// public static bool AutoHide { get @@ -50,6 +88,13 @@ public static bool AutoHide } /// Возвращает текущие границы отображения панели задач + /// Прямоугольник, представляющий текущие границы панели задач, или при ошибке + /// + /// + /// public static Rectangle CurrentBounds { get @@ -62,6 +107,13 @@ public static Rectangle CurrentBounds } /// Возвращает границы отображения при полностью видимой панели задач + /// Прямоугольник, представляющий границы видимой панели задач + /// + /// + /// public static Rectangle DisplayBounds => RefreshBoundsAndPosition() ? Rectangle.FromLTRB( @@ -72,20 +124,50 @@ public static Rectangle CurrentBounds : CurrentBounds; /// Возвращает дескриптор окна панели задач + /// Дескриптор окна (IntPtr) панели задач + /// + /// + /// public static IntPtr Handle => _AppBarData.hWnd; /// Возвращает положение панели задач на экране + /// Перечисление , указывающее положение панели + /// + /// + /// public static TaskbarPosition Position => RefreshBoundsAndPosition() ? (TaskbarPosition)_AppBarData.uEdge : TaskbarPosition.Unknown; private const int SW_HIDE = 0; + /// Скрывает панель задач + /// + /// + /// public static void Hide() => _ = User32.ShowWindow(Handle, SW_HIDE); private const int SW_SHOW = 1; /// Показывает панель задач + /// + /// + /// public static void Show() => _ = User32.ShowWindow(Handle, SW_SHOW); private static bool RefreshBoundsAndPosition() => From eb3a8d9309c223f2528c78790666da9f1c1f8c71 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 02:25:05 +0300 Subject: [PATCH 25/33] refactor(svg): comprehensive SVG rendering engine revision with Russian documentation and README --- MathCore.WPF/Svg2Xaml/README.md | 366 ++++++++++++++++++ MathCore.WPF/Svg2Xaml/SvgBaseElement.cs | 26 +- MathCore.WPF/Svg2Xaml/SvgDocument.cs | 30 +- .../Svg2Xaml/SvgDrawableBaseElement.cs | 97 ++++- MathCore.WPF/Svg2Xaml/SvgReader.cs | 85 ++-- MathCore.WPF/Svg2Xaml/SvgReaderOptions.cs | 47 ++- MathCore.WPF/Svg2Xaml/SvgSVGElement.cs | 6 +- 7 files changed, 608 insertions(+), 49 deletions(-) create mode 100644 MathCore.WPF/Svg2Xaml/README.md diff --git a/MathCore.WPF/Svg2Xaml/README.md b/MathCore.WPF/Svg2Xaml/README.md new file mode 100644 index 0000000..d58142f --- /dev/null +++ b/MathCore.WPF/Svg2Xaml/README.md @@ -0,0 +1,366 @@ +# SVG2Xaml - Механизм рендеринга SVG для WPF + +## Обзор + +`SVG2Xaml` — это мощный механизм для загрузки и преобразования SVG (Scalable Vector Graphics) документов в WPF `DrawingImage` объекты. Он позволяет отображать масштабируемую векторную графику в приложениях WPF с полной поддержкой трансформаций, эффектов и стилей. + +## Архитектура + +Механизм состоит из следующих основных компонентов: + +### Основные классы + +| Класс | Назначение | +|-------|-----------| +| **SvgReader** | Статический класс для загрузки SVG документов из потоков и XML читателей | +| **SvgDocument** | Внутренний класс, представляющий структуру загруженного SVG документа | +| **SvgReaderOptions** | Класс для настройки параметров парсинга и рендеринга | +| **SvgBaseElement** | Базовый класс для всех элементов SVG | +| **SvgDrawableBaseElement** | Базовый класс для отрисовываемых элементов с поддержкой стилей | +| **SvgSvgElement** | Представляет корневой элемент `` | + +### Иерархия элементов + +``` +SvgBaseElement +├── SvgDrawableBaseElement +│ ├── SvgSvgElement (корневой элемент) +│ ├── SvgRectElement (прямоугольник) +│ ├── SvgCircleElement (круг) +│ ├── SvgEllipseElement (эллипс) +│ ├── SvgLineElement (линия) +│ ├── SvgPathElement (путь) +│ ├── SvgPolygonElement (полигон) +│ └── [и другие элементы...] +└── [Вспомогательные элементы] + ├── SvgDefsElement (определения) + ├── SvgClipPathElement (обрезка) + ├── SvgFilterElement (фильтры) + └── [и другие...] +``` + +## Основные принципы использования + +### 1. Загрузка SVG из файла + +```csharp +using (var fileStream = File.OpenRead("image.svg")) +{ + var drawingImage = SvgReader.Load(fileStream); + // Использование drawingImage в WPF контролах +} +``` + +### 2. Загрузка SVG из потока с опциями + +```csharp +var options = new SvgReaderOptions +{ + IgnoreEffects = true // Отключить применение фильтр-эффектов +}; + +using (var stream = File.OpenRead("image.svg")) +{ + var drawingImage = SvgReader.Load(stream, options); +} +``` + +### 3. Загрузка SVG через XmlReader + +```csharp +var settings = new XmlReaderSettings +{ + DtdProcessing = DtdProcessing.Ignore +}; + +using (var xmlReader = XmlReader.Create("image.svg", settings)) +{ + var drawingImage = SvgReader.Load(xmlReader); +} +``` + +### 4. Использование DrawingImage в XAML + +```xaml + +``` + +Соответствующий C# код: + +```csharp +public DrawingImage SvgDrawingImage { get; private set; } + +public void LoadSvgImage(string filePath) +{ + using (var stream = File.OpenRead(filePath)) + { + SvgDrawingImage = SvgReader.Load(stream); + } +} +``` + +## Поддерживаемые элементы SVG + +### Основные графические элементы + +- `` — корневой элемент +- `` — группировка элементов +- `` — прямоугольник +- `` — круг +- `` — эллипс +- `` — линия +- `` — ломаная линия +- `` — полигон +- `` — путь (использует команды M, L, C, Z и т.д.) +- `` — текст +- `` — встроенное изображение +- `` — использование определённых элементов + +### Элементы определений и стилей + +- `` — определения элементов +- ` + /// public static DrawingImage Load(XmlReader reader, SvgReaderOptions options) { options ??= new(); var document = XDocument.Load(reader); if(document.Root.Name.NamespaceName != "http://www.w3.org/2000/svg") - throw new XmlException("Root element is not in namespace 'http://www.w3.org/2000/svg'."); + throw new XmlException("Корневой элемент не находится в пространстве имён 'http://www.w3.org/2000/svg'."); return document.Root.Name.LocalName == "svg" ? new SvgDocument(document.Root, options).Draw() - : throw new XmlException("Root element is not an element."); + : throw new XmlException("Корневой элемент не является элементом ."); } //========================================================================== /// - /// Loads an SVG document and renders it into a - /// . + /// Загружает SVG документ и преобразует его в объект с параметрами по умолчанию /// /// - /// A to read the XML structure of the SVG - /// document. + /// Объект для чтения XML структуры SVG документа /// - /// A containing the rendered SVG document. + /// Объект с преобразованным SVG документом + /// + /// + /// public static DrawingImage Load(XmlReader reader) => Load(reader, null); //========================================================================== /// - /// Loads an SVG document and renders it into a - /// . + /// Загружает SVG документ из потока и преобразует его в объект /// /// - /// A to read the XML structure of the SVG - /// document. + /// Объект для чтения содержимого SVG документа /// /// - /// to use for parsing respectively - /// rendering the SVG document. + /// Объект для настройки параметров парсинга и рендеринга SVG /// - /// A containing the rendered SVG document. + /// Объект с преобразованным SVG документом + /// + /// Этот метод автоматически создаёт XmlReader с отключённой обработкой DTD для безопасности + /// + /// + /// + /// public static DrawingImage Load(Stream stream, SvgReaderOptions options) { using var reader = XmlReader.Create(stream, new() { DtdProcessing = DtdProcessing.Ignore }); @@ -98,13 +123,17 @@ public static DrawingImage Load(Stream stream, SvgReaderOptions options) //========================================================================== /// - /// Loads an SVG document and renders it into a - /// . + /// Загружает SVG документ из потока и преобразует его в объект с параметрами по умолчанию /// /// - /// A to read the XML structure of the SVG - /// document. + /// Объект для чтения содержимого SVG документа /// - /// A containing the rendered SVG document. + /// Объект с преобразованным SVG документом + /// + /// + /// public static DrawingImage Load(Stream stream) => Load(stream, null); } // class SvgReader \ No newline at end of file diff --git a/MathCore.WPF/Svg2Xaml/SvgReaderOptions.cs b/MathCore.WPF/Svg2Xaml/SvgReaderOptions.cs index 294176f..d87405c 100644 --- a/MathCore.WPF/Svg2Xaml/SvgReaderOptions.cs +++ b/MathCore.WPF/Svg2Xaml/SvgReaderOptions.cs @@ -33,40 +33,61 @@ namespace MathCore.WPF.SVG; //**************************************************************************** /// -/// Defines a set of options to customize rendering repspectively reading -/// of SVG documents. +/// Определяет набор параметров для настройки парсинга и рендеринга SVG документов /// +/// +/// Класс используется для передачи опций в методы загрузки SvgReader. +/// Позволяет контролировать применение эффектов и другие параметры рендеринга +/// +/// +/// +/// public class SvgReaderOptions { //========================================================================== - private bool _MIgnoreEffects; + private bool _IgnoreEffects; //========================================================================== - /// Initializes a new instance. + /// Инициализирует новый экземпляр класса + /// Создаёт параметры с значениями по умолчанию public SvgReaderOptions() { // ... } //========================================================================== - /// Initializes a new instance. + /// Инициализирует новый экземпляр класса с указанными параметрами /// - /// Specifies whether filter effects should be applied using WPF bitmap - /// effects. + /// Значение, указывающее должны ли фильтр-эффекты SVG игнорироваться или преобразовываться в растровые эффекты WPF /// - public SvgReaderOptions(bool IgnoreEffects) => _MIgnoreEffects = IgnoreEffects; + /// + /// + /// + public SvgReaderOptions(bool IgnoreEffects) => _IgnoreEffects = IgnoreEffects; //========================================================================== /// - /// Gets or sets whether SVG effects should either be ignored or - /// converted to bitmap effects. + /// Получает или устанавливает значение, указывающее должны ли SVG эффекты игнорироваться или преобразовываться в растровые эффекты WPF /// + /// + /// Значение true отключает обработку фильтр-эффектов, значение false включает преобразование эффектов в + /// public bool IgnoreEffects { - get => _MIgnoreEffects; - - set => _MIgnoreEffects = value; + get => _IgnoreEffects; + set => _IgnoreEffects = value; } } // class SvgReaderOptions \ No newline at end of file diff --git a/MathCore.WPF/Svg2Xaml/SvgSVGElement.cs b/MathCore.WPF/Svg2Xaml/SvgSVGElement.cs index 5be46b6..f1c186e 100644 --- a/MathCore.WPF/Svg2Xaml/SvgSVGElement.cs +++ b/MathCore.WPF/Svg2Xaml/SvgSVGElement.cs @@ -30,6 +30,10 @@ namespace MathCore.WPF.SVG; -/// Represents an <svg> element. +/// Представляет элемент <svg> корневого элемента SVG документа +/// +/// Этот класс является представлением корневого элемента SVG и содержит все остальные элементы документа. +/// Отвечает за управление координатной системой, viewBox и другими параметрами всего документа +/// internal class SvgSvgElement(SvgDocument document, SvgBaseElement parent, XElement SvgElement) : SvgDrawableContainerBaseElement(document, parent, SvgElement); \ No newline at end of file From 030a3cdd2786601c109ebb03ed0bce7c1bab0140 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 02:59:17 +0300 Subject: [PATCH 26/33] =?UTF-8?q?docs:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=20=D1=80=D0=B5=D0=B2=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=8F=20TeX=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/TeX/Atom.cs | 47 +++++- MathCore.WPF/TeX/Box.cs | 62 ++++++- MathCore.WPF/TeX/HorizontalBox.cs | 23 ++- MathCore.WPF/TeX/README.md | 244 +++++++++++++++++++++++++++ MathCore.WPF/TeX/RowAtom.cs | 34 +++- MathCore.WPF/TeX/TexEnvironment.cs | 84 ++++++++- MathCore.WPF/TeX/TexFormula.cs | 104 ++++++++++-- MathCore.WPF/TeX/TexFormulaParser.cs | 111 +++++++++++- MathCore.WPF/TeX/TexRenderer.cs | 73 +++++++- MathCore.WPF/TeX/TexUtilities.cs | 8 +- MathCore.WPF/TeX/VerticalBox.cs | 42 ++++- 11 files changed, 795 insertions(+), 37 deletions(-) create mode 100644 MathCore.WPF/TeX/README.md diff --git a/MathCore.WPF/TeX/Atom.cs b/MathCore.WPF/TeX/Atom.cs index 193515f..75c1c36 100644 --- a/MathCore.WPF/TeX/Atom.cs +++ b/MathCore.WPF/TeX/Atom.cs @@ -1,19 +1,56 @@ namespace MathCore.WPF.TeX; -/// Atom (smallest unit) of TexFormula +/// +/// Атом (наименьшая единица) TeX формулы +/// +/// +/// Атомы - это строительные блоки всех математических формул в TeX модуле. +/// Каждый атом отвечает за: +/// 1. Хранение своих данных (символы, подформулы и т.д.) +/// 2. Определение своего типа (Ordinary, BinaryOperator, Relation и т.д.) +/// 3. Преобразование себя в визуальный бокс для рендеринга +/// +/// Существуют различные типы атомов: CharAtom (символ), RowAtom (ряд атомов), +/// FractionAtom (дробь), ScriptsAtom (индексы) и другие. +/// internal abstract class Atom { + /// Инициализирует новый экземпляр атома со стандартным типом Ordinary protected Atom() => Type = TexAtomType.Ordinary; + /// + /// Тип атома, определяющий его математические свойства и расстояния до соседних элементов + /// public TexAtomType Type { get; set; } + /// + /// Преобразует атом в визуальный бокс для рендеринга в указанной среде + /// + /// Среда рендеринга с информацией о стиле, шрифте и других параметрах + /// Бокс, который содержит визуальное представление этого атома + /// + /// Каждый подкласс Atom реализует этот метод для преобразования своих данных + /// в визуальное представление. Рекурсивно вызывается для всех дочерних атомов. + /// public abstract Box CreateBox(TexEnvironment environment); - /// Gets type of leftmost child item - /// + /// + /// Получает тип самого левого дочернего элемента (для подсчёта расстояний) + /// + /// Тип TexAtomType самого левого элемента + /// + /// Используется при подсчёте расстояний между соседними атомами. + /// Для составных атомов (RowAtom, FractionAtom) возвращает тип левого потомка. + /// public virtual TexAtomType GetLeftType() => Type; - /// Gets type of leftmost child item - /// + /// + /// Получает тип самого правого дочернего элемента (для подсчёта расстояний) + /// + /// Тип TexAtomType самого правого элемента + /// + /// Используется при подсчёте расстояний между соседними атомами. + /// Для составных атомов (RowAtom, FractionAtom) возвращает тип правого потомка. + /// public virtual TexAtomType GetRightType() => Type; } \ No newline at end of file diff --git a/MathCore.WPF/TeX/Box.cs b/MathCore.WPF/TeX/Box.cs index 76a0d89..04af979 100644 --- a/MathCore.WPF/TeX/Box.cs +++ b/MathCore.WPF/TeX/Box.cs @@ -2,34 +2,66 @@ using System.Windows; using System.Windows.Media; -// Represents graphical box that is part of math expression, and can itself contain child boxes. namespace MathCore.WPF.TeX; +/// +/// Визуальный бокс - графический элемент математического выражения, который может содержать дочерние боксы +/// +/// +/// Боксы - это результат преобразования атомов (Atom) в визуальное представление. +/// Каждый бокс содержит: +/// - Размеры (ширину, высоту, глубину) +/// - Визуальные свойства (кисти для переднего плана и фона) +/// - Дочерние боксы (для составных элементов) +/// +/// Различные типы боксов отрисовывают себя по-разному: +/// CharBox отрисовывает символ, HorizontalBox - ряд боксов, FractionBox - дробь и т.д. +/// public abstract class Box { private readonly List _Children; private readonly ReadOnlyCollection _ChildrenReadOnly; + /// Дочерние боксы, входящие в состав этого бокса public ReadOnlyCollection Children => _ChildrenReadOnly; + /// Кисть для отрисовки переднего плана (символы, линии и т.д.) public Brush? Foreground { get; set; } + /// Кисть для отрисовки фонового прямоугольника public Brush Background { get; set; } + /// + /// Общая высота бокса (высота над базовой линией + глубина ниже базовой линии) + /// public double TotalHeight => Height + Depth; + /// Ширина бокса (в единицах относительного размера, масштабируется при рендеринге) public double Width { get; set; } + /// Высота бокса выше базовой линии (в единицах относительного размера) public double Height { get; set; } + /// Глубина бокса ниже базовой линии (в единицах относительного размера) public double Depth { get; set; } + /// Вертикальное смещение бокса от базовой линии (положительное = вверх) public double Shift { get; set; } + /// + /// Инициализирует новый экземпляр бокса с использованием визуальных свойств из среды + /// + /// Среда рендеринга с информацией о цветах и стилях internal Box(TexEnvironment environment) : this(environment.Foreground, environment.Background) { } + /// Инициализирует новый экземпляр пустого бокса protected Box() : this(null, null) { } + /// + /// Инициализирует новый экземпляр бокса с заданными визуальными свойствами + /// + /// Кисть для переднего плана + /// Кисть для фона protected Box(Brush foreground, Brush background) { _Children = []; @@ -38,17 +70,43 @@ protected Box(Brush foreground, Brush background) Background = background; } + /// + /// Отрисовывает бокс и его дочерние элементы в WPF DrawingContext + /// + /// WPF контекст для отрисовки + /// Коэффициент масштабирования (обычно размер шрифта) + /// Координата X в пиксельных точках + /// Координата Y в пиксельных точках (базовая линия) + /// + /// Базовая реализация отрисовывает фоновый прямоугольник, если задана кисть для фона. + /// Подклассы переопределяют этот метод для отрисовки своего специфичного содержимого. + /// public virtual void Draw(DrawingContext Context, double scale, double x, double y) { if(Background is null) return; - // Fill background of box with color. + // Отрисовать фон бокса прямоугольником Context.DrawRectangle(Background, null, new(x * scale, (y - Height) * scale, Width * scale, (Height + Depth) * scale)); } + /// Добавляет дочерний бокс в конец списка дочерних элементов + /// Бокс для добавления public virtual void Add(Box box) => _Children.Add(box); + /// + /// Вставляет дочерний бокс в указанную позицию в списке дочерних элементов + /// + /// Позиция для вставки (0-основана) + /// Бокс для вставки public virtual void Add(int position, Box box) => _Children.Insert(position, box); + /// + /// Получает идентификатор последнего используемого шрифта в этом боксе и его дочерних элементах + /// + /// Идентификатор шрифта + /// + /// Используется для оптимизации - когда несколько элементов используют один шрифт, + /// можно избежать переключения контекста рендеринга. + /// public abstract int GetLastFontId(); } \ No newline at end of file diff --git a/MathCore.WPF/TeX/HorizontalBox.cs b/MathCore.WPF/TeX/HorizontalBox.cs index 2ba5926..ff0c8c9 100644 --- a/MathCore.WPF/TeX/HorizontalBox.cs +++ b/MathCore.WPF/TeX/HorizontalBox.cs @@ -2,11 +2,24 @@ namespace MathCore.WPF.TeX; -/// Box containing horizontal stack of child boxes +/// +/// Бокс, содержащий горизонтальный стек дочерних боксов +/// +/// +/// HorizontalBox используется для размещения боксов в горизонтальном направлении. +/// Поддерживает выравнивание (влево, по центру, вправо). +/// internal sealed class HorizontalBox : Box { + /// Общая ширина всех дочерних боксов private double _ChildBoxesTotalWidth; + /// + /// Инициализирует новый бокс с одним элементом и заданным выравниванием + /// + /// Элемент для размещения + /// Требуемая ширина контейнера + /// Выравнивание (Center, Left, Right) public HorizontalBox(Box box, double width, TexAlignment alignment) : this() { @@ -30,12 +43,20 @@ public HorizontalBox(Box box, double width, TexAlignment alignment) } } + /// Инициализирует новый горизонтальный бокс с одним элементом + /// Элемент для добавления public HorizontalBox(Box box) : this() => Add(box); + /// Инициализирует новый горизонтальный бокс с кистями для отрисовки + /// Кисть переднего плана + /// Кисть фона public HorizontalBox(Brush foreground, Brush background) : base(foreground, background) { } + /// Инициализирует новый пустой горизонтальный бокс public HorizontalBox() { } + /// Добавляет дочерний бокс и обновляет общую ширину + /// Бокс для добавления public override void Add(Box box) { base.Add(box); diff --git a/MathCore.WPF/TeX/README.md b/MathCore.WPF/TeX/README.md new file mode 100644 index 0000000..0846e5d --- /dev/null +++ b/MathCore.WPF/TeX/README.md @@ -0,0 +1,244 @@ +# MathCore.WPF.TeX - TeX Rendering Engine для WPF + +Модуль `MathCore.WPF.TeX` представляет собой полнофункциональный механизм рендеринга математических формул в формате TeX для приложений WPF. + +## Основные возможности + +- **Парсинг TeX формул** - поддержка синтаксиса LaTeX математических выражений +- **Рендеринг в WPF** - отрисовка формул в DrawingContext с поддержкой масштабирования +- **Гибкая система шрифтов** - поддержка различных TeX шрифтов и стилей +- **Управление стилями** - применение различных математических стилей (Display, Text, Script, ScriptScript) +- **Поддержка символов** - включение специальных математических символов и операторов +- **Визуальный контроль MathView** - WPF элемент для отображения формул в приложениях + +## Архитектура модуля + +### Основные компоненты + +#### 1. **Парсер (TexFormulaParser)** +Преобразует строковое представление TeX формулы в дерево атомов (Atom). + +```csharp +var parser = new TexFormulaParser(); +var formula = parser.Parse("x^2 + y^2 = z^2"); +``` + +#### 2. **Атомы (Atom и его наследники)** +Наименьшие единицы TeX формулы. Каждый атом отвечает за отрисовку своей части: + +- `CharAtom` - отдельный символ +- `RowAtom` - горизонтальный ряд атомов +- `FractionAtom` - дробь +- `ScriptsAtom` - надстрочные/подстрочные индексы +- `BigOperatorAtom` - большие операторы (∑, ∫ и т.д.) +- `RootAtom` - корень (квадратный, кубический и т.д.) +- `FencedAtom` - огороженные выражения + +#### 3. **Формула (TexFormula)** +Контейнер для корневого атома и управления процессом рендеринга. + +```csharp +var formula = new TexFormula(); +formula.Add(new CharAtom('x')); +var renderer = formula.GetRenderer(TexStyle.Display, 20.0); +``` + +#### 4. **Среда (TexEnvironment)** +Хранит контекстную информацию о текущем стиле, шрифте и прочие параметры рендеринга. + +#### 5. **Шрифты (ITeXFont и наследники)** +Интерфейс для работы с TeX шрифтами: +- `DefaultTexFont` - стандартный TeX шрифт +- Обеспечивают метрики символов и сведения о расположении в шрифтах + +#### 6. **Боксы (Box и наследники)** +Результат рендеринга атома. Содержат информацию о размерах и способны рисовать себя: + +- `CharBox` - отрисовка символа +- `HorizontalBox` - горизонтальный композитный бокс +- `VerticalBox` - вертикальный композитный бокс +- `FractionBox` - отрисовка дроби +- `OverUnderBox` - элементы над/под выражением + +#### 7. **Рендерер (TexRenderer)** +Финальный этап: преобразование дерева боксов в команды отрисовки WPF. + +```csharp +var renderer = formula.GetRenderer(TexStyle.Display, scale: 20.0); +renderer.Render(drawingContext, x: 10, y: 10); +``` + +#### 8. **Визуальный контроль (MathView)** +WPF элемент для простого использования в XAML приложениях: + +```xaml + +``` + +### Основной цикл обработки + +``` +Строка TeX + ↓ +TexFormulaParser.Parse() + ↓ +Дерево Atom объектов + ↓ +Atom.CreateBox(TexEnvironment) + ↓ +Дерево Box объектов + ↓ +Box.Draw(DrawingContext, scale, x, y) + ↓ +Визуализация в WPF +``` + +## Использование + +### Базовое использование - парсинг и рендеринг + +```csharp +// 1. Создать парсер +var parser = new TexFormulaParser(); + +// 2. Распарсить формулу +var formula = parser.Parse("\\frac{a}{b}"); + +// 3. Получить рендерер +var renderer = formula.GetRenderer(TexStyle.Display, scale: 20.0); + +// 4. Отрисовать в DrawingContext +using (var drawingGroup = new DrawingGroup()) +{ + var drawingContext = drawingGroup.Open(); + renderer.Render(drawingContext, x: 10, y: 10); + drawingContext.Close(); +} +``` + +### Использование в XAML (MathView) + +```xaml + + + + + +``` + +### Программное построение формул + +```csharp +var formula = new TexFormula(); +formula.Add(new CharAtom('x')); +formula.Add(new ScriptsAtom(null, new CharAtom('2'), null, null)); + +var renderer = formula.GetRenderer(TexStyle.Text, 16.0); +``` + +## Поддерживаемые TeX команды + +### Базовые команды +- `\frac{numbers}{denominator}` - обыкновенная дробь +- `\sqrt[n]{x}` - корень n-й степени +- `^` - надстрочный индекс (верхний индекс) +- `_` - подстрочный индекс (нижний индекс) +- `'` - штрих (производная) + +### Стили и оформление +- `\overline{x}` - надчеркивание +- `\underline{x}` - подчеркивание +- `\hat{x}`, `\tilde{x}`, `\bar{x}` - диакритические знаки +- `\phantom{x}` - невидимый символ с тем же размером +- `\left` и `\right` - автоматическое масштабирование ограничителей + +### Большие операторы +- `\sum` - сумма +- `\prod` - произведение +- `\int` - интеграл +- `\oint` - циркулярный интеграл + +### Ограничители и скобки +- `(`, `)` - круглые скобки +- `[`, `]` - квадратные скобки +- `\{`, `\}` - фигурные скобки +- `|` - вертикальная линия +- `\|` - двойная вертикальная линия + +## Стили математического текста + +Модуль поддерживает четыре основных стиля TeX: + +- **Display** (`TexStyle.Display`) - для отдельных строк формул +- **Text** (`TexStyle.Text`) - для формул внутри текста +- **Script** (`TexStyle.Script`) - для индексов (меньший размер) +- **ScriptScript** (`TexStyle.ScriptScript`) - для вложенных индексов (еще меньше) + +Каждый стиль автоматически масштабирует шрифты согласно TeX стандартам. + +## Конфигурация шрифтов + +Модуль использует конфигурацию шрифтов из XML файлов: +- `TexFormulaSettings.xml` - основные параметры форматирования +- `TexSymbols.xml` - отображение символов на шрифты +- `GlueSettings.xml` - параметры пробелов между элементами +- `DefaultTexFont.xml` - параметры шрифта по умолчанию +- `PredefinedTexFormulas.xml` - предопределённые формулы + +Все файлы конфигурации расположены в директории `Styles`. + +## Расширение функциональности + +### Добавление новых символов + +1. Обновить `TexSymbols.xml` для добавления отображения символа на шрифт +2. Обновить `DefaultTexFont.xml` с метриками нового символа + +### Добавление новых команд + +1. Создать новый класс, наследующий `Atom` +2. Реализовать метод `CreateBox(TexEnvironment environment)` +3. Зарегистрировать команду в `TexFormulaParser` + +## Производительность и оптимизация + +- **Кэширование** - результаты парсинга и рендеринга кэшируются где возможно +- **Ленивое вычисление** - размеры боксов вычисляются по необходимости +- **Масштабирование вместо перерисовки** - используется коэффициент масштабирования вместо пересоздания боксов + +## Обработка ошибок + +Модуль выбрасывает следующие исключения: +- `TexParseException` - ошибка парсинга формулы +- `FormulaNotFoundException` - неизвестная команда +- `SymbolNotFoundException` - неизвестный символ +- `DelimiterMappingNotFoundException` - неизвестный ограничитель +- `SymbolMappingNotFoundException` - символ не может быть отображён на шрифт + +## Примеры сложных формул + +``` +E = mc^2 +\frac{-b \pm \sqrt{b^2 - 4ac}}{2a} +\int_0^\infty e^{-x^2} dx +\left(\frac{n}{k}\right) = \frac{n!}{k!(n-k)!} +``` + +## Лицензия и авторство + +Данный модуль является частью проекта MathCore.WPF. Основан на идеях и компонентах Math.NET Numerics и WPF-Math. + +## Дополнительные ресурсы + +- Синтаксис TeX: https://en.wikibooks.org/wiki/LaTeX/Mathematics +- Справка по символам: https://en.wikibooks.org/wiki/LaTeX/Special_Characters +- Стили в LaTeX: https://en.wikibooks.org/wiki/LaTeX/Fonts#Math_mode + +--- + +**Версия документации**: 1.0 +**Последнее обновление**: 2024 diff --git a/MathCore.WPF/TeX/RowAtom.cs b/MathCore.WPF/TeX/RowAtom.cs index da27ea2..0dac207 100644 --- a/MathCore.WPF/TeX/RowAtom.cs +++ b/MathCore.WPF/TeX/RowAtom.cs @@ -1,14 +1,25 @@ using System.Collections; -// Atom representing horizontal row of other atoms, separated by glue. namespace MathCore.WPF.TeX; +/// +/// Атом, представляющий горизонтальный ряд других атомов, разделённых пробелами (glue) +/// +/// +/// RowAtom служит контейнером для последовательности атомов, размещаемых горизонтально. +/// Этот класс отвечает за: +/// 1. Управление коллекцией дочерних атомов +/// 2. Обработку правил TeX типографии (BinaryOperatorChangeSet, LigatureKernChangeSet) +/// 3. Вставку пробелов между элементами согласно их типам +/// internal class RowAtom : Atom, IRow { - // Set of atom types that make previous atom of BinaryOperator type change to Ordinary type. + // Набор типов атомов, которые могут изменить тип BinaryOperator в Ordinary + // (например, если за оператором следует отношение, оператор становится обыкновенным символом) private static readonly BitArray __BinaryOperatorChangeSet; - // Set of atom types that may need kern, or together with previous atom, be replaced by ligature. + // Набор типов атомов, которые могут требовать kern (тонкую подстройку расстояния) + // или вместе с предыдущим атомом быть заменены на лигатуру (если такая существует) private static readonly BitArray __LigatureKernChangeSet; static RowAtom() @@ -30,14 +41,27 @@ static RowAtom() __LigatureKernChangeSet.Set((int)TexAtomType.Punctuation, true); } + /// Предыдущий атом (используется для подсчёта типов) public DummyAtom PreviousAtom { get; set; } + + /// Коллекция дочерних атомов в этом ряду public List Elements { get; } + + /// Инициализирует новый пустой ряд атомов public RowAtom() => Elements = []; + /// + /// Инициализирует новый ряд атомов из списка формул + /// + /// Список формул для добавления public RowAtom(List FormulaList) : this() => Elements.AddRange(FormulaList.Where(f => f.RootAtom != null).Select(f => f.RootAtom)); + /// + /// Инициализирует новый ряд из одного базового атома + /// + /// Базовый атом (может быть null или RowAtom) public RowAtom(Atom? BaseAtom) : this() { @@ -49,6 +73,10 @@ public RowAtom(Atom? BaseAtom) } } + /// + /// Добавляет атом в ряд + /// + /// Добавляемый атом public void Add(Atom? atom) { if(atom != null) Elements.Add(atom); } private static void ChangeAtomToOrdinary(DummyAtom CurrentAtom, DummyAtom? PreviousAtom, Atom? NextAtom) diff --git a/MathCore.WPF/TeX/TexEnvironment.cs b/MathCore.WPF/TeX/TexEnvironment.cs index f875427..aa02c18 100644 --- a/MathCore.WPF/TeX/TexEnvironment.cs +++ b/MathCore.WPF/TeX/TexEnvironment.cs @@ -2,29 +2,63 @@ namespace MathCore.WPF.TeX; -/// Specifies current graphical parameters used to create boxes +/// +/// Среда рендеринга, определяющая текущие графические параметры для создания боксов +/// +/// +/// Класс служит контейнером для всей информации, необходимой при преобразовании атомов в боксы: +/// - Математический стиль (Display, Text, Script, ScriptScript) +/// - Используемый TeX шрифт с метриками +/// - Цвета переднего плана и фона +/// - ID последнего используемого шрифта для оптимизации +/// +/// Среда выполняет две основные функции: +/// 1. Хранит контекстную информацию, передаваемую вниз по иерархии атомов +/// 2. Предоставляет методы для создания новых сред с модифицированными стилями +/// internal class TexEnvironment { - /// ID of font that was last used + /// ID шрифта, который был использован последним private int _LastFontId = TexFontUtilities.NoFontId; + /// Текущий математический стиль для рендеринга public TexStyle Style { get; private set; } + /// TeX шрифт, используемый для получения метрик и информации о символах public ITeXFont TexFont { get; } + /// Кисть для отрисовки фонового прямоугольника public Brush Background { get; set; } + /// Кисть для отрисовки переднего плана (символы, линии и т.д.) public Brush Foreground { get; set; } + + /// + /// ID последнего используемого шрифта (используется для оптимизации при рендеринге) + /// public int LastFontId { get => _LastFontId == TexFontUtilities.NoFontId ? TexFont.GetMuFontId() : _LastFontId; set => _LastFontId = value; } + /// + /// Инициализирует новую среду рендеринга с заданным стилем и шрифтом + /// + /// Математический стиль + /// TeX шрифт с метриками public TexEnvironment(TexStyle style, ITeXFont TexFont) : this(style, TexFont, null, null) { } + /// + /// Инициализирует новую среду с полным набором параметров + /// + /// Математический стиль + /// TeX шрифт + /// Кисть для фона (может быть null) + /// Кисть для переднего плана (может быть null) private TexEnvironment(TexStyle style, ITeXFont TexFont, Brush background, Brush foreground) { + // Валидировать и установить стиль if(style is TexStyle.Display or TexStyle.Text or TexStyle.Script or TexStyle.ScriptScript) Style = style; else @@ -35,6 +69,14 @@ private TexEnvironment(TexStyle style, ITeXFont TexFont, Brush background, Brush Foreground = foreground; } + /// + /// Получает сжатый стиль для специальных типов атомов + /// + /// Новая среда со сжатым стилем + /// + /// Сжатый стиль используется для некоторых специальных атомов + /// (например, для радикалов и индексов). + /// public TexEnvironment GetCrampedStyle() { var new_environment = Clone(); @@ -42,6 +84,14 @@ public TexEnvironment GetCrampedStyle() return new_environment; } + /// + /// Получает стиль для числителя дроби + /// + /// Новая среда со стилем для числителя + /// + /// Стиль для числителя обычно меньше, чем для основного выражения, + /// так как дроби с дробями должны выглядеть меньше. + /// public TexEnvironment GetNumeratorStyle() { var new_environment = Clone(); @@ -49,6 +99,13 @@ public TexEnvironment GetNumeratorStyle() return new_environment; } + /// + /// Получает стиль для знаменателя дроби + /// + /// Новая среда со стилем для знаменателя + /// + /// Стиль для знаменателя обычно меньше, чем для основного выражения. + /// public TexEnvironment GetDenominatorStyle() { var new_environment = Clone(); @@ -56,6 +113,10 @@ public TexEnvironment GetDenominatorStyle() return new_environment; } + /// + /// Получает стиль для индекса (степень корня) + /// + /// Новая среда со стилем ScriptScript public TexEnvironment GetRootStyle() { var new_environment = Clone(); @@ -63,6 +124,13 @@ public TexEnvironment GetRootStyle() return new_environment; } + /// + /// Получает стиль для подстрочного индекса + /// + /// Новая среда со стилем для подстрочного индекса + /// + /// Подстрочный индекс обычно меньше основного выражения. + /// public TexEnvironment GetSubscriptStyle() { var new_environment = Clone(); @@ -70,6 +138,13 @@ public TexEnvironment GetSubscriptStyle() return new_environment; } + /// + /// Получает стиль для надстрочного индекса + /// + /// Новая среда со стилем для надстрочного индекса + /// + /// Надстрочный индекс обычно меньше основного выражения. + /// public TexEnvironment GetSuperscriptStyle() { var new_environment = Clone(); @@ -77,8 +152,13 @@ public TexEnvironment GetSuperscriptStyle() return new_environment; } + /// + /// Создаёт копию текущей среды с сохранением всех параметров + /// + /// Новая среда - клон текущей public TexEnvironment Clone() => new(Style, TexFont, Background, Foreground); + /// Очищает цвета переднего плана и фона, но сохраняет стиль и шрифт public void Reset() { Background = null; diff --git a/MathCore.WPF/TeX/TexFormula.cs b/MathCore.WPF/TeX/TexFormula.cs index 9470231..d786829 100644 --- a/MathCore.WPF/TeX/TexFormula.cs +++ b/MathCore.WPF/TeX/TexFormula.cs @@ -2,9 +2,45 @@ namespace MathCore.WPF.TeX; -/// Represents mathematical formula that can be rendered +/// +/// Представляет математическую формулу, которая может быть отрисована в WPF контексте +/// +/// +/// Класс служит контейнером для иерархии атомов (Atom) и управляет процессом преобразования +/// формулы в боксы для рендеринга. Основной цикл жизни: +/// 1. Парсинг TeX строки создаёт TexFormula с корневым атомом +/// 2. Вызов GetRenderer() преобразует атомы в боксы +/// 3. Рендерер отрисовывает боксы в DrawingContext WPF +/// +/// +/// +/// { +/// subFormula1, +/// subFormula2 +/// }); +/// +/// // Получение рендерера и отрисовка +/// var renderer = formula.GetRenderer(TexStyle.Display, scale: 20.0); +/// using (var drawingGroup = new DrawingGroup()) +/// { +/// var dc = drawingGroup.Open(); +/// renderer.Render(dc, 10, 10); +/// dc.Close(); +/// } +/// ]]> +/// public sealed class TexFormula { + /// + /// Инициализирует новый экземпляр TexFormula из списка подформул + /// + /// Список подформул для объединения в одну формулу + /// + /// Если в списке одна формула, она используется как есть. + /// Если несколько - они объединяются в RowAtom + /// public TexFormula(List FormulaList) { Debug.Assert(FormulaList != null); @@ -15,10 +51,16 @@ public TexFormula(List FormulaList) RootAtom = new RowAtom(FormulaList); } + /// Текстовый стиль, применяемый к формуле public string TextStyle { get; set; } + /// Корневой атом формулы (основная единица TeX формулы) internal Atom? RootAtom { get; set; } + /// + /// Инициализирует новый экземпляр TexFormula на основе другой формулы + /// + /// Исходная формула для копирования public TexFormula(TexFormula formula) { Debug.Assert(formula != null); @@ -26,14 +68,42 @@ public TexFormula(TexFormula formula) Add(formula); } + /// Инициализирует новый пустой экземпляр TexFormula public TexFormula() { } + /// + /// Получает рендерер для отрисовки формулы в WPF контексте + /// + /// Математический стиль (Display, Text, Script, ScriptScript) + /// Коэффициент масштабирования (обычно размер шрифта в пиксельных точках) + /// Рендерер, готовый к отрисовке формулы + /// + /// Этот метод преобразует иерархию атомов в иерархию боксов и создаёт рендерер, + /// который может отрисовать формулу в DrawingContext WPF + /// + /// + /// + /// public TexRenderer GetRenderer(TexStyle style, double scale) { var environment = new TexEnvironment(style, new DefaultTexFont(scale)); return new(CreateBox(environment), scale); } + /// + /// Добавляет подформулу к текущей формуле + /// + /// Подформула для добавления + /// + /// Если добавляемая формула содержит RowAtom, он оборачивается в новый RowAtom. + /// Остальные атомы добавляются как есть. + /// public void Add(TexFormula formula) { Debug.Assert(formula != null); @@ -42,6 +112,13 @@ public void Add(TexFormula formula) Add(formula.RootAtom is RowAtom ? new RowAtom(formula.RootAtom) : formula.RootAtom); } + /// Добавляет атом к корневому атому формулы + /// Атом для добавления + /// + /// Если корневой атом ещё не установлен, атом становится корневым. + /// Если корневой атом уже существует и не является RowAtom, он оборачивается в RowAtom. + /// Затем новый атом добавляется в RowAtom. + /// internal void Add(Atom atom) { Debug.Assert(atom != null); @@ -55,22 +132,15 @@ internal void Add(Atom atom) } } - //public void SetForeground(Brush brush) - //{ - // if(RootAtom is StyledAtom) - // ((StyledAtom)(RootAtom = ((StyledAtom)RootAtom).Clone())).Foreground = brush; - // else - // RootAtom = new StyledAtom(RootAtom, null, brush); - //} - - //public void SetBackground(Brush brush) - //{ - // if(RootAtom is StyledAtom) - // ((StyledAtom)(RootAtom = ((StyledAtom)RootAtom).Clone())).Background = brush; - // else - // RootAtom = new StyledAtom(RootAtom, brush, null); - //} - + /// + /// Преобразует корневой атом в бокс, готовый к рендерингу + /// + /// Среда рендеринга, содержащая стиль и информацию о шрифте + /// Бокс, который может быть отрисован в DrawingContext WPF + /// + /// Если корневой атом не установлен, возвращается пустой StrutBox. + /// Для каждого атома вызывается метод CreateBox для получения его визуального представления. + /// internal Box CreateBox(TexEnvironment environment) { var root = RootAtom; diff --git a/MathCore.WPF/TeX/TexFormulaParser.cs b/MathCore.WPF/TeX/TexFormulaParser.cs index 9711477..892ed37 100644 --- a/MathCore.WPF/TeX/TexFormulaParser.cs +++ b/MathCore.WPF/TeX/TexFormulaParser.cs @@ -1,29 +1,89 @@ using System.Text; -// TODO: put all error strings into resources +// Примечание: все строки ошибок должны быть перемещены в ресурсы для локализации namespace MathCore.WPF.TeX; +/// +/// Парсер TeX формул - преобразует строковое представление в дерево атомов +/// +/// +/// Парсер реализует синтаксис LaTeX математических выражений и преобразует их +/// в иерархию объектов Atom, которые затем преобразуются в визуальные боксы для рендеринга. +/// +/// Поддерживаемые конструкции: +/// - Символы и команды (например, \frac, \sqrt) +/// - Группы в фигурных скобках +/// - Надстрочные (^) и подстрочные (_) индексы +/// - Штрихи (') для производных +/// - Предопределённые формулы и символы +/// +/// Парсер инициализируется один раз при создании первого экземпляра, +/// загружая конфигурацию из XML файлов. +/// +/// +/// +/// public class TexFormulaParser { - // Special characters for parsing + // Специальные символы для парсинга + /// Символ экранирования для команд TeX private const char __EscapeChar = '\\'; + /// Символ открывающейся группы private const char __LeftGroupChar = '{'; + + /// Символ закрывающейся группы private const char __RightGroupChar = '}'; + + /// Символ открывающейся квадратной скобки private const char __LeftBracketChar = '['; + + /// Символ закрывающейся квадратной скобки private const char __RightBracketChar = ']'; + /// Символ подстрочного индекса private const char __SubScriptChar = '_'; + + /// Символ надстрочного индекса private const char __SuperScriptChar = '^'; + + /// Символ штриха (производной) private const char __PrimeChar = '\''; - // Information used for parsing + // Информация, используемая при парсинге + /// Набор поддерживаемых команд TeX (frac, sqrt и т.д.) private static HashSet __Commands; + + /// Массив отображений символов на их TeX представления private static string[] __Symbols; + + /// Массив отображений ограничителей на их TeX представления private static string[] __Delimiters; + + /// Набор поддерживаемых текстовых стилей private static HashSet __TextStyles; + + /// Словарь предопределённых формул (кэш загруженных формул) private static readonly Dictionary __PredefinedFormulas; + /// + /// Названия парных ограничителей для автоматического масштабирования + /// (например, \left( и \right)) + /// private static readonly string[][] __DelimiterNames = [ ["lbrace", "rbrace"], @@ -39,11 +99,13 @@ public class TexFormulaParser ["Vert", "Vert"] ]; - /// True if parser has been initialized + /// Флаг инициализации парсера private static bool __IsInitialized; + /// Получить названия парных ограничителей internal static string[][] DelimiterNames => __DelimiterNames; + /// Статический конструктор инициализирует парсер при первом использовании static TexFormulaParser() { __IsInitialized = false; @@ -52,10 +114,13 @@ static TexFormulaParser() Initialize(); } + /// Инициализирует внутренние структуры парсера из конфигурации private static void Initialize() { + // Регистрация поддерживаемых команд __Commands = ["frac", "sqrt"]; + // Загрузка конфигурации из XML файлов var formula_settings_parser = new TexPredefinedFormulaSettingsParser(); __Symbols = formula_settings_parser.GetSymbolMappings(); __Delimiters = formula_settings_parser.GetDelimiterMappings(); @@ -63,16 +128,28 @@ private static void Initialize() __IsInitialized = true; + // Загрузка предопределённых формул var predefined_formulas_parser = new TexPredefinedFormulaParser(); predefined_formulas_parser.Parse(__PredefinedFormulas); } + /// + /// Получает предопределённую формулу по имени + /// + /// Имя предопределённой формулы + /// Формула или null, если не найдена internal static TexFormula? GetFormula(string name) { var f = __PredefinedFormulas.GetValue(name); return f is null ? null : new TexFormula(f); } + /// + /// Получает TeX представление ограничителя по символу + /// + /// Символ ограничителя + /// TeX имя ограничителя + /// Если ограничитель не найден internal static string GetDelimeterMapping(char character) { if (character < 0 || character >= __Delimiters.Length) @@ -81,22 +158,48 @@ internal static string GetDelimeterMapping(char character) return __Delimiters[character]; } + /// + /// Получает атом ограничителя по его имени + /// + /// TeX имя ограничителя + /// Атом ограничителя или null, если не является ограничителем internal static SymbolAtom? GetDelimiterSymbol(string name) { var result = SymbolAtom.GetAtom(name); return !result.IsDelimeter ? null : result; } + /// Проверяет, является ли символ специальным TeX символом private static bool IsSymbol(char c) => c is not (>= '0' and <= '9' or >= 'a' and <= 'z' or >= 'A' and <= 'Z'); + /// Проверяет, является ли символ пробельным private static bool IsWhiteSpace(char ch) => ch is ' ' or '\t' or '\n' or '\r'; + /// Инициализирует новый экземпляр парсера TeX формул + /// Если парсер не был инициализирован public TexFormulaParser() { if (!__IsInitialized) throw new InvalidOperationException("Parser has not yet been initialized."); } + /// + /// Парсит строку TeX формулы и возвращает объект TexFormula + /// + /// TeX строка формулы для парсинга + /// Объект TexFormula, готовый к рендерингу + /// Если обнаружена синтаксическая ошибка в формуле + /// + /// Этот метод преобразует TeX строку в дерево атомов (Atom). + /// Поддерживает все основные конструкции LaTeX математических выражений. + /// + /// + /// + /// public TexFormula Parse(string value) { var formula = new TexFormula(); diff --git a/MathCore.WPF/TeX/TexRenderer.cs b/MathCore.WPF/TeX/TexRenderer.cs index 4f186fe..a057f30 100644 --- a/MathCore.WPF/TeX/TexRenderer.cs +++ b/MathCore.WPF/TeX/TexRenderer.cs @@ -3,17 +3,88 @@ namespace MathCore.WPF.TeX; +/// +/// Рендерер для отрисовки иерархии боксов математической формулы в WPF DrawingContext +/// +/// +/// Класс служит мостом между миром боксов (логическое представление) и WPF отрисовкой. +/// Хранит исходный бокс и коэффициент масштабирования, а также предоставляет методы +/// для отрисовки и получения размеров формулы. +/// +/// +/// +/// public class TexRenderer { + /// Главный бокс, содержащий всю иерархию боксов формулы public Box Box { get; set; } + /// Коэффициент масштабирования, применяемый при рендеринге public double Scale { get; } + /// + /// Размер отрисованной формулы в пиксельных точках (с учётом масштабирования) + /// public Size RenderSize => new(Box.Width * Scale, Box.TotalHeight * Scale); + /// + /// Расстояние от верхнего края до базовой линии формулы (используется для выравнивания) + /// public double Baseline => Box.Height / Box.TotalHeight * Scale; - internal TexRenderer(Box box, double scale) { Box = box; Scale = scale; } + /// + /// Инициализирует новый экземпляр рендерера (обычно создаётся через TexFormula.GetRenderer) + /// + /// Главный бокс со всей иерархией формулы + /// Коэффициент масштабирования + internal TexRenderer(Box box, double scale) + { + Box = box; + Scale = scale; + } + + /// + /// Отрисовывает формулу в указанный DrawingContext на заданные координаты + /// + /// WPF контекст отрисовки + /// Координата X в пиксельных точках + /// Координата Y в пиксельных точках + /// + /// Метод преобразует координаты, учитывая масштаб, и вызывает методы отрисовки боксов. + /// Координата Y интерпретируется как верхний край формулы. + /// + /// + /// + /// public void Render(DrawingContext DrawingContext, double x, double y) { var box = Box; diff --git a/MathCore.WPF/TeX/TexUtilities.cs b/MathCore.WPF/TeX/TexUtilities.cs index d5335dc..3c2b445 100644 --- a/MathCore.WPF/TeX/TexUtilities.cs +++ b/MathCore.WPF/TeX/TexUtilities.cs @@ -1,10 +1,16 @@ namespace MathCore.WPF.TeX; +/// +/// Утилиты и константы для работы с TeX механизмом рендеринга +/// internal static class TexUtilities { + /// Пространство имён для ресурсов стилей TeX public const string ResourcesStylesNamespace = "MathCore.WPF.TeX.Styles."; - public const string ResourcesFontsNamespace = "MathCore.WPF.TeX.Fonts."; + /// Пространство имён для ресурсов шрифтов TeX + public const string ResourcesFontsNamespace = "MathCore.WPF.TeX.Fonts."; + /// Точность сравнения чисел с плавающей запятой при вычислении метрик шрифтов public const double FloatPrecision = 0.0000001; } \ No newline at end of file diff --git a/MathCore.WPF/TeX/VerticalBox.cs b/MathCore.WPF/TeX/VerticalBox.cs index 4b63357..75de8ee 100644 --- a/MathCore.WPF/TeX/VerticalBox.cs +++ b/MathCore.WPF/TeX/VerticalBox.cs @@ -2,12 +2,27 @@ namespace MathCore.WPF.TeX; -/// Box containing vertical stack of child boxes +/// +/// Бокс, содержащий вертикальный стек дочерних боксов +/// +/// +/// VerticalBox используется для размещения боксов в вертикальном направлении. +/// Поддерживает выравнивание (сверху, по центру, снизу). +/// internal class VerticalBox : Box { + /// Самая левая позиция дочерних боксов private double _LeftMostPos = double.MaxValue; + + /// Самая правая позиция дочерних боксов private double _RightMostPos = double.MinValue; + /// + /// Инициализирует новый вертикальный бокс с одним элементом и заданным выравниванием + /// + /// Элемент для размещения + /// Дополнительное пространство для выравнивания + /// Выравнивание (Center, Top, Bottom) public VerticalBox(Box box, double rest, TexAlignment alignment) { Add(box); @@ -31,8 +46,13 @@ public VerticalBox(Box box, double rest, TexAlignment alignment) } } + /// Инициализирует новый пустой вертикальный бокс public VerticalBox() { } + /// + /// Добавляет дочерний бокс и обновляет размеры контейнера + /// + /// Бокс для добавления public override void Add(Box box) { base.Add(box); @@ -47,6 +67,11 @@ public override void Add(Box box) RecalculateWidth(box); } + /// + /// Добавляет дочерний бокс по указанной позиции и обновляет размеры контейнера + /// + /// Позиция для добавления бокса + /// Бокс для добавления public override void Add(int position, Box box) { base.Add(position, box); @@ -61,6 +86,10 @@ public override void Add(int position, Box box) RecalculateWidth(box); } + /// + /// Пересчитывает ширину контейнера на основе добавленного бокса + /// + /// Добавленный бокс private void RecalculateWidth(Box box) { _LeftMostPos = Math.Min(_LeftMostPos, box.Shift); @@ -68,6 +97,13 @@ private void RecalculateWidth(Box box) Width = _RightMostPos - _LeftMostPos; } + /// + /// Рисует содержимое контейнера и его дочерних элементов + /// + /// Контекст рисования + /// Масштаб рисования + /// Позиция X + /// Позиция Y public override void Draw(DrawingContext Context, double scale, double x, double y) { base.Draw(Context, scale, x, y); @@ -81,6 +117,10 @@ public override void Draw(DrawingContext Context, double scale, double x, double } } + /// + /// Получает ID шрифта для последнего добавленного элемента + /// + /// ID шрифта public override int GetLastFontId() { var font_id = TexFontUtilities.NoFontId; From 479127885ae09f7d98b30767bd02c0ae86af1db0 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 03:12:02 +0300 Subject: [PATCH 27/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20prompt=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D0=B8=D0=B7=D0=B8=D0=B8=20=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D1=83=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В проект добавлен файл auditAndDocumentModule.prompt.md с подробной инструкцией по комплексной ревизии, документированию, локализации комментариев, проверке качества кода и управлению версионированием для модулей/пространств имён. Инструкция включает чек-лист и оформлена на русском языке. --- .../prompts/auditAndDocumentModule.prompt.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/prompts/auditAndDocumentModule.prompt.md diff --git a/.github/prompts/auditAndDocumentModule.prompt.md b/.github/prompts/auditAndDocumentModule.prompt.md new file mode 100644 index 0000000..28e4e12 --- /dev/null +++ b/.github/prompts/auditAndDocumentModule.prompt.md @@ -0,0 +1,76 @@ +--- +name: auditAndDocumentModule +--- + +Проведите комплексную ревизию и документирование указанного модуля или пространства имён. Выполните следующие задачи: + +## 1. Создание архитектурной документации + +Напишите полный README.md файл, включающий: +- **Основные возможности** - список ключевых функций модуля +- **Архитектура** - описание всех основных компонентов с диаграммами/схемами +- **Основной цикл обработки** - блок-схема или текстовое описание потока данных +- **Примеры использования** - базовые и продвинутые примеры с кодом +- **Поддерживаемая функциональность** - список команд, методов, свойств +- **Конфигурация** - описание файлов и параметров конфигурации +- **Точки расширения** - как добавлять новые функции +- **Производительность** - рекомендации по оптимизации +- **Обработка ошибок** - описание исключений и их причин + +## 2. Добавление XML-документации + +Для всех публичных типов и методов добавьте: +- `` - краткое описание (одна строка) +- `` - подробное объяснение (многострочное) +- `` - описание параметров с типами +- `` - описание возвращаемого значения +- `` - выбрасываемые исключения +- `` - практические примеры использования в блоке `` + +## 3. Локализация комментариев + +Переведите все комментарии в коде на целевой язык (указать в инструкции): +- Встроенные однострочные комментарии +- Многострочные блочные комментарии +- Сохраняйте техническую терминологию в том же языке (CamelCase имена, аббревиатуры) + +## 4. Ревизия качества кода + +Проанализируйте код на: +- Ошибки компиляции и синтаксические проблемы +- Потенциальные ошибки в реализации +- Нарушения принципов SOLID +- Проблемы с обработкой исключений +- Неиспользуемый код +- Архитектурные проблемы + +## 5. Управление версионированием (Git) + +Выполните следующий рабочий процесс: +- Создайте feature-ветку (например, `feature/module-review-and-docs`) +- Добавьте и закоммитьте все изменения с описательными сообщениями +- Переключитесь на целевую ветку (обычно `dev`) +- Выполните merge feature-ветки в целевую ветку +- Удалите временную feature-ветку +- Проверьте успешную интеграцию + +## 6. Итоговый отчёт + +Создайте отчёт, включающий: +- Список всех модифицированных файлов +- Количество добавленных и удаленных строк +- Краткое описание каждого изменения +- Выявленные проблемы (если есть) +- Рекомендации по дальнейшему улучшению +- Git информацию (ветка, commit ID, статистика merge) + +## Контрольный список + +- [ ] Проект успешно компилируется +- [ ] README.md создан и содержит полную документацию +- [ ] XML-комментарии добавлены ко всем публичным членам +- [ ] Все комментарии переведены на целевой язык +- [ ] Примеры кода заключены в блоки CDATA +- [ ] Проведена ревизия качества кода +- [ ] Git операции выполнены успешно +- [ ] Итоговый отчёт составлен From 62817788a47f4bde7de341cfe298d993ec1365ae Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 03:25:29 +0300 Subject: [PATCH 28/33] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B6=D0=B8=D0=B7=D0=BD=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=BC=20=D1=86=D0=B8=D0=BA=D0=BB=D0=BE=D0=BC=20=D1=85=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B5=D0=B5=D0=B2,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен HashSet для отслеживания привязок в GlobalHotKeysCollection и методы для корректного управления жизненным циклом GlobalHotKeyBinding при изменениях коллекции. Реализовано удаление идентификаторов хоткеев из внутренней таблицы при их снятии регистрации. Добавлена очистка обработчика сообщений при отсутствии хоткеев. В проект добавлен README.MD с примерами использования и описанием компонентов. --- .../HotKeys/GlobalHotKeysCollection.cs | 83 +++++++++++++++++-- .../AttachedProperties/HotKeys/README.MD | 52 ++++++++++++ 2 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 MathCore.WPF/AttachedProperties/HotKeys/README.MD diff --git a/MathCore.WPF/AttachedProperties/HotKeys/GlobalHotKeysCollection.cs b/MathCore.WPF/AttachedProperties/HotKeys/GlobalHotKeysCollection.cs index b12fdba..182be1e 100644 --- a/MathCore.WPF/AttachedProperties/HotKeys/GlobalHotKeysCollection.cs +++ b/MathCore.WPF/AttachedProperties/HotKeys/GlobalHotKeysCollection.cs @@ -45,6 +45,8 @@ internal static (Keys Key, ModifierKeys Modifer) GetModifiers(Keys Key, Modifier return (key, modifers); } + private readonly HashSet _Bindings = []; + /// Инициализация новой коллекции горячих клавиш public GlobalHotKeysCollection() => ((INotifyCollectionChanged)this).CollectionChanged += OnCollectionChanged; @@ -57,22 +59,62 @@ private void OnCollectionChanged(object? Sender, NotifyCollectionChangedEventArg switch (e.Action) { case NotifyCollectionChangedAction.Add: - if (e.NewItems?.Cast() is { } added) - foreach (var item in e.NewItems.Cast()) item.SetHost(this); + AddBindings(e.NewItems); break; case NotifyCollectionChangedAction.Remove: - if (e.OldItems?.Cast() is { } removed) - foreach (var item in removed) item.SetHost(null); + RemoveBindings(e.OldItems); break; case NotifyCollectionChangedAction.Replace: - if (e.NewItems?.Cast() is { } @new) - foreach (var item in @new) item.SetHost(this); - if (e.OldItems?.Cast() is { } old) - foreach (var item in old) item.SetHost(null); + AddBindings(e.NewItems); + RemoveBindings(e.OldItems); + break; + case NotifyCollectionChangedAction.Reset: + ResetBindings(); break; } } + private void AddBindings(IList? Items) + { + if (Items is null) return; + + foreach (GlobalHotKeyBinding item in Items) + { + item.SetHost(this); + _Bindings.Add(item); + } + } + + private void RemoveBindings(IList? Items) + { + if (Items is null) return; + + foreach (GlobalHotKeyBinding item in Items) + { + item.SetHost(null); + _Bindings.Remove(item); + } + } + + private void ResetBindings() + { + var current_bindings = new HashSet(this); + + foreach (var binding in _Bindings) + { + if (current_bindings.Contains(binding)) continue; + + binding.SetHost(null); + Unregister(binding); + } + + foreach (var binding in current_bindings) + binding.SetHost(this); + + _Bindings.Clear(); + _Bindings.UnionWith(current_bindings); + } + protected override Freezable CreateInstanceCore() { var collection = new GlobalHotKeysCollection(); @@ -131,6 +173,24 @@ private static ushort GetHotKeyId((Keys Key, ModifierKeys Modifer) Key) return key_id; } + private static void RemoveHotKeyId(ushort KeyId) + { + (Keys Key, ModifierKeys Modifer) key_to_remove = default; + var has_key = false; + + foreach (var pair in __HotKeyIds) + { + if (pair.Value != KeyId) continue; + + key_to_remove = pair.Key; + has_key = true; + break; + } + + if (has_key) + __HotKeyIds.Remove(key_to_remove); + } + /// Таблица идентификаторов зарегистрированных горячих клавиш private static readonly HashSet __RegisteredHotKeys = []; @@ -190,8 +250,15 @@ private static void UnregisterHotKey(ushort KeyId) User32.UnregisterHotKey(IntPtr.Zero, KeyId); __RegisteredHotKeys.Remove(KeyId); + RemoveHotKeyId(KeyId); Kernel32.GlobalDeleteAtom(KeyId); + if (__HotKeyBindings is { Count: 0 }) + { + ComponentDispatcher.ThreadPreprocessMessage -= OnFilterMessage; + __HotKeyBindings = null; + } + Debug.WriteLine("Hot key {0} unregistered at system"); } diff --git a/MathCore.WPF/AttachedProperties/HotKeys/README.MD b/MathCore.WPF/AttachedProperties/HotKeys/README.MD new file mode 100644 index 0000000..bfe1dc0 --- /dev/null +++ b/MathCore.WPF/AttachedProperties/HotKeys/README.MD @@ -0,0 +1,52 @@ +# GlobalHotKeys + +Компоненты для регистрации глобальных горячих клавиш в WPF с привязкой к `ICommand`. + +## Состав + +- `GlobalHotKeysCollection` — коллекция горячих клавиш, подключаемая через attached‑свойство `UI.HotKeys` +- `GlobalHotKeyBinding` — описание одной горячей клавиши (клавиша, модификаторы, команда, параметр) + +## Использование в XAML + +```xml + + + + + + + + +``` + +## Использование в коде + +```csharp +var hot_keys = UI.GetHotKeys(this); + +hot_keys.Add(new GlobalHotKeyBinding +{ + Key = Keys.F5, + Command = RefreshCommand +}); + +hot_keys.Add(new GlobalHotKeyBinding +{ + Key = Keys.S, + Modifer = ModifierKeys.Control, + Command = SaveCommand, + CommandParameter = "autosave" +}); +``` + +## Примечания + +- Коллекция автоматически освобождает зарегистрированные хоткеи при выгрузке `Window` +- Горячие клавиши обрабатываются на уровне системных сообщений, поэтому работают вне фокуса элементов управления From 174943f8f687137a1b49c0c3ac561eb680679ba7 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 17:11:34 +0300 Subject: [PATCH 29/33] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0?= =?UTF-8?q?=20Mapper=20=D0=B8=20=D0=B5=D0=B3=D0=BE=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Расширены XML-комментарии для класса Mapper, его свойств и методов. Добавлены подробные описания, примеры использования в XAML и C#, а также пояснения по работе коэффициента масштабирования. Логика работы класса не изменялась. --- MathCore.WPF/Converters/Mapper.cs | 71 ++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/MathCore.WPF/Converters/Mapper.cs b/MathCore.WPF/Converters/Mapper.cs index 8515ba3..0fd04dd 100644 --- a/MathCore.WPF/Converters/Mapper.cs +++ b/MathCore.WPF/Converters/Mapper.cs @@ -5,7 +5,34 @@ namespace MathCore.WPF.Converters; -/// Масштабирует значение из одного диапазона в другой +/// Масштабирует значение из одного диапазона в другой с помощью линейной интерполяции +/// +/// Конвертер выполняет линейное преобразование значения из исходного диапазона [MinValue, MaxValue] +/// в целевой диапазон [MinScale, MaxScale]. Используется в WPF для привязки данных с автоматическим +/// масштабированием значений. +/// +/// +/// Пример использования в XAML для масштабирования значения температуры от 0-100°C в диапазон 0-360° для поворота стрелки: +/// +/// +/// +/// ]]> +/// Пример использования в C#: +/// +/// [ValueConversion(typeof(double), typeof(double))] [MarkupExtensionReturnType(typeof(Mapper))] public class Mapper() : DoubleValueConverter @@ -15,6 +42,8 @@ public class Mapper() : DoubleValueConverter private double _MinScale; /// Минимальное значение масштаба + /// Минимальное значение целевого диапазона. По умолчанию 0 + /// Устанавливает нижнюю границу выходного диапазона. При изменении автоматически пересчитывается коэффициент масштабирования public double MinScale { get => _MinScale; @@ -28,6 +57,8 @@ public double MinScale private double _MaxScale = 1; /// Максимальное значение масштаба + /// Максимальное значение целевого диапазона. По умолчанию 1 + /// Устанавливает верхнюю границу выходного диапазона. При изменении автоматически пересчитывается коэффициент масштабирования public double MaxScale { get => _MaxScale; @@ -41,6 +72,8 @@ public double MaxScale private double _MinValue; /// Минимальное значение исходного диапазона + /// Минимальное значение входного диапазона. По умолчанию 0 + /// Устанавливает нижнюю границу входного диапазона. При изменении автоматически пересчитывается коэффициент масштабирования public double MinValue { get => _MinValue; @@ -54,6 +87,8 @@ public double MinValue private double _MaxValue = 1; /// Максимальное значение исходного диапазона + /// Максимальное значение входного диапазона. По умолчанию 1 + /// Устанавливает верхнюю границу входного диапазона. При изменении автоматически пересчитывается коэффициент масштабирования public double MaxValue { get => _MaxValue; @@ -66,18 +101,44 @@ public double MaxValue private void RecalculateK() { - var denom = (_MaxValue - _MinValue); + var denom = _MaxValue - _MinValue; _k = denom == 0 ? 0 : (_MaxScale - _MinScale) / denom; } - /// + /// Преобразует значение из исходного диапазона в целевой диапазон + /// Значение для преобразования + /// Дополнительный параметр, если указан, используется вместо v. По умолчанию null + /// Значение, масштабированное в целевой диапазон [MinScale, MaxScale] + /// + /// Использует формулу линейной интерполяции: result = (x - MinValue) * k + MinScale, + /// где k = (MaxScale - MinScale) / (MaxValue - MinValue) + /// + /// + /// + /// protected override double Convert(double v, double? p = null) { - var x = (p ?? v); + var x = p ?? v; var result = (x - _MinValue) * _k + _MinScale; return result; } - /// + /// Выполняет обратное преобразование значения из целевого диапазона в исходный диапазон + /// Значение из целевого диапазона для обратного преобразования + /// Дополнительный параметр (не используется). По умолчанию null + /// Значение, преобразованное обратно в исходный диапазон [MinValue, MaxValue], или double.NaN если коэффициент масштабирования равен нулю + /// + /// Использует обратную формулу: result = (x - MinScale) / k + MinValue. + /// Если коэффициент k равен нулю (входной диапазон имеет нулевую длину), возвращается double.NaN + /// + /// + /// + /// protected override double ConvertBack(double x, double? p = null) => _k == 0 ? double.NaN : (x - _MinScale) / _k + _MinValue; } \ No newline at end of file From 17b54b27f068f73382ab2d94abd725785757075e Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 17:13:53 +0300 Subject: [PATCH 30/33] =?UTF-8?q?=D0=AD=D0=BA=D1=81=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D1=82=D1=8B=20=D1=81=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BB=D1=8F=D0=BC=D0=B8=20=D0=B8=20=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BC=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=BC?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=B0=D0=BC=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BA=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BC=D0=B8=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MathCore.WPF.WindowTest/TestWindows8.xaml | 88 +++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml index fc01484..6bb1a8d 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml @@ -5,19 +5,97 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:MathCore.WPF.WindowTest.ViewModels" xmlns:sh="clr-namespace:MathCore.WPF.Shapes;assembly=MathCore.WPF" + xmlns:s="clr-namespace:System;assembly=System.Runtime" Title="{Binding Title}" + Background="Black" + Foreground="WhiteSmoke" Width="800" Height="450"> + - - + + + 123 + + 60 + 150 + + 0 + 20 + + From e280448ee0580089e564b6cb48ec86d73acaa87d Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 18:44:39 +0300 Subject: [PATCH 31/33] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20MapperConverter=20(Freezable)=20=D0=B8=20MarkupE?= =?UTF-8?q?xtension=20MapperF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены классы MapperConverter (Freezable, IValueConverter с поддержкой DependencyProperty и привязок) и MapperF (MarkupExtension для быстрого создания конвертера в XAML). Обновлена документация с примерами использования. Добавлены подробные модульные тесты для новых конвертеров, включая проверки работы с привязками, изменением свойств и независимостью экземпляров. --- MathCore.WPF/Converters/MapperConverter.cs | 170 ++++++++++++++ MathCore.WPF/Converters/MapperF.cs | 62 +++++ MathCore.WPF/Converters/README.md | 74 +++++- .../Converters/MapperTests.cs | 218 ++++++++++++++++++ 4 files changed, 521 insertions(+), 3 deletions(-) create mode 100644 MathCore.WPF/Converters/MapperConverter.cs create mode 100644 MathCore.WPF/Converters/MapperF.cs diff --git a/MathCore.WPF/Converters/MapperConverter.cs b/MathCore.WPF/Converters/MapperConverter.cs new file mode 100644 index 0000000..ddb016f --- /dev/null +++ b/MathCore.WPF/Converters/MapperConverter.cs @@ -0,0 +1,170 @@ +#nullable enable + +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +using MathCore.WPF.Converters.Base; + +namespace MathCore.WPF.Converters; + +/// Масштабирует значение из одного диапазона в другой с помощью линейной интерполяции. Версия на основе Freezable для использования в XAML с наследованием DataContext +/// +/// Это версия класса Mapper, которая наследует от Freezable вместо MarkupExtension. +/// Freezable позволяет конвертеру получать DataContext из дерева XAML, +/// что недоступно при использовании MarkupExtension напрямую. +/// +/// Конвертер выполняет линейное преобразование значения из исходного диапазона [MinValue, MaxValue] +/// в целевой диапазон [MinScale, MaxScale]. +/// +/// Все свойства реализованы как свойства-зависимости (DependencyProperty) для полной поддержки привязок WPF. +/// +/// +/// Пример использования в XAML внутри ресурсов элемента: +/// +/// +/// +/// +/// +/// ]]> +/// +/// Пример использования с наследованием DataContext через Binding: +/// +/// +/// +/// +/// +/// +/// ]]> +/// +[ValueConversion(typeof(double), typeof(double))] +public sealed class MapperConverter : Freezable, IValueConverter +{ + private double _k = 1; + + /// Минимальное значение масштаба (свойство-зависимость) + public static readonly DependencyProperty MinScaleProperty = DependencyProperty.Register( + nameof(MinScale), + typeof(double), + typeof(MapperConverter), + new PropertyMetadata(0d, OnRangeChanged)); + + /// Минимальное значение масштаба + /// Минимальное значение целевого диапазона. По умолчанию 0 + public double MinScale + { + get => (double)GetValue(MinScaleProperty); + set => SetValue(MinScaleProperty, value); + } + + /// Максимальное значение масштаба (свойство-зависимость) + public static readonly DependencyProperty MaxScaleProperty = DependencyProperty.Register( + nameof(MaxScale), + typeof(double), + typeof(MapperConverter), + new PropertyMetadata(1d, OnRangeChanged)); + + /// Максимальное значение масштаба + /// Максимальное значение целевого диапазона. По умолчанию 1 + public double MaxScale + { + get => (double)GetValue(MaxScaleProperty); + set => SetValue(MaxScaleProperty, value); + } + + /// Минимальное значение исходного диапазона (свойство-зависимость) + public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register( + nameof(MinValue), + typeof(double), + typeof(MapperConverter), + new PropertyMetadata(0d, OnRangeChanged)); + + /// Минимальное значение исходного диапазона + /// Минимальное значение входного диапазона. По умолчанию 0 + public double MinValue + { + get => (double)GetValue(MinValueProperty); + set => SetValue(MinValueProperty, value); + } + + /// Максимальное значение исходного диапазона (свойство-зависимость) + public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register( + nameof(MaxValue), + typeof(double), + typeof(MapperConverter), + new PropertyMetadata(1d, OnRangeChanged)); + + /// Максимальное значение исходного диапазона + /// Максимальное значение входного диапазона. По умолчанию 1 + public double MaxValue + { + get => (double)GetValue(MaxValueProperty); + set => SetValue(MaxValueProperty, value); + } + + /// Обработчик изменения свойств диапазона для пересчёта коэффициента масштабирования + private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is MapperConverter mapper) + mapper.RecalculateK(); + } + + /// Пересчитывает коэффициент масштабирования на основе текущих диапазонов + private void RecalculateK() + { + var denom = MaxValue - MinValue; + _k = denom == 0 ? 0 : (MaxScale - MinScale) / denom; + } + + /// Преобразует значение из исходного диапазона в целевой диапазон + /// Значение для преобразования + /// Целевой тип (должен быть double) + /// Дополнительный параметр преобразования (не используется) + /// Культура для преобразования + /// Значение, масштабированное в целевой диапазон [MinScale, MaxScale], или double.NaN если преобразование не удалось + /// + /// Метод преобразует входное значение, используя формулу линейной интерполяции: + /// result = (x - MinValue) * k + MinScale, где k = (MaxScale - MinScale) / (MaxValue - MinValue) + /// + /// Входное значение автоматически преобразуется в double с использованием логики базового класса DoubleValueConverter. + /// + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (!DoubleValueConverter.TryConvertToDouble(value, culture, out var x)) + return double.NaN; + + var result = (x - MinValue) * _k + MinScale; + return result; + } + + /// Выполняет обратное преобразование значения из целевого диапазона в исходный диапазон + /// Значение из целевого диапазона для обратного преобразования + /// Целевой тип (должен быть double) + /// Дополнительный параметр преобразования (не используется) + /// Культура для преобразования + /// Значение, преобразованное обратно в исходный диапазон [MinValue, MaxValue], или double.NaN если коэффициент масштабирования равен нулю или преобразование не удалось + /// + /// Метод преобразует входное значение, используя обратную формулу: + /// result = (x - MinScale) / k + MinValue + /// + /// Если коэффициент k равен нулю (входной диапазон имеет нулевую длину), возвращается double.NaN. + /// + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (!DoubleValueConverter.TryConvertToDouble(value, culture, out var x)) + return double.NaN; + + return _k == 0 ? double.NaN : (x - MinScale) / _k + MinValue; + } + + /// Создаёт копию текущего объекта Freezable + /// Копия текущего объекта + protected override Freezable CreateInstanceCore() => new MapperConverter(); +} diff --git a/MathCore.WPF/Converters/MapperF.cs b/MathCore.WPF/Converters/MapperF.cs new file mode 100644 index 0000000..b193683 --- /dev/null +++ b/MathCore.WPF/Converters/MapperF.cs @@ -0,0 +1,62 @@ +#nullable enable + +using System.Windows.Markup; + +namespace MathCore.WPF.Converters; + +/// Расширение разметки для удобного создания экземпляров MapperConverter в XAML +/// +/// Это расширение разметки предоставляет краткий синтаксис для создания MapperConverter +/// при использовании в привязках, которые не требуют наследования DataContext. +/// +/// Используйте это расширение когда все параметры известны на момент разработки +/// и не должны быть привязаны к DataContext. +/// +/// Для случаев, когда параметры должны быть привязаны к источникам данных, +/// используйте MapperConverter напрямую как элемент ресурсов с привязками. +/// +/// +/// Пример использования в XAML для простого статического маппера: +/// +/// ]]> +/// +/// Пример в коде на C#: +/// +/// +[MarkupExtensionReturnType(typeof(MapperConverter))] +public class MapperF : MarkupExtension +{ + /// Минимальное значение исходного диапазона + public double MinValue { get; set; } + + /// Максимальное значение исходного диапазона + public double MaxValue { get; set; } = 1; + + /// Минимальное значение целевого диапазона (масштаба) + public double MinScale { get; set; } + + /// Максимальное значение целевого диапазона (масштаба) + public double MaxScale { get; set; } = 1; + + /// Возвращает новый экземпляр MapperConverter с установленными параметрами + /// Поставщик сервисов (не используется) + /// Новый экземпляр MapperConverter с заданными значениями диапазонов + /// + /// Каждый вызов ProvideValue создаёт новый независимый экземпляр MapperConverter. + /// Это гарантирует, что каждая привязка получает свой экземпляр конвертера. + /// + public override object ProvideValue(IServiceProvider? serviceProvider) => + new MapperConverter + { + MinValue = MinValue, + MaxValue = MaxValue, + MinScale = MinScale, + MaxScale = MaxScale + }; +} diff --git a/MathCore.WPF/Converters/README.md b/MathCore.WPF/Converters/README.md index 042f54f..d83ef6e 100644 --- a/MathCore.WPF/Converters/README.md +++ b/MathCore.WPF/Converters/README.md @@ -167,6 +167,63 @@ public class Addition(double P) : SimpleDoubleValueConverter(P, --- +### `MapperConverter` +**Назначение**: Freezable-версия конвертера Mapper для использования в XAML с наследованием DataContext + +**Параметры**: +- `MinScale`, `MaxScale` — диапазон экранных значений (DependencyProperty) +- `MinValue`, `MaxValue` — диапазон физических значений (DependencyProperty) + +**XAML использование в ресурсах с привязками**: +```xaml + + + + + +``` + +**XAML использование с динамическими привязками**: +```xaml + +``` + +**Пример**: Значение `50` из диапазона `[0, 100]` → `180` в диапазон `[0, 360]` + +**Особенности**: +- Наследует от `Freezable`, что позволяет получать `DataContext` из дерева XAML +- Все свойства реализованы как `DependencyProperty` для полной поддержки привязок +- Поддерживает обратное преобразование (`ConvertBack`) +- Пересчитывает коэффициент масштабирования автоматически при изменении любого параметра диапазона + +--- + +### `MapperF` +**Назначение**: Расширение разметки для быстрого создания MapperConverter в привязках + +**Параметры**: +- `MinValue`, `MaxValue` — диапазон физических значений +- `MinScale`, `MaxScale` — диапазон экранных значений + +**XAML использование в привязке (простой синтаксис)**: +```xaml + +``` + +**Пример**: Температура `50°C` → поворот на `180°` + +**Особенности**: +- Упрощённый синтаксис для статических конфигураций +- Создаёт новый независимый экземпляр `MapperConverter` при каждом вызове +- Используйте `MapperConverter` напрямую в ресурсах, если нужны динамические привязки параметров + +--- + ### `Inverse` **Назначение**: Вычисляет значение по формуле `f(x) = Parameter / x` @@ -294,7 +351,7 @@ public class Addition(double P) : SimpleDoubleValueConverter(P, --- ### `GreaterThanOrEqual` -**Назначение**: Проверяет, больше или равно ли значение параметру +**Nazначение**: Проверяет, больше или равно ли значение параметру **Параметры**: `Value` (double) — пороговое значение @@ -1033,9 +1090,20 @@ public class Addition(double P) : SimpleDoubleValueConverter(P, --- ### `Mapper` -**Назначение**: Преобразует значения между диапазонами (масштабирование) +**Назначение**: Преобразует физическое значение в экранное значение (масштабирование диапазонов) -Описано выше в арифметических конвертерах. +**Параметры**: +- `MinScale`, `MaxScale` — диапазон экранных значений +- `MinValue`, `MaxValue` — диапазон физических значений + +**XAML использование**: +```xaml +{Binding Value, Converter={Mapper MinScale=-200, MaxScale=400, MinValue=-5, MaxValue=5}} +``` + +**Пример**: Значение `-5` → `-200`, значение `5` → `400` + +**Особенности**: Линейное масштабирование между диапазонами --- diff --git a/Tests/MathCore.WPF.Tests/Converters/MapperTests.cs b/Tests/MathCore.WPF.Tests/Converters/MapperTests.cs index 37d889a..5c56eb2 100644 --- a/Tests/MathCore.WPF.Tests/Converters/MapperTests.cs +++ b/Tests/MathCore.WPF.Tests/Converters/MapperTests.cs @@ -9,6 +9,7 @@ namespace MathCore.WPF.Tests.Converters; public class MapperTests { [TestMethod] + /// Проверка преобразования значения конвертером Mapper из одного диапазона в другой public void Convert() { const double min_scale = 15; @@ -35,6 +36,7 @@ public void Convert() } [TestMethod] + /// Проверка обратного преобразования конвертером Mapper из целевого диапазона в исходный public void ConvertBack() { const double min_scale = 15; @@ -59,4 +61,220 @@ public void ConvertBack() Assert.That.Value(at_scale_max).IsEqual(max_value); Assert.That.Value(at_scale_360).IsEqual(229.54545454545456); } +} + +[TestClass] +public class MapperConverterTests +{ + [TestMethod] + /// Проверка базового преобразования значения конвертером MapperConverter с заданными диапазонами + public void Convert_WithBasicRange_ReturnsCorrectValue() + { + const double min_scale = 15; + const double max_scale = 345; + const double min_value = 10; + const double max_value = 220; + + var converter = new MapperConverter + { + MinScale = min_scale, + MaxScale = max_scale, + MinValue = min_value, + MaxValue = max_value + }; + + var result_at_0 = (double)converter.Convert(0, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_min_value = converter.Convert(min_value, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_max_value = converter.Convert(max_value, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_middle = (double)converter.Convert(0.45454545454545, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value(result_at_0).IsEqual(-0.7142857142857135); + Assert.That.Value(result_at_min_value).IsEqual(min_scale); + Assert.That.Value(result_at_max_value).IsEqual(max_scale); + Assert.That.Value(result_at_middle).IsEqual(0, 1e-14); + } + + [TestMethod] + /// Проверка обратного преобразования конвертером MapperConverter из целевого диапазона в исходный + public void ConvertBack_WithBasicRange_ReturnsCorrectValue() + { + const double min_scale = 15; + const double max_scale = 345; + const double min_value = 10; + const double max_value = 220; + + var converter = new MapperConverter + { + MinScale = min_scale, + MaxScale = max_scale, + MinValue = min_value, + MaxValue = max_value + }; + + var result_at_scale_0 = (double)converter.ConvertBack(0, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_scale_min = converter.ConvertBack(min_scale, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_scale_max = converter.ConvertBack(max_scale, typeof(double), null, CultureInfo.CurrentCulture); + var result_at_scale_360 = (double)converter.ConvertBack(360, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value(result_at_scale_0).IsEqual(0.45454545454545, 5.1e-15); + Assert.That.Value(result_at_scale_min).IsEqual(min_value); + Assert.That.Value(result_at_scale_max).IsEqual(max_value); + Assert.That.Value(result_at_scale_360).IsEqual(229.54545454545456); + } + + [TestMethod] + /// Проверка преобразования значения конвертером MapperConverter при нулевом диапазоне входных значений + public void ConvertBack_WithZeroRange_ReturnsNaN() + { + var converter = new MapperConverter + { + MinValue = 10, + MaxValue = 10, // Нулевой диапазон + MinScale = 0, + MaxScale = 100 + }; + + var result = converter.ConvertBack(50, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value((double)result).IsNaN(); + } + + [TestMethod] + /// Проверка преобразования строкового значения конвертером MapperConverter в диапазон значений + public void Convert_WithStringInput_ConvertsToDouble() + { + var converter = new MapperConverter + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var result = (double)converter.Convert("50", typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value(result).IsEqual(180); + } + + [TestMethod] + /// Проверка преобразования значения null конвертером MapperConverter + public void Convert_WithNullInput_ReturnsNaN() + { + var converter = new MapperConverter + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var result = converter.Convert(null, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value((double)result).IsNaN(); + } + + [TestMethod] + /// Проверка изменения свойств конвертера MapperConverter и пересчёта коэффициента масштабирования + public void PropertyChange_UpdatesDependencyPropertyValue() + { + var converter = new MapperConverter + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var result_50_before = (double)converter.Convert(50, typeof(double), null, CultureInfo.CurrentCulture); + + converter.MaxScale = 180; // Изменяем максимальный масштаб + + var result_50_after = (double)converter.Convert(50, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value(result_50_before).IsEqual(180); // 50 из [0,100] в [0,360] = 180 + Assert.That.Value(result_50_after).IsEqual(90); // 50 из [0,100] в [0,180] = 90 + } +} + +[TestClass] +public class MapperFTests +{ + [TestMethod] + /// Проверка создания экземпляра MapperConverter через расширение разметки MapperF + public void ProvideValue_CreatesMapperConverterInstance() + { + var extension = new MapperF + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var converter = extension.ProvideValue(null); + + Assert.IsNotNull(converter); + Assert.IsInstanceOfType(converter, typeof(MapperConverter)); + } + + [TestMethod] + /// Проверка правильного копирования параметров расширения разметки MapperF в созданный конвертер + public void ProvideValue_ConverterHasCorrectParameters() + { + const double min_value = 10; + const double max_value = 220; + const double min_scale = 15; + const double max_scale = 345; + + var extension = new MapperF + { + MinValue = min_value, + MaxValue = max_value, + MinScale = min_scale, + MaxScale = max_scale + }; + + var converter = (MapperConverter)extension.ProvideValue(null); + + Assert.That.Value(converter.MinValue).IsEqual(min_value); + Assert.That.Value(converter.MaxValue).IsEqual(max_value); + Assert.That.Value(converter.MinScale).IsEqual(min_scale); + Assert.That.Value(converter.MaxScale).IsEqual(max_scale); + } + + [TestMethod] + /// Проверка преобразования значения через конвертер, созданный расширением разметки MapperF + public void ProvideValue_CreatedConverterWorks() + { + var extension = new MapperF + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var converter = (IValueConverter)extension.ProvideValue(null); + var result = (double)converter.Convert(50, typeof(double), null, CultureInfo.CurrentCulture); + + Assert.That.Value(result).IsEqual(180); + } + + [TestMethod] + /// Проверка создания независимых экземпляров конвертера при нескольких вызовах расширения разметки MapperF + public void ProvideValue_CreatesIndependentInstances() + { + var extension = new MapperF + { + MinValue = 0, + MaxValue = 100, + MinScale = 0, + MaxScale = 360 + }; + + var converter1 = extension.ProvideValue(null); + var converter2 = extension.ProvideValue(null); + + Assert.AreNotSame(converter1, converter2); + } } \ No newline at end of file From 946e1e695c33738dfbe712b0cdcfda064f607d48 Mon Sep 17 00:00:00 2001 From: Infarh Date: Sun, 15 Feb 2026 21:26:14 +0300 Subject: [PATCH 32/33] =?UTF-8?q?=D0=AD=D0=BA=D1=81=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MathCore.WPF/MathCore.WPF.csproj | 11 ---- .../MathCore.WPF.Tests.csproj | 2 +- .../MathCore.WPF.WindowTest/TestWindows8.xaml | 60 +++++++------------ 3 files changed, 22 insertions(+), 51 deletions(-) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index 3d31890..fc19078 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -48,12 +48,6 @@ - - - - - - @@ -65,11 +59,6 @@ - - - - - diff --git a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj index 4751f19..dcb28ac 100644 --- a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj +++ b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj @@ -13,7 +13,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml index 6bb1a8d..e0d71b5 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindows8.xaml @@ -6,6 +6,7 @@ xmlns:vm="clr-namespace:MathCore.WPF.WindowTest.ViewModels" xmlns:sh="clr-namespace:MathCore.WPF.Shapes;assembly=MathCore.WPF" xmlns:s="clr-namespace:System;assembly=System.Runtime" + xmlns:conv="clr-namespace:MathCore.WPF.Converters;assembly=MathCore.WPF" Title="{Binding Title}" Background="Black" Foreground="WhiteSmoke" @@ -14,11 +15,17 @@