Files
SQLVision/SQLVision.Visualizers/Factories/ControlFactory.cs
2026-01-05 00:37:54 +03:00

599 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using SQLVision.Core.Enums;
using SQLVision.Core.Interfaces;
using SQLVision.Core.Models;
using SQLVision.Visualizers.Interfaces;
using System.Text.Json;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace SQLVision.Visualizers.Factories;
public class ControlFactory : IControlFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ControlFactory> _logger;
private readonly ISqlExecutionService? _executionService;
public ControlFactory(
IServiceProvider serviceProvider,
ILogger<ControlFactory> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
_executionService = serviceProvider.GetService<ISqlExecutionService>();
}
public FrameworkElement CreateControl(ScriptParameter parameter, Action<object?> onValueChanged)
{
try
{
return parameter.Type switch
{
ParameterType.String => CreateTextBox(parameter, onValueChanged),
ParameterType.Integer => CreateNumberBox(parameter, onValueChanged),
ParameterType.Decimal => CreateNumberBox(parameter, onValueChanged, true),
ParameterType.DateTime => CreateDatePicker(parameter, onValueChanged),
ParameterType.Boolean => CreateCheckBox(parameter, onValueChanged),
ParameterType.Table => CreateComboBox(parameter, onValueChanged),
ParameterType.MultiSelect => CreateListView(parameter, onValueChanged),
ParameterType.Color => CreateColorPicker(parameter, onValueChanged),
ParameterType.File => CreateFilePicker(parameter, onValueChanged),
ParameterType.Json => CreateJsonEditor(parameter, onValueChanged),
_ => CreateTextBox(parameter, onValueChanged)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating control for parameter {Parameter}", parameter.Name);
return CreateFallbackControl(parameter, onValueChanged);
}
}
private FrameworkElement CreateTextBox(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var textBox = new TextBox
{
PlaceholderText = parameter.Watermark ?? $"Введите {parameter.EffectiveDisplayName.ToLower()}",
Text = parameter.DefaultValue?.ToString() ?? string.Empty
};
textBox.TextChanged += (s, e) => onValueChanged(textBox.Text);
stackPanel.Children.Add(header);
stackPanel.Children.Add(textBox);
return stackPanel;
}
private FrameworkElement CreateNumberBox(ScriptParameter parameter, Action<object?> onValueChanged, bool isDecimal = false)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var numberBox = new NumberBox
{
PlaceholderText = parameter.Watermark ?? $"Введите {parameter.EffectiveDisplayName.ToLower()}",
SmallChange = isDecimal ? 0.1 : 1,
LargeChange = isDecimal ? 1 : 10,
SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Inline,
AcceptsExpression = false
};
if (isDecimal)
{
numberBox.Value = Convert.ToDouble(parameter.DefaultValue ?? 0);
numberBox.ValueChanged += (s, e) => onValueChanged(e.NewValue);
}
else
{
numberBox.Value = Convert.ToInt32(parameter.DefaultValue ?? 0);
numberBox.ValueChanged += (s, e) => onValueChanged((int)e.NewValue);
}
if (parameter.ValidationRules != null)
{
if (parameter.ValidationRules.TryGetValue("min", out var min))
numberBox.Minimum = Convert.ToDouble(min);
if (parameter.ValidationRules.TryGetValue("max", out var max))
numberBox.Maximum = Convert.ToDouble(max);
}
stackPanel.Children.Add(header);
stackPanel.Children.Add(numberBox);
return stackPanel;
}
private FrameworkElement CreateDatePicker(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var datePicker = new DatePicker
{
Date = parameter.DefaultValue is DateTime defaultDate
? new DateTimeOffset(defaultDate)
: DateTimeOffset.Now
};
datePicker.DateChanged += (s, e) => onValueChanged(e.NewDate.DateTime);
if (parameter.ValidationRules != null)
{
if (parameter.ValidationRules.TryGetValue("mindate", out var minDate) &&
minDate is string minDateStr && DateTime.TryParse(minDateStr, out var minDateTime))
{
datePicker.MinYear = new DateTimeOffset(minDateTime);
}
if (parameter.ValidationRules.TryGetValue("maxdate", out var maxDate) &&
maxDate is string maxDateStr && DateTime.TryParse(maxDateStr, out var maxDateTime))
{
datePicker.MaxYear = new DateTimeOffset(maxDateTime);
}
}
stackPanel.Children.Add(header);
stackPanel.Children.Add(datePicker);
return stackPanel;
}
private FrameworkElement CreateCheckBox(ScriptParameter parameter, Action<object?> onValueChanged)
{
var checkBox = new CheckBox
{
Content = parameter.EffectiveDisplayName,
IsChecked = parameter.DefaultValue is bool defaultBool ? defaultBool : false
};
checkBox.Checked += (s, e) => onValueChanged(true);
checkBox.Unchecked += (s, e) => onValueChanged(false);
return checkBox;
}
private FrameworkElement CreateComboBox(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var comboBox = new ComboBox
{
PlaceholderText = parameter.Watermark ?? $"Выберите {parameter.EffectiveDisplayName.ToLower()}",
DisplayMemberPath = parameter.DisplayMember,
SelectedValuePath = parameter.ValueMember
};
LoadComboBoxDataAsync(comboBox, parameter).ConfigureAwait(false);
comboBox.SelectionChanged += (s, e) =>
{
if (comboBox.SelectedValue != null)
onValueChanged(comboBox.SelectedValue);
};
stackPanel.Children.Add(header);
stackPanel.Children.Add(comboBox);
return stackPanel;
}
private async Task LoadComboBoxDataAsync(ComboBox comboBox, ScriptParameter parameter)
{
if (string.IsNullOrEmpty(parameter.TableQuery) || _executionService == null)
return;
try
{
var connectionString = "Server=localhost;Database=master;Trusted_Connection=True;";
var dataTable = await _executionService.LoadComboBoxDataAsync(
parameter.TableQuery,
connectionString,
DatabaseProvider.SqlServer);
await comboBox.DispatcherQueue.EnqueueAsync(() =>
{
comboBox.ItemsSource = dataTable.DefaultView;
if (parameter.DefaultValue != null)
{
comboBox.SelectedValue = parameter.DefaultValue;
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading combo box data for {Parameter}", parameter.Name);
await comboBox.DispatcherQueue.EnqueueAsync(() =>
{
comboBox.ItemsSource = new List<string> { "Ошибка загрузки данных" };
comboBox.IsEnabled = false;
});
}
}
private FrameworkElement CreateListView(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var listView = new ListView
{
SelectionMode = ListViewSelectionMode.Multiple
};
// TODO: Загрузка данных для ListView
listView.SelectionChanged += (s, e) =>
{
var selectedItems = new List<object>();
foreach (var item in listView.SelectedItems)
{
selectedItems.Add(item);
}
onValueChanged(selectedItems);
};
stackPanel.Children.Add(header);
stackPanel.Children.Add(listView);
return stackPanel;
}
private FrameworkElement CreateColorPicker(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var colorPicker = new ColorPicker
{
ColorSpectrumShape = ColorSpectrumShape.Box,
IsMoreButtonVisible = true,
IsColorSliderVisible = true,
IsColorChannelTextInputVisible = true,
IsHexInputVisible = true
};
// Установка цвета по умолчанию с нашей реализацией ParseColor
if (parameter.DefaultValue is string defaultColor)
{
try
{
colorPicker.Color = ParseColor(defaultColor);
}
catch
{
// Если не удалось распарсить, оставляем цвет по умолчанию
_logger.LogWarning("Failed to parse color: {Color}", defaultColor);
}
}
colorPicker.ColorChanged += (s, e) =>
onValueChanged($"#{e.NewColor.R:X2}{e.NewColor.G:X2}{e.NewColor.B:X2}");
stackPanel.Children.Add(header);
stackPanel.Children.Add(colorPicker);
return stackPanel;
}
// Наша собственная реализация парсинга цвета (без Microsoft.Toolkit.Uwp)
private Windows.UI.Color ParseColor(string colorString)
{
if (string.IsNullOrEmpty(colorString))
return Windows.UI.Colors.Black;
// Удаляем #
colorString = colorString.Trim().TrimStart('#');
try
{
if (colorString.Length == 6)
{
// RRGGBB
var r = Convert.ToByte(colorString.Substring(0, 2), 16);
var g = Convert.ToByte(colorString.Substring(2, 2), 16);
var b = Convert.ToByte(colorString.Substring(4, 2), 16);
return Windows.UI.Color.FromArgb(255, r, g, b);
}
else if (colorString.Length == 8)
{
// AARRGGBB
var a = Convert.ToByte(colorString.Substring(0, 2), 16);
var r = Convert.ToByte(colorString.Substring(2, 2), 16);
var g = Convert.ToByte(colorString.Substring(4, 2), 16);
var b = Convert.ToByte(colorString.Substring(6, 2), 16);
return Windows.UI.Color.FromArgb(a, r, g, b);
}
else if (colorString.Length == 3)
{
// RGB
var r = Convert.ToByte(new string(colorString[0], 2), 16);
var g = Convert.ToByte(new string(colorString[1], 2), 16);
var b = Convert.ToByte(new string(colorString[2], 2), 16);
return Windows.UI.Color.FromArgb(255, r, g, b);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to parse color string: {ColorString}", colorString);
}
return Windows.UI.Colors.Black;
}
private FrameworkElement CreateFilePicker(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var contentStack = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 8
};
var textBox = new TextBox
{
PlaceholderText = "Путь к файлу",
Width = 200,
IsReadOnly = true
};
var button = new Button
{
Content = "Выбрать"
};
button.Click += async (s, e) =>
{
// Получаем активное окно для инициализации FileOpenPicker
var window = Application.Current as App;
var hwnd = IntPtr.Zero;
if (window?.MainWindow != null)
{
hwnd = WindowNative.GetWindowHandle(window.MainWindow);
}
var openPicker = new FileOpenPicker();
if (hwnd != IntPtr.Zero)
{
InitializeWithWindow.Initialize(openPicker, hwnd);
}
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
if (parameter.ValidationRules != null &&
parameter.ValidationRules.TryGetValue("extensions", out var extensions))
{
foreach (var ext in extensions.ToString()!.Split(','))
{
openPicker.FileTypeFilter.Add(ext.Trim());
}
}
else
{
openPicker.FileTypeFilter.Add("*");
}
var file = await openPicker.PickSingleFileAsync();
if (file != null)
{
textBox.Text = file.Path;
onValueChanged(file.Path);
}
};
contentStack.Children.Add(textBox);
contentStack.Children.Add(button);
stackPanel.Children.Add(header);
stackPanel.Children.Add(contentStack);
return stackPanel;
}
private FrameworkElement CreateJsonEditor(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var textBox = new TextBox
{
PlaceholderText = "Введите JSON",
AcceptsReturn = true,
TextWrapping = TextWrapping.Wrap,
Height = 100,
FontFamily = new FontFamily("Consolas")
};
if (parameter.DefaultValue != null)
{
try
{
textBox.Text = JsonSerializer.Serialize(
parameter.DefaultValue,
new JsonSerializerOptions { WriteIndented = true });
}
catch
{
textBox.Text = parameter.DefaultValue.ToString();
}
}
textBox.TextChanged += (s, e) =>
{
try
{
var json = JsonSerializer.Deserialize<JsonElement>(textBox.Text);
onValueChanged(json);
}
catch
{
// Игнорируем ошибки парсинга JSON
}
};
stackPanel.Children.Add(header);
stackPanel.Children.Add(textBox);
return stackPanel;
}
private FrameworkElement CreateFallbackControl(ScriptParameter parameter, Action<object?> onValueChanged)
{
var stackPanel = new StackPanel { Spacing = 4 };
var header = new TextBlock
{
Text = parameter.EffectiveDisplayName,
FontWeight = FontWeights.SemiBold
};
var textBox = new TextBox
{
Text = $"Ошибка создания контрола для типа {parameter.Type}",
IsReadOnly = true,
Foreground = new SolidColorBrush(Windows.UI.Colors.Red)
};
stackPanel.Children.Add(header);
stackPanel.Children.Add(textBox);
return stackPanel;
}
public void UpdateControlState(FrameworkElement control, ScriptParameter parameter, Dictionary<string, object?> currentValues)
{
// Обновление состояния контрола на основе зависимостей
if (!string.IsNullOrEmpty(parameter.DependsOn))
{
var isEnabled = CheckDependency(parameter, currentValues);
control.IsEnabled = isEnabled;
if (!isEnabled)
{
ResetControlValue(control);
}
}
}
private bool CheckDependency(ScriptParameter parameter, Dictionary<string, object?> currentValues)
{
if (!currentValues.TryGetValue(parameter.DependsOn, out var dependencyValue))
return false;
if (parameter.DependencyValues != null)
{
return parameter.DependencyValues
.Any(kvp => object.Equals(kvp.Value, dependencyValue));
}
return dependencyValue != null && !string.IsNullOrWhiteSpace(dependencyValue.ToString());
}
private void ResetControlValue(FrameworkElement control)
{
// Ищем первый дочерний элемент нужного типа
var child = FindChildOfType<TextBox>(control) ??
FindChildOfType<ComboBox>(control) ??
FindChildOfType<CheckBox>(control) ??
FindChildOfType<DatePicker>(control) ??
FindChildOfType<NumberBox>(control);
switch (child)
{
case TextBox textBox:
textBox.Text = string.Empty;
break;
case ComboBox comboBox:
comboBox.SelectedIndex = -1;
break;
case CheckBox checkBox:
checkBox.IsChecked = false;
break;
case DatePicker datePicker:
datePicker.Date = DateTimeOffset.Now;
break;
case NumberBox numberBox:
numberBox.Value = 0;
break;
}
}
private T? FindChildOfType<T>(DependencyObject parent) where T : DependencyObject
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(parent);
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (current is T result)
return result;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
queue.Enqueue(VisualTreeHelper.GetChild(current, i));
}
}
return null;
}
}