diff --git a/SmartAquaViewer/Classes/GraphSettings.cs b/SmartAquaViewer/Classes/GraphSettings.cs deleted file mode 100644 index ad8546f..0000000 --- a/SmartAquaViewer/Classes/GraphSettings.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static SmartAquaViewer.Model.Enums; - -namespace SmartAquaViewer.Classes -{ - public interface IChartSettings - { - GraphType Kind { get; } - } - - public sealed class AvailableFields - { - public IReadOnlyList Numeric { get; init; } = new List(); - public IReadOnlyList Categorical { get; init; } = new List(); - } - - public class LineSettings : IChartSettings - { - public GraphType Kind => GraphType.LINE; - [Required] public string XField { get; set; } = ""; // 보통 시간 - [MinLength(1)] public List YFields { get; set; } = new(); - public bool ShowLegend { get; set; } = true; - } - - public class BoxPlotSettings : IChartSettings - { - public GraphType Kind => GraphType.BOX; - [Required] public string GroupField { get; set; } = ""; // 수조/장비 등 카테고리 - [Required] public string ValueField { get; set; } = ""; // DO, pH, 압력 등 숫자 - public bool ShowOutliers { get; set; } = true; - } - - public class ScatterSettings : IChartSettings - { - public GraphType Kind => GraphType.SCATTER; - [Required] public string XField { get; set; } = ""; // 숫자 - [Required] public string YField { get; set; } = ""; // 숫자 - public string? ColorBy { get; set; } // 카테고리(수조/장비/상태) - public string? SizeBy { get; set; } // 숫자(버블 크기) - } - - public class StepSettings : IChartSettings - { - public GraphType Kind => GraphType.STEP; - [Required] public string XField { get; set; } = ""; // 보통 시간 - // 장비별 상태 필드 매핑 - public ObservableCollection Series { get; } = new(); - public double OnValue { get; set; } = 1; - public double OffValue { get; set; } = 0; - } - - public class StateSeriesMap - { - public string SeriesName { get; set; } = ""; // 예: "오존용해장치" - public string Field { get; set; } = ""; // 예: "오존용해장치(상태)" - public string OnToken { get; set; } = "ON"; - public string OffToken { get; set; } = "OFF"; - } - -} diff --git a/SmartAquaViewer/Controls/GraphControl.xaml b/SmartAquaViewer/Controls/GraphControl.xaml index d32cf89..4cd8d4f 100644 --- a/SmartAquaViewer/Controls/GraphControl.xaml +++ b/SmartAquaViewer/Controls/GraphControl.xaml @@ -2,48 +2,15 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:SmartAquaViewer.Controls" + xmlns:oxy="http://oxyplot.org/wpf" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="1080"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/SmartAquaViewer/Helper/MultiSelectBehavior.cs b/SmartAquaViewer/Helper/MultiSelectBehavior.cs index b68e3a5..a718ee4 100644 --- a/SmartAquaViewer/Helper/MultiSelectBehavior.cs +++ b/SmartAquaViewer/Helper/MultiSelectBehavior.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows; +using System.ComponentModel; namespace SmartAquaViewer.Helper { @@ -38,11 +39,112 @@ namespace SmartAquaViewer.Helper private static void Lb_SelectionChanged(object? sender, SelectionChangedEventArgs e) { if (sender is not ListBox lb) return; + var list = GetSelectedItems(lb); - if (list == null) return; + if (list != null) + { + foreach (var removed in e.RemovedItems) list.Remove(removed); + foreach (var added in e.AddedItems) list.Add(added); + } + + // Dictionary 동기화도 같이 수행 + var dict = GetSelectedDictionary(lb); + if (dict != null) + { + string? keyPath = GetKeyPath(lb); + string? valuePath = GetValuePath(lb); + + foreach (var removed in e.RemovedItems) + { + var key = ResolvePath(removed, keyPath); + if (key != null && dict.Contains(key)) dict.Remove(key); + } + foreach (var added in e.AddedItems) + { + var key = ResolvePath(added, keyPath); + var value = ResolvePath(added, valuePath); + if (key != null) dict[key] = value; + } + } + } + + // ===== IDictionary 버전 ===== + public static readonly DependencyProperty SelectedDictionaryProperty = + DependencyProperty.RegisterAttached( + "SelectedDictionary", typeof(IDictionary), typeof(MultiSelectBehavior), + new PropertyMetadata(null, OnSelectedDictionaryChanged)); + + public static void SetSelectedDictionary(DependencyObject element, IDictionary? value) + => element.SetValue(SelectedDictionaryProperty, value); + public static IDictionary? GetSelectedDictionary(DependencyObject element) + => (IDictionary?)element.GetValue(SelectedDictionaryProperty); + + // 선택된 항목에서 key/value를 뽑아낼 경로 (예: "Id", "Name", "User.Profile.Id") + public static readonly DependencyProperty KeyPathProperty = + DependencyProperty.RegisterAttached( + "KeyPath", typeof(string), typeof(MultiSelectBehavior), new PropertyMetadata(null)); + + public static void SetKeyPath(DependencyObject element, string? value) + => element.SetValue(KeyPathProperty, value); + public static string? GetKeyPath(DependencyObject element) + => (string?)element.GetValue(KeyPathProperty); - foreach (var removed in e.RemovedItems) list.Remove(removed); - foreach (var added in e.AddedItems) list.Add(added); + // 값 경로. 비우거나 "." 이면 아이템 자체를 값으로 사용 + public static readonly DependencyProperty ValuePathProperty = + DependencyProperty.RegisterAttached( + "ValuePath", typeof(string), typeof(MultiSelectBehavior), new PropertyMetadata(".")); + + public static void SetValuePath(DependencyObject element, string? value) + => element.SetValue(ValuePathProperty, value); + public static string? GetValuePath(DependencyObject element) + => (string?)element.GetValue(ValuePathProperty); + + private static void OnSelectedDictionaryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not ListBox lb) return; + + lb.SelectionChanged -= Lb_SelectionChanged; + lb.SelectionChanged += Lb_SelectionChanged; + + if (e.NewValue is IDictionary dict) + { + dict.Clear(); + + string? keyPath = GetKeyPath(lb); + string? valuePath = GetValuePath(lb); + + foreach (var item in lb.SelectedItems) + { + var key = ResolvePath(item, keyPath); + var value = ResolvePath(item, valuePath); + if (key != null) dict[key] = value; + } + } + } + + // ===== 유틸 ===== + private static object? ResolvePath(object? instance, string? path) + { + if (instance == null) return null; + if (string.IsNullOrWhiteSpace(path) || path == ".") return instance; + + object? current = instance; + foreach (var segment in path.Split('.')) + { + if (current == null) return null; + + // IDictionary인 중간 단계도 지원 (예: dict["Key"]) + if (current is IDictionary dict) + { + if (dict.Contains(segment)) { current = dict[segment]; continue; } + return null; + } + + var pd = TypeDescriptor.GetProperties(current)[segment]; + if (pd == null) return null; + current = pd.GetValue(current); + } + return current; } } } diff --git a/SmartAquaViewer/SmartAquaViewer.csproj b/SmartAquaViewer/SmartAquaViewer.csproj index a503939..aae0fc5 100644 --- a/SmartAquaViewer/SmartAquaViewer.csproj +++ b/SmartAquaViewer/SmartAquaViewer.csproj @@ -80,7 +80,9 @@ PreserveNewest - + + PreserveNewest + diff --git a/SmartAquaViewer/View/MonitoringView.xaml b/SmartAquaViewer/View/MonitoringView.xaml index 045f4bb..7b6744b 100644 --- a/SmartAquaViewer/View/MonitoringView.xaml +++ b/SmartAquaViewer/View/MonitoringView.xaml @@ -164,8 +164,8 @@ - - + + @@ -173,7 +173,7 @@ - - - + + + +