From f4304373b1501bbe39f7636146159515fd010f06 Mon Sep 17 00:00:00 2001 From: hj615 Date: Fri, 21 Nov 2025 15:10:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Utils=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SmartAquaViewer/Classes/Constants.cs | 27 +++ SmartAquaViewer/Classes/Utils.cs | 108 ++++++++++ .../Helper/DataGridAutoPageSizeBehavior.cs | 62 +++++- SmartAquaViewer/INI/EmbeddedAssembly.cs | 190 ++++++++++++++++++ SmartAquaViewer/INI/INIManager.cs | 86 ++++++++ SmartAquaViewer/MainWindow.xaml.cs | 13 ++ SmartAquaViewer/Model/ConfigData.cs | 28 +++ SmartAquaViewer/Model/Datas.cs | 7 + SmartAquaViewer/Resources/Generic.xaml | 3 + SmartAquaViewer/View/MonitoringView.xaml | 9 +- SmartAquaViewer/ViewModel/CCTVViewModel.cs | 4 +- 11 files changed, 530 insertions(+), 7 deletions(-) create mode 100644 SmartAquaViewer/Classes/Constants.cs create mode 100644 SmartAquaViewer/Classes/Utils.cs create mode 100644 SmartAquaViewer/INI/EmbeddedAssembly.cs create mode 100644 SmartAquaViewer/INI/INIManager.cs create mode 100644 SmartAquaViewer/Model/ConfigData.cs diff --git a/SmartAquaViewer/Classes/Constants.cs b/SmartAquaViewer/Classes/Constants.cs new file mode 100644 index 0000000..f0184c7 --- /dev/null +++ b/SmartAquaViewer/Classes/Constants.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartAquaViewer.Classes +{ + class Constants + { + public const string CONFIG_INI = "config.ini"; + + public class Config + { + public const string CONFIG = "CONFIG"; + public const string TITLE = "TITLE"; + public const string DATA_FILE_PATH = "DATA-FILE-PATH"; + public const string REC_PATH = "REC-PATH"; + } + + public class Directories + { + public const string DATA_FOLDER = "data_folder"; + public const string REC_FOLDER = "REC"; + } + } +} diff --git a/SmartAquaViewer/Classes/Utils.cs b/SmartAquaViewer/Classes/Utils.cs new file mode 100644 index 0000000..c58fd38 --- /dev/null +++ b/SmartAquaViewer/Classes/Utils.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartAquaViewer.INI; + +namespace SmartAquaViewer.Classes +{ + class Utils + { + public static Utils Instance { get; } = new Utils(); + + public readonly string CurrentDirectory = Environment.CurrentDirectory; + + private static INIManager? iniManager; + public static INIManager IniManager + { + get + { + if (iniManager == null) + { + string iniPath = Path.Combine(Instance.CurrentDirectory, Constants.CONFIG_INI); + iniManager = new INIManager(iniPath); + } + return iniManager; + } + } + + private string GetGeneralSection(String strKey, String? defaultValue) + { + return IniManager.ReadValue(Constants.Config.CONFIG, strKey, defaultValue); + } + + private void WriteValue(String strKey, String value) + { + IniManager.WriteValue(Constants.Config.CONFIG, strKey, value); + } + + private string? title; + private string Title + { + get + { + if (title == null) + { + title = GetGeneralSection(Constants.Config.TITLE, null); + } + return title; + } + } + + public string GetTitle() + { + return Title; + } + + private string? dataFilePath; + private string DataFilePath + { + get + { + if (dataFilePath == null) + { + dataFilePath = GetGeneralSection(Constants.Config.DATA_FILE_PATH, null); + } + return dataFilePath; + } + } + + public string GetDataFilePath() + { + return DataFilePath; + } + + public void CreateDirectory(string folderName) + { + string directoryPath = Path.Combine(Environment.CurrentDirectory, folderName); + + if (Directory.Exists(directoryPath)) + return; + + Directory.CreateDirectory(directoryPath); + Console.WriteLine($"Directory created at: {directoryPath}"); + } + + public void DebugWriteLine(string value, params object[] args) + { + if (Debugger.IsAttached) + { + Debug.WriteLine(value, args); + FileLog(value); + } + } + + public void WriteFileLog(object value) + { + File.AppendAllText(Path.Combine(CurrentDirectory, "log.txt"), String.Format("[{0}]\n{1}\n", DateTime.Now.ToString("yyyy-MM-dd HH:MM:ss"), value), Encoding.Default); + } + + public void FileLog(object value) + { + WriteFileLog(value); + } + } +} diff --git a/SmartAquaViewer/Helper/DataGridAutoPageSizeBehavior.cs b/SmartAquaViewer/Helper/DataGridAutoPageSizeBehavior.cs index 21aaa0c..5ed4ad7 100644 --- a/SmartAquaViewer/Helper/DataGridAutoPageSizeBehavior.cs +++ b/SmartAquaViewer/Helper/DataGridAutoPageSizeBehavior.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Threading; using SmartAquaViewer.ViewModel; namespace SmartAquaViewer.Helper @@ -20,6 +22,13 @@ namespace SmartAquaViewer.Helper public static void SetEnable(DependencyObject d, bool v) => d.SetValue(EnableProperty, v); public static bool GetEnable(DependencyObject d) => (bool)d.GetValue(EnableProperty); + public static readonly DependencyProperty SuspendProperty = + DependencyProperty.RegisterAttached("Suspend", typeof(bool), typeof(DataGridAutoPageSizeBehavior), + new PropertyMetadata(false)); + + public static void SetSuspend(DependencyObject d, bool v) => d.SetValue(SuspendProperty, v); + public static bool GetSuspend(DependencyObject d) => (bool)d.GetValue(SuspendProperty); + // 🔹 여기! 어느 페이저를 갱신할지 바인딩으로 지정 public static readonly DependencyProperty PagerProperty = DependencyProperty.RegisterAttached( @@ -29,15 +38,45 @@ namespace SmartAquaViewer.Helper public static void SetPager(DependencyObject d, IPager? v) => d.SetValue(PagerProperty, v); public static IPager? GetPager(DependencyObject d) => (IPager?)d.GetValue(PagerProperty); + public static readonly DependencyProperty ThrottleMsProperty = + DependencyProperty.RegisterAttached("ThrottleMs", typeof(int), typeof(DataGridAutoPageSizeBehavior), + new PropertyMetadata(120)); + public static void SetThrottleMs(DependencyObject d, int v) => d.SetValue(ThrottleMsProperty, v); + public static int GetThrottleMs(DependencyObject d) => (int)d.GetValue(ThrottleMsProperty); + + private static readonly ConditionalWeakTable _timers = new(); + private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is DataGrid dg) + if (d is not DataGrid dg) return; + if ((bool)e.NewValue) + { + dg.SizeChanged += DataGrid_SizeChanged; + EnsureTimer(dg); + } + else { - if ((bool)e.NewValue) dg.SizeChanged += DataGrid_SizeChanged; - else dg.SizeChanged -= DataGrid_SizeChanged; + dg.SizeChanged -= DataGrid_SizeChanged; + if (_timers.TryGetValue(dg, out var t)) + { + t.Stop(); + _timers.Remove(dg); + } } } + private static void EnsureTimer(DataGrid dg) + { + if (_timers.TryGetValue(dg, out _)) return; + var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(GetThrottleMs(dg)) }; + timer.Tick += (_, __) => + { + timer.Stop(); + ApplyAutoPageSize(dg); + }; + _timers.Add(dg, timer); + } + private static void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e) { if (sender is not DataGrid dg) return; @@ -57,6 +96,21 @@ namespace SmartAquaViewer.Helper if (pager.PageSize != rows) pager.PageSize = rows; // 페이저 쪽에서 RebuildPage() 호출됨 } - } + private static void ApplyAutoPageSize(DataGrid dg) + { + if (GetSuspend(dg)) return; // 여기도 방어 + if (dg.RowHeight <= 0 || dg.ActualHeight <= 0) return; + + double header = dg.ColumnHeaderHeight > 0 ? dg.ColumnHeaderHeight : 30; + double available = Math.Max(0, dg.ActualHeight - header - 2); + int rows = Math.Max(1, (int)(available / dg.RowHeight)); + + IPager? pager = GetPager(dg) ?? dg.DataContext as IPager; + if (pager is null) return; + + if (pager.PageSize != rows) + pager.PageSize = rows; // 내부에서 RebuildPage() 호출 + } + } } diff --git a/SmartAquaViewer/INI/EmbeddedAssembly.cs b/SmartAquaViewer/INI/EmbeddedAssembly.cs new file mode 100644 index 0000000..e1745a6 --- /dev/null +++ b/SmartAquaViewer/INI/EmbeddedAssembly.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using SmartAquaViewer.Classes; + +namespace SmartAquaViewer.INI +{ + class EmbeddedAssembly + { + static Dictionary dic = null; + + /// + /// Load Assembly, DLL from Embedded Resources into memory. + /// + /// Embedded Resource string. Example: WindowsFormsApplication1.SomeTools.dll + /// File Name. Example: SomeTools.dll + public static void Load(string embeddedResource, string fileName) + { + if (dic == null) + dic = new Dictionary(); + + byte[] ba = null; + Assembly asm = null; + Assembly curAsm = Assembly.GetExecutingAssembly(); + + using (Stream stm = curAsm.GetManifestResourceStream(embeddedResource)) + { + // Either the file is not existed or it is not mark as embedded resource + if (stm == null) + throw new Exception(embeddedResource + " is not found in Embedded Resources."); + + // Get byte[] from the file from embedded resource + ba = new byte[(int)stm.Length]; + stm.Read(ba, 0, (int)stm.Length); + try + { + asm = Assembly.Load(ba); + + // Add the assembly/dll into dictionary + dic.Add(asm.FullName, asm); + return; + } + catch + { + // Purposely do nothing + // Unmanaged dll or assembly cannot be loaded directly from byte[] + // Let the process fall through for next part + } + } + + bool fileOk = false; + string tempFile = ""; + + using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider()) + { + // Get the hash value from embedded DLL/assembly + string fileHash = BitConverter.ToString(sha1.ComputeHash(ba)).Replace("-", string.Empty); + + // Define the temporary storage location of the DLL/assembly + tempFile = Path.GetTempPath() + fileName; + + // Determines whether the DLL/assembly is existed or not + if (File.Exists(tempFile)) + { + // Get the hash value of the existed file + byte[] bb = File.ReadAllBytes(tempFile); + string fileHash2 = BitConverter.ToString(sha1.ComputeHash(bb)).Replace("-", string.Empty); + + // Compare the existed DLL/assembly with the Embedded DLL/assembly + if (fileHash == fileHash2) + { + // Same file + fileOk = true; + } + else + { + // Not same + fileOk = false; + } + } + else + { + // The DLL/assembly is not existed yet + fileOk = false; + } + } + + // Create the file on disk + if (!fileOk) + { + File.WriteAllBytes(tempFile, ba); + } + + // Load it into memory + asm = Assembly.LoadFile(tempFile); + + // Add the loaded DLL/assembly into dictionary + dic.Add(asm.FullName, asm); + } + + /// + /// Retrieve specific loaded DLL/assembly from memory + /// + /// + /// + public static Assembly Get(string assemblyFullName) + { + if (dic == null || dic.Count == 0) + return null; + + if (dic.ContainsKey(assemblyFullName)) + return dic[assemblyFullName]; + + return null; + + // Don't throw Exception if the dictionary does not contain the requested assembly. + // This is because the event of AssemblyResolve will be raised for every + // Embedded Resources (such as pictures) of the projects. + // Those resources wil not be loaded by this class and will not exist in dictionary. + } + + internal static void Copy(string embeddedResource, string fileName) + { + byte[] ba = null; + Assembly curAsm = Assembly.GetExecutingAssembly(); + + using (Stream stm = curAsm.GetManifestResourceStream(embeddedResource)) + { + // Either the file is not existed or it is not mark as embedded resource + if (stm == null) + throw new Exception(embeddedResource + " is not found in Embedded Resources."); + + // Get byte[] from the file from embedded resource + ba = new byte[(int)stm.Length]; + stm.Read(ba, 0, (int)stm.Length); + } + + bool fileOk = false; + + using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider()) + { + // Get the hash value from embedded DLL/assembly + string fileHash = BitConverter.ToString(sha1.ComputeHash(ba)).Replace("-", string.Empty); + + // Determines whether the DLL/assembly is existed or not + if (File.Exists(fileName)) + { + // Get the hash value of the existed file + byte[] bb = File.ReadAllBytes(fileName); + string fileHash2 = BitConverter.ToString(sha1.ComputeHash(bb)).Replace("-", string.Empty); + + // Compare the existed DLL/assembly with the Embedded DLL/assembly + if (fileHash == fileHash2) + { + // Same file + fileOk = true; + } + else + { + // Not same + fileOk = false; + } + } + else + { + // The DLL/assembly is not existed yet + fileOk = false; + } + } + + // Create the file on disk + if (!fileOk) + { + try + { + File.WriteAllBytes(fileName, ba); + } + catch (Exception ex) + { + Utils.Instance.DebugWriteLine(ex.ToString()); + } + } + } + } +} diff --git a/SmartAquaViewer/INI/INIManager.cs b/SmartAquaViewer/INI/INIManager.cs new file mode 100644 index 0000000..79672b9 --- /dev/null +++ b/SmartAquaViewer/INI/INIManager.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using SmartAquaViewer.Classes; + +namespace SmartAquaViewer.INI +{ + class INIManager + { + private readonly string strINIPath; + + [DllImport("kernel32", CharSet = CharSet.Ansi)] + private static extern long WritePrivateProfileString(String section, String key, String val, String filePath); + + [DllImport("kernel32", CharSet = CharSet.Ansi)] + private static extern int GetPrivateProfileString(String section, String key, String def, StringBuilder retVal, int size, String filePath); + + public INIManager(String INIPath) + { + strINIPath = INIPath; + if (!ExistINI()) + { + CreateIni(); + } + else + { + CheckAndInsertIni(); + } + } + + public void CreateIni() + { + WriteValue("CONFIG", Constants.Config.TITLE, "SmartAquaViwer"); + WriteValue("CONFIG", Constants.Config.DATA_FILE_PATH, Constants.Directories.DATA_FOLDER); + } + + public bool ExistINI() + { + return File.Exists(strINIPath); + } + + public void CheckAndInsertIni() + { + CheckAndInsert("CONFIG", Constants.Config.TITLE, "SmartAquaViwer"); + CheckAndInsert("CONFIG", Constants.Config.DATA_FILE_PATH, Constants.Directories.DATA_FOLDER); + + } + + public void CheckAndInsert(String strSection, String strKey, String strValue) + { + string key = ReadValue(strSection, strKey); + if (key == null || key.Trim() == "") + { + WriteValue(strSection, strKey, strValue); + } + } + + public void WriteValue(String strSection, String strKey, String strValue) + { + WritePrivateProfileString(strSection, strKey, strValue, strINIPath); + } + + public void DeleteSection(String strSection) + { + WritePrivateProfileString(strSection, null, null, strINIPath); + } + + public string ReadValue(String strSection, String Key) + { + return ReadValue(strSection, Key, ""); + } + + public string ReadValue(String strSection, String Key, String def) + { + var strValue = new StringBuilder(255); + + int i = GetPrivateProfileString(strSection, Key, def, strValue, 255, strINIPath); + + return strValue.ToString(); + } + } +} diff --git a/SmartAquaViewer/MainWindow.xaml.cs b/SmartAquaViewer/MainWindow.xaml.cs index 641abed..ae96562 100644 --- a/SmartAquaViewer/MainWindow.xaml.cs +++ b/SmartAquaViewer/MainWindow.xaml.cs @@ -8,6 +8,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using SmartAquaViewer.Classes; using SmartAquaViewer.DataAnalysis; using SmartAquaViewer.Model; @@ -22,9 +23,21 @@ namespace SmartAquaViewer { InitializeComponent(); + Loaded += MainWindow_Loaded; StateChanged += MainWindow_StateChanged; } + private void MainWindow_Loaded(object sender, RoutedEventArgs e) + { + ConfigData.Instance.LoadConfig(); + CreateDirectories(); + } + + private void CreateDirectories() + { + Utils.Instance.CreateDirectory(Constants.Directories.DATA_FOLDER); + } + private void WindowNormal_Click(object sender, RoutedEventArgs e) { WindowState = WindowState.Normal; diff --git a/SmartAquaViewer/Model/ConfigData.cs b/SmartAquaViewer/Model/ConfigData.cs new file mode 100644 index 0000000..1c00714 --- /dev/null +++ b/SmartAquaViewer/Model/ConfigData.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartAquaViewer.Classes; + +namespace SmartAquaViewer.Model +{ + public class ConfigData + { + public static ConfigData Instance { get; } = new ConfigData(); + + public string Title { get; set; } + public string DataFilePath { get; set; } + + public ConfigData() + { + + } + + public void LoadConfig() + { + Title = Utils.Instance.GetTitle(); + DataFilePath = Utils.Instance.GetDataFilePath(); + } + } +} diff --git a/SmartAquaViewer/Model/Datas.cs b/SmartAquaViewer/Model/Datas.cs index ca04dbd..8e446bf 100644 --- a/SmartAquaViewer/Model/Datas.cs +++ b/SmartAquaViewer/Model/Datas.cs @@ -17,11 +17,13 @@ namespace SmartAquaViewer.Model public ObservableCollection WaterQualityList { get; set; } public ReadOnlyObservableCollection WaterQualityView { get; } + public ObservableCollection CctvInfoList { get; set; } private Datas() { WaterQualityList = new ObservableCollection(); WaterQualityView = new ReadOnlyObservableCollection(WaterQualityList); + CctvInfoList = new ObservableCollection(); } public ObservableCollection GetWaterQualityVO() @@ -39,6 +41,11 @@ namespace SmartAquaViewer.Model OnPropertyChanged(nameof(WaterQualityList)); } + public void SetCCTVInfoList() + { + + } + public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); diff --git a/SmartAquaViewer/Resources/Generic.xaml b/SmartAquaViewer/Resources/Generic.xaml index 73a1efc..d8d53bc 100644 --- a/SmartAquaViewer/Resources/Generic.xaml +++ b/SmartAquaViewer/Resources/Generic.xaml @@ -149,8 +149,11 @@