diff --git a/AppDiagnostic/App.config b/AppDiagnostic/App.config new file mode 100644 index 000000000..3bedfc101 --- /dev/null +++ b/AppDiagnostic/App.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AppDiagnostic/AppDiagnostic.csproj b/AppDiagnostic/AppDiagnostic.csproj new file mode 100644 index 000000000..65f8e8e7a --- /dev/null +++ b/AppDiagnostic/AppDiagnostic.csproj @@ -0,0 +1,101 @@ + + + + + Debug + AnyCPU + {FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2} + WinExe + AppDiagnostic + AppDiagnostic + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\log4net.3.0.3\lib\net462\log4net.dll + + + + + + + + + + + + + + + + + + Component + + + + + + Form + + + MainForm.cs + + + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {8f942729-f454-4c99-ba6c-746962065ae3} + SharedComponents + + + + \ No newline at end of file diff --git a/AppDiagnostic/BufferedListView.cs b/AppDiagnostic/BufferedListView.cs new file mode 100644 index 000000000..c33523f7f --- /dev/null +++ b/AppDiagnostic/BufferedListView.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +public class BufferedListView : ListView +{ + public BufferedListView() + { + // Zapne dvojité bufferovanie pre ListView + this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + this.UpdateStyles(); + } +} \ No newline at end of file diff --git a/AppDiagnostic/DiagApi.cs b/AppDiagnostic/DiagApi.cs new file mode 100644 index 000000000..2ea6b1064 --- /dev/null +++ b/AppDiagnostic/DiagApi.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class DiagApi + { + private static MainForm diagWindow; // Udržiava referenciu na existujúce okno + + public DiagApi() + { + if (diagWindow == null || diagWindow.IsDisposed) // Ak okno neexistuje, vytvoríme ho + { + diagWindow = new MainForm(); + diagWindow.FormClosing += (s, e) => + { + diagWindow = null; // Keď sa zavrie, vyčistíme referenciu + }; + diagWindow.Show(); + } + else + { + diagWindow.BringToFront(); // Ak už beží, len ho presunieme na vrch + diagWindow.WindowState = FormWindowState.Normal; // Ak je minimalizované, obnovíme ho + } + } + } +} diff --git a/AppDiagnostic/LogAdapter.cs b/AppDiagnostic/LogAdapter.cs new file mode 100644 index 000000000..33a351c7e --- /dev/null +++ b/AppDiagnostic/LogAdapter.cs @@ -0,0 +1,174 @@ +using SharedComponents; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class LogAdapter + { + private ListView _listView; + public string _filterText = string.Empty; + private bool _isUserScrolling = false; // Indikátor, že používateľ manuálne scrolluje + private bool _autoScrollEnabled = true; // Indikátor, že automatické scrollovanie je povolené + private Timer _refreshTimer; + private const int RefreshInterval = 1000; + + public LogAdapter(ListView listView) + { + _listView = listView; + + // Nastavenie ListView + _listView.View = View.Details; + _listView.Columns.Clear(); + _listView.Columns.Add("Logs"); + + _listView.Scrollable = true; + + // Nastavenie šírky stĺpca na celú šírku ListView + AdjustColumnWidth(); + + // Udalosť pre dynamickú zmenu veľkosti stĺpca pri zmene veľkosti ListView + _listView.Resize += (sender, args) => AdjustColumnWidth(); + + // Pridanie udalosti pre manuálne skrolovanie + _listView.MouseWheel += ListView_MouseWheel; + + // Inicializácia časovača na pravidelný refresh + _refreshTimer = new Timer { Interval = RefreshInterval }; + _refreshTimer.Tick += (sender, args) => RefreshListView(); + _refreshTimer.Start(); + } + + private void ListView_MouseWheel(object sender, MouseEventArgs e) + { + _isUserScrolling = true; + + // Ak sa manuálnym scrollovaním používateľ dostane na koniec, obnovíme automatické scrollovanie + if (IsOnLastItem()) + { + _isUserScrolling = false; + _autoScrollEnabled = true; + } + else + { + _autoScrollEnabled = false; + } + } + + public void RefreshListView() + { + //LiveLogCache.Instance.AddLog(string.Format("-------------------------------RunDeviceAfter()-------------------------------")); + + if (_listView.InvokeRequired) + { + _listView.Invoke(new Action(RefreshListView)); + return; + } + + _listView.BeginUpdate(); + try + { + // Získanie pozície prvého viditeľného prvku pre stabilizáciu scrollovania + int topIndexBeforeRefresh = _listView.TopItem?.Index ?? 0; + + // Pred pridaním nových položiek si pamätáme, či bol používateľ na poslednej položke + bool wasOnLastItem = IsOnLastItem(); + + // Uchovanie aktuálne označených položiek (indexov) + var selectedIndices = _listView.SelectedIndices.Cast().ToList(); + + // Načítanie logov + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount()) + .Where(log => string.IsNullOrEmpty(_filterText) || + log.IndexOf(_filterText, StringComparison.OrdinalIgnoreCase) >= 0) + .ToList(); + + // Vymazanie existujúcich položiek a pridanie nových + _listView.Items.Clear(); + + foreach (var log in logs) + { + var item = new ListViewItem(log) + { + BackColor = _listView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240) + }; + _listView.Items.Add(item); + } + + // Obnovenie označených položiek + foreach (var index in selectedIndices) + { + if (index < _listView.Items.Count) + { + _listView.Items[index].Selected = true; + } + } + + // Ak bol používateľ na poslednom prvku, nastavíme focus a scroll na posledný prvok + if (_autoScrollEnabled && wasOnLastItem && _listView.Items.Count > 0) + { + var lastItemIndex = _listView.Items.Count - 1; + _listView.EnsureVisible(lastItemIndex); + _listView.Items[lastItemIndex].Focused = true; + } + else + { + // Ak používateľ nebol na spodku, vrátime sa na predchádzajúcu pozíciu scrollu + if (topIndexBeforeRefresh < _listView.Items.Count) + { + _listView.TopItem = _listView.Items[topIndexBeforeRefresh]; + } + } + } + finally + { + _listView.EndUpdate(); + } + } + + + + public void ApplyFilter(string filterText) + { + _filterText = filterText?.Trim() ?? string.Empty; + RefreshListView(); + } + + private bool IsOnLastItem() + { + if (_listView.Items.Count == 0) + return false; + + // Získame poslednú položku + int lastItemIndex = _listView.Items.Count - 1; + var lastItem = _listView.Items[lastItemIndex]; + + // Overíme, či je posledná položka úplne viditeľná + return lastItem.Bounds.Bottom <= _listView.ClientRectangle.Bottom; + } + + public void EnableAutoScroll() + { + _autoScrollEnabled = true; + } + + public void DisableAutoScroll() + { + _autoScrollEnabled = false; + } + + private void AdjustColumnWidth() + { + if (_listView.Columns.Count > 0) + { + _listView.Columns[0].Width = _listView.ClientSize.Width; + } + } + } + +} diff --git a/AppDiagnostic/LogFilter.cs b/AppDiagnostic/LogFilter.cs new file mode 100644 index 000000000..6a8ec4f91 --- /dev/null +++ b/AppDiagnostic/LogFilter.cs @@ -0,0 +1,91 @@ +using SharedComponents; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class LogFilter + { + private ListView _listView; + private string _filterText = string.Empty; // Pre uloženie aktuálneho filtrovaného reťazca + + // Konstruktor + public LogFilter(ListView listView) + { + _listView = listView; + } + + // Metóda na aplikovanie filtra na ListView + public void ApplyFilter(string filterText) + { + _filterText = filterText.ToLower(); // Ukladáme text filtra bez ohľadu na veľkosť písmen + + // Po aplikovaní filtra obnovíme zobrazenie + RefreshListView(); + } + + public void RefreshListView() + { + try + { + // Skontrolujeme, či sme na hlavnom vlákne a v prípade potreby použijeme Invoke + if (_listView.InvokeRequired) + { + // Použijeme Invoke, ak nie sme na hlavnom vlákne + _listView.Invoke(new Action(RefreshListView)); + } + else + { + // Vymazanie existujúcich položiek + _listView.Items.Clear(); + + // Načítame všetky logy (ideálne z cache alebo databázy) + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.Logs.Count); + + // Pridáme len tie logy, ktoré spĺňajú filter + foreach (var log in logs) + { + if (log.ToLower().Contains(_filterText)) // Kontrola, či log obsahuje filter text + { + var listViewItem = new ListViewItem(log); + + // Striedanie farieb riadkov + if (_listView.Items.Count % 2 == 0) // Párny index => biela + { + listViewItem.BackColor = Color.White; + } + else // Nepárny index => svetlá sivá + { + listViewItem.BackColor = Color.FromArgb(240, 240, 240); // Veľmi svetlá sivá + } + + // Pridanie efektu pre novú položku + ApplyNewItemEffect(listViewItem); + + // Pridanie položky do ListView + _listView.Items.Add(listViewItem); + } + } + } + } + catch (Exception ex) + { + // Zachytíme výnimku, ak sa niečo pokazí, a zobrazíme ju užívateľovi + MessageBox.Show($"An error occurred while refreshing the log view: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + // Môžete pridať ďalšie metódy na zobrazenie alebo efekt pre nové položky, ak je to potrebné + private void ApplyNewItemEffect(ListViewItem item) + { + // Prípadný efekt pre nový pridaný log + // Môžete pridať animáciu alebo zmenu farby atď. + item.ForeColor = Color.Green; // Napríklad zmeníme farbu písma na zelenú + } + } +} diff --git a/AppDiagnostic/MainForm.Designer.cs b/AppDiagnostic/MainForm.Designer.cs new file mode 100644 index 000000000..1eef3958d --- /dev/null +++ b/AppDiagnostic/MainForm.Designer.cs @@ -0,0 +1,278 @@ +using System.Windows.Forms; + +namespace AppDiagnostic +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + //private BufferedListView memoListView; + private System.Windows.Forms.ColumnHeader columnHeaderTime; + private System.Windows.Forms.ColumnHeader columnHeaderMessage; + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.columnHeaderTime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeaderMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.panel1 = new System.Windows.Forms.Panel(); + this.refreshMemoButton = new System.Windows.Forms.Button(); + this.button6 = new System.Windows.Forms.Button(); + this.button7 = new System.Windows.Forms.Button(); + this.filterManagementPanel = new System.Windows.Forms.Panel(); + this.button5 = new System.Windows.Forms.Button(); + this.filterButton = new System.Windows.Forms.Button(); + this.filterTextBox = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.wndControlPanel = new System.Windows.Forms.Panel(); + this.button2 = new System.Windows.Forms.Button(); + this.flowControlsPanel = new System.Windows.Forms.Panel(); + this.runButton = new System.Windows.Forms.Button(); + this.stopButton = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.memoListView = new BufferedListView(); + this.panel1.SuspendLayout(); + this.filterManagementPanel.SuspendLayout(); + this.wndControlPanel.SuspendLayout(); + this.flowControlsPanel.SuspendLayout(); + this.SuspendLayout(); + // + // columnHeaderTime + // + this.columnHeaderTime.Text = "Time"; + this.columnHeaderTime.Width = 150; + // + // columnHeaderMessage + // + this.columnHeaderMessage.Text = "Message"; + this.columnHeaderMessage.Width = 350; + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.panel1.Controls.Add(this.refreshMemoButton); + this.panel1.Controls.Add(this.button6); + this.panel1.Controls.Add(this.button7); + this.panel1.Location = new System.Drawing.Point(189, 356); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(283, 26); + this.panel1.TabIndex = 16; + // + // refreshMemoButton + // + this.refreshMemoButton.Location = new System.Drawing.Point(108, 0); + this.refreshMemoButton.Name = "refreshMemoButton"; + this.refreshMemoButton.Size = new System.Drawing.Size(84, 26); + this.refreshMemoButton.TabIndex = 17; + this.refreshMemoButton.Text = "Refresh memo"; + this.refreshMemoButton.UseVisualStyleBackColor = true; + this.refreshMemoButton.Click += new System.EventHandler(this.refreshMemoButton_Click); + // + // button6 + // + this.button6.Location = new System.Drawing.Point(0, 0); + this.button6.Name = "button6"; + this.button6.Size = new System.Drawing.Size(102, 26); + this.button6.TabIndex = 4; + this.button6.Text = "Clean diag cache"; + this.button6.UseVisualStyleBackColor = true; + this.button6.Click += new System.EventHandler(this.button6_Click); + // + // button7 + // + this.button7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.button7.Location = new System.Drawing.Point(198, 0); + this.button7.Name = "button7"; + this.button7.Size = new System.Drawing.Size(72, 26); + this.button7.TabIndex = 5; + this.button7.Text = "Save logs"; + this.button7.UseVisualStyleBackColor = true; + this.button7.Click += new System.EventHandler(this.button7_Click); + // + // filterManagementPanel + // + this.filterManagementPanel.Controls.Add(this.button5); + this.filterManagementPanel.Controls.Add(this.filterButton); + this.filterManagementPanel.Controls.Add(this.filterTextBox); + this.filterManagementPanel.Controls.Add(this.label2); + this.filterManagementPanel.Location = new System.Drawing.Point(127, 6); + this.filterManagementPanel.Name = "filterManagementPanel"; + this.filterManagementPanel.Size = new System.Drawing.Size(393, 26); + this.filterManagementPanel.TabIndex = 15; + // + // button5 + // + this.button5.Enabled = false; + this.button5.Location = new System.Drawing.Point(294, 4); + this.button5.Name = "button5"; + this.button5.Size = new System.Drawing.Size(81, 20); + this.button5.TabIndex = 11; + this.button5.Text = "Add new filter"; + this.button5.UseVisualStyleBackColor = true; + // + // filterButton + // + this.filterButton.Location = new System.Drawing.Point(225, 4); + this.filterButton.Name = "filterButton"; + this.filterButton.Size = new System.Drawing.Size(64, 20); + this.filterButton.TabIndex = 10; + this.filterButton.Text = "Set filter"; + this.filterButton.UseVisualStyleBackColor = true; + this.filterButton.Click += new System.EventHandler(this.filterButton_Click_1); + // + // filterTextBox + // + this.filterTextBox.Location = new System.Drawing.Point(33, 4); + this.filterTextBox.Name = "filterTextBox"; + this.filterTextBox.Size = new System.Drawing.Size(187, 20); + this.filterTextBox.TabIndex = 7; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 8); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(29, 13); + this.label2.TabIndex = 6; + this.label2.Text = "Filter"; + // + // wndControlPanel + // + this.wndControlPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.wndControlPanel.Controls.Add(this.button2); + this.wndControlPanel.Location = new System.Drawing.Point(831, 356); + this.wndControlPanel.Name = "wndControlPanel"; + this.wndControlPanel.Size = new System.Drawing.Size(70, 26); + this.wndControlPanel.TabIndex = 14; + // + // button2 + // + this.button2.Location = new System.Drawing.Point(3, 0); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(64, 26); + this.button2.TabIndex = 2; + this.button2.Text = "Close"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // flowControlsPanel + // + this.flowControlsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.flowControlsPanel.Controls.Add(this.runButton); + this.flowControlsPanel.Controls.Add(this.stopButton); + this.flowControlsPanel.Location = new System.Drawing.Point(12, 356); + this.flowControlsPanel.Name = "flowControlsPanel"; + this.flowControlsPanel.Size = new System.Drawing.Size(133, 26); + this.flowControlsPanel.TabIndex = 13; + // + // runButton + // + this.runButton.Enabled = false; + this.runButton.Location = new System.Drawing.Point(0, 0); + this.runButton.Name = "runButton"; + this.runButton.Size = new System.Drawing.Size(64, 26); + this.runButton.TabIndex = 4; + this.runButton.Text = "Run"; + this.runButton.UseVisualStyleBackColor = true; + this.runButton.Click += new System.EventHandler(this.runButton_Click); + // + // stopButton + // + this.stopButton.Location = new System.Drawing.Point(69, 0); + this.stopButton.Name = "stopButton"; + this.stopButton.Size = new System.Drawing.Size(64, 26); + this.stopButton.TabIndex = 5; + this.stopButton.Text = "Stop"; + this.stopButton.UseVisualStyleBackColor = true; + this.stopButton.Click += new System.EventHandler(this.stopButton_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 19); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(76, 13); + this.label1.TabIndex = 12; + this.label1.Text = "Flow of events"; + // + // memoListView + // + this.memoListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.memoListView.FullRowSelect = true; + this.memoListView.GridLines = true; + this.memoListView.HideSelection = false; + this.memoListView.Location = new System.Drawing.Point(12, 38); + this.memoListView.Name = "memoListView"; + this.memoListView.Size = new System.Drawing.Size(889, 312); + this.memoListView.TabIndex = 0; + this.memoListView.UseCompatibleStateImageBehavior = false; + this.memoListView.View = System.Windows.Forms.View.Details; + + this.memoListView.DoubleClick += new System.EventHandler(this.MemoListView_DoubleClick); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(913, 388); + this.Controls.Add(this.memoListView); + this.Controls.Add(this.panel1); + this.Controls.Add(this.filterManagementPanel); + this.Controls.Add(this.wndControlPanel); + this.Controls.Add(this.flowControlsPanel); + this.Controls.Add(this.label1); + this.Name = "MainForm"; + this.Text = "Application live diagnostic"; + this.panel1.ResumeLayout(false); + this.filterManagementPanel.ResumeLayout(false); + this.filterManagementPanel.PerformLayout(); + this.wndControlPanel.ResumeLayout(false); + this.flowControlsPanel.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button button6; + private System.Windows.Forms.Button button7; + private System.Windows.Forms.Panel filterManagementPanel; + private System.Windows.Forms.Button button5; + private System.Windows.Forms.Button filterButton; + private System.Windows.Forms.TextBox filterTextBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel wndControlPanel; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Panel flowControlsPanel; + private System.Windows.Forms.Button runButton; + private System.Windows.Forms.Button stopButton; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button refreshMemoButton; + private BufferedListView memoListView; + } +} + diff --git a/AppDiagnostic/MainForm.cs b/AppDiagnostic/MainForm.cs new file mode 100644 index 000000000..ee4f6b817 --- /dev/null +++ b/AppDiagnostic/MainForm.cs @@ -0,0 +1,266 @@ +using log4net.Config; +using log4net; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using log4net.Appender; +using SharedComponents; +using static System.Windows.Forms.VisualStyles.VisualStyleElement; +using System.IO; // Pre File.WriteAllLines a prácu so súbormi +using System.Linq; + +namespace AppDiagnostic +{ + public partial class MainForm : Form + { + private bool stopUpdate = false; // Premenná na zastavenie aktualizácie + private int lastDisplayedLogIndex = 0; + private bool autoScrollEnabled = true; // Automatický posun, predvolene povolený + private MainForm appDiagnosticForm; + private Timer refreshTimer; + private LogAdapter _logAdapter; // Pre LogAdapter + private LogFilter _logFilter; // LogFilter objekt pre filtráciu + + public MainForm() + { + InitializeComponent(); + + // Inicializujeme LogAdapter a priradíme ho k memoListView + _logAdapter = new LogAdapter(memoListView); + + RefreshLogView(); + + // Pripojenie na udalosť pri pridaní nového logu + // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem + //LiveLogCache.Instance.LogAdded += OnLogAdded; + + // Vytvoríme LogFilter objekt + //_logFilter = new LogFilter(memoListView); + } + + private void OnLogAdded(string log) + { + try + { + RefreshLogView(); + } + catch (Exception ex) + { + MessageBox.Show($"Error in OnLogAdded: {ex.Message}"); + throw; + } + } + + private void RefreshLogView() + { + // Skontrolujte, či voláme z iného vlákna + if (InvokeRequired) + { + Invoke((Action)RefreshLogView); + return; + } + + memoListView.BeginUpdate(); // Zabraňuje vizuálnym aktualizáciám počas vykresľovania + + try + { + // Skontrolujte, či je užívateľ na poslednom viditeľnom zázname + if (memoListView.Items.Count > 0) + { + var lastVisibleIndex = memoListView.TopItem.Index + memoListView.ClientRectangle.Height / memoListView.Items[0].Bounds.Height; + autoScrollEnabled = lastVisibleIndex >= memoListView.Items.Count - 1; + } + + // Získajte nové logy od posledného indexu + var logs = LiveLogCache.Instance.GetLogs(lastDisplayedLogIndex, LiveLogCache.Instance.Logs.Count - lastDisplayedLogIndex); + + // Pridajte len nové logy + foreach (var log in logs) + { + memoListView.Items.Add(new ListViewItem(log)); + } + + // Aktualizujte posledný zobrazený index + lastDisplayedLogIndex = LiveLogCache.Instance.Logs.Count; + + // Ak je autoScrollEnabled, posuňte sa na posledný záznam + if (autoScrollEnabled && memoListView.Items.Count > 0) + { + memoListView.EnsureVisible(memoListView.Items.Count - 1); + } + } + finally + { + memoListView.EndUpdate(); // Umožní vizuálne aktualizácie + } + } + + // Táto metóda sa spustí pri scrollovaní v memoListView (tzn. ak sa používateľ dostane na spodok listu) + private void memoListView_Scroll(object sender, EventArgs e) + { + // Skontrolujte, či používateľ dosiahol spodok ListView + if (memoListView.Items.Count > 0 && + memoListView.Items[memoListView.Items.Count - 1].Bounds.Bottom <= memoListView.ClientSize.Height) + { + // Ak áno, načítame ďalšie logy + RefreshLogView(); + } + } + + private void button6_Click(object sender, EventArgs e) + { + //LiveLogCache.Instance.AddLog("Nový log z hlavnej aplikácie"); + + LiveLogCache.Instance.ClearLogs(); + ClearListView(); + } + + private void runButton_Click(object sender, EventArgs e) + { + if (stopUpdate) // Ak je časovač zastavený, spustíme ho + { + stopUpdate = false; // Zastav aktualizáciu + runButton.Enabled = false; // Zakážeme tlačidlo Start počas behu + stopButton.Enabled = true; // Povolenie tlačidla Stop + refreshTimer.Start(); + } + } + + private void stopButton_Click(object sender, EventArgs e) + { + if (!stopUpdate) // Ak časovač beží, môžeme ho zastaviť + { + stopUpdate = true; // Spusti aktualizáciu + runButton.Enabled = true; // Povolenie tlačidla Start + stopButton.Enabled = false; // Zakážeme tlačidlo Stop + refreshTimer.Stop(); + } + } + + private void ClearListView() + { + if (InvokeRequired) + { + Invoke(new Action(ClearListView)); + return; + } + + memoListView.Items.Clear(); + } + + private void refreshMemoButton_Click(object sender, EventArgs e) + { + + } + + private void button7_Click(object sender, EventArgs e) + { + SaveLogsToFile(); + //LiveLogCache.Instance.AddLog("Toto je nový log."); + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + // Odpojenie udalosti, keď sa okno zatvára, inak môže po opätovnom spustení okna a pridaní nového itemu do listu zhhodiť program + // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem + //LiveLogCache.Instance.LogAdded -= OnLogAdded; + + _logAdapter = null; + + base.OnFormClosing(e); + } + + private void filterButton_Click_1(object sender, EventArgs e) + { + // Aplikujeme filter na základe textu z filterTextBox + string filterText = filterTextBox.Text; + //_logFilter.ApplyFilter(filterText); // Aplikovanie filtra + _logAdapter.ApplyFilter(filterText); + } + private void SubForm_FormClosing(object sender, FormClosingEventArgs e) + { + try + { + // Vaša logika pri uzatváraní formy + // this.Hide(); // Podforma sa len skryje + } + catch (Exception ex) + { + // Ošetrenie výnimky + MessageBox.Show("Chyba pri zatváraní pod-aplikácie: " + ex.Message); + } + } + + private void SaveLogsToFile() + { + using (SaveFileDialog saveFileDialog = new SaveFileDialog()) + { + saveFileDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"; + saveFileDialog.Title = "Save TBF live logs"; + saveFileDialog.DefaultExt = "txt"; + saveFileDialog.FileName = "TBFLiveLogs.txt"; + + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + try + { + // Načítanie všetkých logov z LiveLogCache + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount()); + + // Uloženie logov do vybraného súboru + File.WriteAllLines(saveFileDialog.FileName, logs); + + MessageBox.Show("Logs saved successfully.", "Save TBF live logs", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show($"An error occurred while saving TBF live logs: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + + private void button2_Click(object sender, EventArgs e) + { + this.Close(); + //this.Hide(); // Podforma sa len skryje + } + + private void MemoListView_DoubleClick(object sender, EventArgs e) + { + if (memoListView.SelectedItems.Count > 0) + { + var selectedItem = memoListView.SelectedItems[0]; + int selectedIndex = memoListView.Items.IndexOf(selectedItem); + + if (selectedIndex >= 0) + { + // Zrušenie filtra + _logAdapter._filterText = string.Empty; + + // Načítanie logov od indexu vybraného riadku + var logs = LiveLogCache.Instance.GetLogs(selectedIndex, LiveLogCache.Instance.GetCount() - selectedIndex); + + // Obnovenie ListView s novými údajmi + memoListView.BeginUpdate(); + memoListView.Items.Clear(); + foreach (var log in logs) + { + var item = new ListViewItem(log) + { + BackColor = memoListView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240) + }; + memoListView.Items.Add(item); + } + memoListView.EndUpdate(); + } + } + } + } +} diff --git a/AppDiagnostic/MainForm.resx b/AppDiagnostic/MainForm.resx new file mode 100644 index 000000000..d58980a38 --- /dev/null +++ b/AppDiagnostic/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AppDiagnostic/Program.cs b/AppDiagnostic/Program.cs new file mode 100644 index 000000000..6aaa9c232 --- /dev/null +++ b/AppDiagnostic/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/AppDiagnostic/Properties/AssemblyInfo.cs b/AppDiagnostic/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..482abc00e --- /dev/null +++ b/AppDiagnostic/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AppDiagnostic")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AppDiagnostic")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fa9abad1-7184-4295-ade8-d44f2e3de6b2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AppDiagnostic/Properties/Resources.Designer.cs b/AppDiagnostic/Properties/Resources.Designer.cs new file mode 100644 index 000000000..5f4bc8a07 --- /dev/null +++ b/AppDiagnostic/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AppDiagnostic.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppDiagnostic.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/AppDiagnostic/Properties/Resources.resx b/AppDiagnostic/Properties/Resources.resx new file mode 100644 index 000000000..13b775f1d --- /dev/null +++ b/AppDiagnostic/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AppDiagnostic/Properties/Settings.settings b/AppDiagnostic/Properties/Settings.settings new file mode 100644 index 000000000..07e66beb1 --- /dev/null +++ b/AppDiagnostic/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/AppDiagnostic/packages.config b/AppDiagnostic/packages.config new file mode 100644 index 000000000..ffd035f93 --- /dev/null +++ b/AppDiagnostic/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Config/Data.cs b/Config/Data.cs new file mode 100644 index 000000000..88726d89d --- /dev/null +++ b/Config/Data.cs @@ -0,0 +1,103 @@ +using System; + +//formulas.cs has been added cause iPerl from master-2 ...MF + +namespace Config +{ + public class Data + { + public const string AdminUsername = "admin"; + public const string AdminPassword = "staratura"; + public const string SQLiteDbFName = "SQLite.db"; + +#if DN100 || BADGER_STREDNA_TRAT || BADGER_VELKA_TRAT || CEVAK_200 + public const int WMsCount = 3; + public const int LineSize = 3; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif MUNICH + public const int WMsCount = 3; + public const int LineSize = 3; + public const int CompoundWMsCount = 3; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 3; +#elif MALTA_WSD25 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif BADGER_MALA_TRAT || BERLIN || FUZHOU_150 || FUZHOU_300 || GELSENWASSER || GENESIS || IZRAEL_200 || PETERSBURG_200 || SENTEC || SLM_150 || TORINO_50 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif PUCHONG_200 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 3; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 3; +#elif RUM_MOB + public const int WMsCount = 8; + public const int LineSize = 8; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif TURA_SPECIAL + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif DEWA_300 || FUZHOU_100 || ROMA_200 || LUXEMBURG_40 + public const int WMsCount = 10; + public const int LineSize = 10; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif DUBAJ_50 + public const int WMsCount = 10; + public const int LineSize = 5; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif SLM_END || WARSAW_END + public const int WMsCount = 10; + public const int LineSize = 5; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 4; +#elif SLM_50 + public const int WMsCount = 12; + public const int LineSize = 12; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 6; + public const int MaxPartNr = WMsCount / LineSize; +#elif ALZIR_25 || BAHRAIN_50 || CEVAK_40 || FEWA_50 || FILIPINY_50 || FUZHOU_50 || HONGKONG_50 || IZRAEL_25 || JUZNA_AFRIKA_50 || KEMPNO_50 || KRAKOW_50 || MILWAUKEE || MURES_40 || PETERSBURG_50 || TORUN_50 || WARSAW_50 || ZAMBIA || ZODINO + public const int WMsCount = 20; + public const int LineSize = 10; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif TURA_IPERL || TURA_IPERL_NEW + public const int WMsCount = 40; + public const int LineSize = 20; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif IZRAEL_50 + public const int WMsCount = 40; + public const int LineSize = 10; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#endif + + public static double RealDensity = 0; /// true water density [kg/m3] + public static double AtTemperature = 0; /// measured at temperature [°C] + public static double Buoyancy = 0; + } +} diff --git a/Config/Formulas.cs b/Config/Formulas.cs new file mode 100644 index 000000000..24b888476 --- /dev/null +++ b/Config/Formulas.cs @@ -0,0 +1,410 @@ +/// +/// Copyright (c) 2013-2018 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using log4net; +using Common; + +//formulas.cs has been added cause iPerl from master-2 ...MF + +namespace Config +{ + public static class Formulas + { + private static readonly ILog log = LogManager.GetLogger(typeof(Formulas)); + + + /// + /// Wrappers + /// + public static double RealDensity() { return Data.RealDensity; } + public static double AtTemperature() { return Data.AtTemperature; } + public static double Buoyancy() { return Data.Buoyancy; } + + + /// Private tables with coeficients to calculate specific enthalpy + static readonly int[] Ii; + static readonly int[] Ji; + static readonly double[] ni; + + /// Private table with coeficients to calculate temperature of a platinum thermometer + static readonly double[] Di; + + + /// + /// Constructor + /// + static Formulas() + { + /// + /// Initialize tables to calculate specific enthalpies + /// + Ii = new int[34] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32 }; + Ji = new int[34] { -2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41 }; + ni = new double[34] { + 0.14632971213167, /// 1 + -0.84548187169114, /// 2 + -0.37563603672040E1, /// 3 + 0.33855169168385E1, /// 4 + -0.95791963387872, /// 5 + 0.15772038513228, /// 6 + -0.16616417199501E-1, /// 7 + 0.81214629983568E-3, /// 8 + 0.28319080123804E-3, /// 9 + -0.60706301565874E-3, /// 10 + -0.18990068218419E-1, /// 11 + -0.32529748770505E-1, /// 12 + -0.21841717175414E-1, /// 13 + -0.52838357969930E-4, /// 14 + -0.47184321073267E-3, /// 15 + -0.30001780793026E-3, /// 16 + 0.47661393906987E-4, /// 17 + -0.44141845330846E-5, /// 18 + -0.72694996297594E-15, /// 19 + -0.31679644845054E-4, /// 20 + -0.28270797985312E-5, /// 21 + -0.85205128120103E-9, /// 22 + -0.22425281908000E-5, /// 23 + -0.65171222895601E-6, /// 24 + -0.14341729937924E-12, /// 25 + -0.40516996860117E-6, /// 26 + -0.12734301741641E-8, /// 27 + -0.17424871230634E-9, /// 28 + -0.68762131295531E-18, /// 29 + 0.14478307828521E-19, /// 30 + 0.26335781662795E-22, /// 31 + -0.11947622640071E-22, /// 32 + 0.18228094581404E-23, /// 33 + -0.93537087292458E-25, /// 34 + }; + + /// + /// Initialize a table to calculate temperature of a platinum thermometer from resistance + /// + Di = new double[] + { + 439.932854, + 472.418020, + 37.684494, + 7.472018, + 2.920828, + 0.005184, + -0.963864, + -0.188732, + 0.191203, + 0.049025, + }; + } + + /// + /// Calculate density of distilled water from temperature + /// + /// ITS-90 temperature in [°C] + /// Density in [kg/m3] + public static double DistilledWaterDensityFromTemp(double t) + { + if (t <= 40) + { + const double c0 = 999.839564; + const double c1 = 0.067998613; + const double c2 = -0.0091101468; + const double c3 = 0.00010058299; + const double c4 = -0.0000011275659; + const double c5 = 6.5985371e-09; + + return ((((c5 * t + c4) * t + c3) * t + c2) * t + c1) * t + c0; + } + else + { + const double a0 = 9.9983952E2; + const double a1 = 1.6952577E1; + const double a2 = -7.9905127E-3; + const double a3 = -4.6241757E-5; + const double a4 = 1.0584601E-7; + const double a5 = -2.8103006E-10; + const double b = 1.6887236E-2; + + return (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0) / (1.0 + b * t); + } + } + + /// + /// Calculate density of distilled water from temperature (obsolete) + /// + /// IPTS-68 temperature in [°C] + /// Density in [kg/m3] + public static double DistilledWaterDensityFromTempIPTS68(double t) + { + const double a0 = 999.842594; + const double a1 = 0.06793952; + const double a2 = -0.009095290; + const double a3 = 0.0001001685; + const double a4 = -0.000001120083; + const double a5 = 6.536332e-09; + + return ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0; + } + + /// + /// Calculate density by comparing calculated data and data from a certificate + /// + /// Density from a certificate in [kg/m3] + /// Temperature from a certificate in [°C] + /// Density correction in [kg/m3] + public static double DensityCorrection(double realDensity, double atTemperature) + { + /// Calculated data + double calculatedDensity = DistilledWaterDensityFromTemp(atTemperature); + + return realDensity - calculatedDensity; + } + + /// + /// Calculate corrected (real) water density from temperature + /// + /// Temperature in [°C] + /// Density in [kg/m3] + public static double WaterDensityFromTemp(double t) + { + return DistilledWaterDensityFromTemp(t) + DensityCorrection(RealDensity(), AtTemperature()); + } + + /// + /// Calculate corrected (real) water density from temperature + /// + /// Temperature in [°C] + /// Density in [kg/m3] + public static double WaterDensityFromTempPress(double temp, double pressure) + { + double x0 = 5.08821E-10; + double x1 = 1.2639418; + double x2 = 0.2660269; + double x3 = 0.3734838; + double x4 = 2.0205242; + double theta = temp / 100.0; + double B = x0 * (((x3 * theta + x2) * theta + x1) * theta + 1) / (1 + x4 * theta); + + return WaterDensityFromTemp(temp) * (1 + B * Units.ConvertTo(Unit.Pa, pressure)); + } + + + public static float AirDensityFromAmbientVales(float tempC, float pressureBar, float humiPct) + { + double pressurePa = 100000.0 * (double)pressureBar; /// [Pa] + double tempKelvin = 273.15 + (double)tempC; + double coef1 = 1.2811805 / 10000.0 * tempKelvin * tempKelvin + - 1.950987 / 100.0 * tempKelvin + + 34.04926034 + - 6.353631 * 1000.0 / tempKelvin; + double coef3 = humiPct / 100.0 * System.Math.Exp(coef1) / pressurePa; + double airDensityKgm3 = 0.00348353 * pressurePa * (1.0 - 0.378 * coef3) / tempKelvin; /// kg/m3 + return (float)airDensityKgm3; + } + + /// + /// Convert 'pulses' to 'volume', prevent division by zero + /// + public static double VolumeFromPulses(int pulses, double pulsesPerLiter) + { + if (pulsesPerLiter <= double.Epsilon) return 0; + return Convert.ToDouble(pulses) / pulsesPerLiter; + } + + /// + /// Calculate the error in % from 'measured' and 'true' volume, prevent division by zero + /// + public static double ErrorFromVolumes(double measuredVolume, double trueVolume) + { + if (-float.Epsilon <= trueVolume && trueVolume <= float.Epsilon) + { + if (-float.Epsilon <= measuredVolume && measuredVolume <= float.Epsilon) + { + log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, -100.0); + return -100.0; + } + + log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, 99.0); + return 99.0; + } + + double error = 100.0 * (measuredVolume - trueVolume) / trueVolume; + log.InfoFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, error); + return error; + } + + + /// + /// Calculates corrected value from a list of corrections by interpolation. + /// It is assumed that values in the list 'corrections' are sorted. + /// + /// Raw uncorrected value + /// Sorted (value, correction) pairs + /// Corrected value + public static double CorrectedValue(double rawValue, IList corrections) + { + return rawValue + GetCorrection(rawValue, corrections); + } + + /// + /// Get a correction from a list of corrections by interpolation. + /// It is assumed that values in the list 'corrections' are sorted. + /// + /// Raw uncorrected value + /// Sorted (value, correction) pairs + /// Corrected value + public static double GetCorrection(double rawValue, IList corrections) + { + if ((corrections == null) || (corrections.Count == 0)) return 0; /// No correction + + if (rawValue < corrections[0].Measurement) + { + /// rawValue is below the lowest value in the correction table + return corrections[0].Correction; + } + + int count = corrections.Count; + for (int i = 1; i < count; i++) + { + if (rawValue < corrections[i].Measurement) + { + double d1 = rawValue - corrections[i - 1].Measurement; + double d2 = corrections[i].Measurement - rawValue; + + if (d1 + d2 <= float.Epsilon) + { + /// Neigboring values in the corection table are close to each other -> calculate the average + return (corrections[i - 1].Correction + corrections[i].Correction) / 2.0; + } + else + { + /// Interpolate the correction from neigboring values in the corection table + return (corrections[i - 1].Correction * d2 + corrections[i].Correction * d1) / (d1 + d2); + } + } + } + + /// rawValue is above the highest value in the correction table + return corrections[count - 1].Correction; + } + + + /// + /// Converts a measurement error to a correction (used when preparing correction tables). + /// + /// Measured value (in arbitrary units) + /// Measurement error in % + /// Correction in the same units as the measured value + public static double CorrectionFromError(double measuredValue, double error) + { + double trueValue = measuredValue / (1 + error/100); + double correction = trueValue - measuredValue; + return correction; + } + + + /// + /// Calculates the heat coefficient for water + /// + /// Pressure [bar] + /// Inlet temperature [°C] + /// Outlet temperature [°C] + /// true = flow measured @inlet, false = flow measured @outlet + /// Heat coefficient for water [J/(m3 K)] + public static double HeatCoefficientWater(double pressure, double T_in, double T_out, bool flowMeasuredAtInlet) + { + if (T_in == T_out) return 0; + + const double R = 461.526; /// [J kg^-1 K^-1] + const double p_star_Pa = 16.53E6; /// [Pa] (=16.53 MPa) + const double T_star = 1386.0; /// [K] + + double T_in_K = Units.ConvertTo(Unit.K, T_in); + double T_out_K = Units.ConvertTo(Unit.K, T_out); + double tau_in = T_star / T_in_K; + double tau_out = T_star / T_out_K; + double pi = Units.ConvertTo(Unit.Pa, pressure) / p_star_Pa; + + double h_in = tau_in * GammaTau(pi, tau_in) * R * T_in_K; + double h_out = tau_out * GammaTau(pi, tau_out) * R * T_out_K; + + double ni = flowMeasuredAtInlet ? GammaPi(pi, tau_in) * R * T_in_K / p_star_Pa + : GammaPi(pi, tau_out) * R * T_out_K / p_star_Pa; + + return (h_in - h_out) / (ni * (T_in - T_out)); + } + + /// + /// gamma(pi) see also STN EN 1434-1 Annex A (A.4) + /// + /// pi = p / p* where p* = 16.53 MPa + /// tau = T* / T where T* = 1386 K + /// gamma(pi) + static double GammaPi(double pi, double tau) + { + double result = 0; + for (int i = 0; i < 34; i++) + { + result -= ni[i] * Ii[i] * Math.Pow(7.1 - pi, Ii[i] - 1) * Math.Pow(tau - 1.222, Ji[i]); + } + return result; + } + + /// + /// gamma(tau) see also STN EN 1434-1 Annex A (A.7) + /// + /// pi = p / p* where p* = 16.53 MPa + /// tau = T* / T where T* = 1386 K + /// gamma(tau) + static double GammaTau(double pi, double tau) + { + double result = 0; + for (int i = 0; i < 34; i++) + { + result += ni[i] * Math.Pow(7.1 - pi, Ii[i]) * Ji[i] * Math.Pow(tau - 1.222, Ji[i] - 1); + } + return result; + } + + /// + /// Conversion of measured resistance of a platinum thermometer to temperature according to ITS-90 + /// + /// Measured resistance in [°C] + /// Calibrated resistance in Ohm at 0.01°C + /// Calibrated ITS-90 coefficient a7 + /// Calibrated ITS-90 coefficient b7 + /// Calibrated ITS-90 coefficient c7 + /// Temperature in [°C] + public static double PlatinumResistanceTM_ITS90_R2T(double R, double R001C, double a7, double b7, double c7) + { + double w = R / R001C; /// ratio + double r1 = w - 1.0; + double dw = r1 * (a7 + r1 * (b7 + r1 * c7)); /// = a7*r1 + b7*r1^2 + c7*r1^3 + double wr = w - dw; + double x = (wr - 2.64) / 1.64; + + double sum = 0; + for (int i = Di.Length - 1; i >= 0; i--) + { + sum = sum * x + Di[i]; + } + + return sum; + } + + /// + /// Conversion of measured resistance of a platinum thermometer to temperature using Callendar-Van Dusen equations + /// + /// Measured resistance in [°C] + /// Calibrated resistance in Ohm at 0°C + /// Calibration coefficient a + /// Calibration coefficient b + /// Temperature in [°C] + public static double PlatinumResistanceTM_ITS27_R2T(double R, double R0, double A, double B) + { + if (R0 * R0 * A * A - 4 * R0 * B * (R0 - R) <= 0) return 0; /// Out of range + + return (-(R0 * A) + Math.Sqrt(R0 * R0 * A * A - 4 * R0 * B * (R0 - R))) / (2 * R0 * B); + } + } +} diff --git a/SchematicDrawing/Pictures/Tank-L-hot.png b/SchematicDrawing/Pictures/Tank-L-hot.png new file mode 100644 index 000000000..fddf1604b Binary files /dev/null and b/SchematicDrawing/Pictures/Tank-L-hot.png differ diff --git a/SchematicDrawing/Pictures/Tank-M-hot.png b/SchematicDrawing/Pictures/Tank-M-hot.png new file mode 100644 index 000000000..40121259a Binary files /dev/null and b/SchematicDrawing/Pictures/Tank-M-hot.png differ diff --git a/SchematicDrawing/Pictures/Tank-S-hot.png b/SchematicDrawing/Pictures/Tank-S-hot.png new file mode 100644 index 000000000..4e052e67c Binary files /dev/null and b/SchematicDrawing/Pictures/Tank-S-hot.png differ diff --git a/SchematicDrawing/Pictures/Tank-XL-hot.png b/SchematicDrawing/Pictures/Tank-XL-hot.png new file mode 100644 index 000000000..34cb418d3 Binary files /dev/null and b/SchematicDrawing/Pictures/Tank-XL-hot.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-L-closed.png b/SchematicDrawing/Pictures/ValveSw-L-closed.png new file mode 100644 index 000000000..052aed4ee Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-L-closed.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-L-open-dry.png b/SchematicDrawing/Pictures/ValveSw-L-open-dry.png new file mode 100644 index 000000000..86d355d00 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-L-open-dry.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-L-open.png b/SchematicDrawing/Pictures/ValveSw-L-open.png new file mode 100644 index 000000000..0bf26b0fd Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-L-open.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-L-vacuum.png b/SchematicDrawing/Pictures/ValveSw-L-vacuum.png new file mode 100644 index 000000000..ffda1b101 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-L-vacuum.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-M-closed.png b/SchematicDrawing/Pictures/ValveSw-M-closed.png new file mode 100644 index 000000000..297d65bde Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-M-closed.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-M-open-dry.png b/SchematicDrawing/Pictures/ValveSw-M-open-dry.png new file mode 100644 index 000000000..532644076 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-M-open-dry.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-M-open.png b/SchematicDrawing/Pictures/ValveSw-M-open.png new file mode 100644 index 000000000..58c10ba47 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-M-open.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-M-vacuum.png b/SchematicDrawing/Pictures/ValveSw-M-vacuum.png new file mode 100644 index 000000000..c13dda053 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-M-vacuum.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-S-closed.png b/SchematicDrawing/Pictures/ValveSw-S-closed.png new file mode 100644 index 000000000..7278a215a Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-S-closed.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-S-open-dry.png b/SchematicDrawing/Pictures/ValveSw-S-open-dry.png new file mode 100644 index 000000000..d54ad96da Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-S-open-dry.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-S-open.png b/SchematicDrawing/Pictures/ValveSw-S-open.png new file mode 100644 index 000000000..0f0dbd371 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-S-open.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-S-vacuum.png b/SchematicDrawing/Pictures/ValveSw-S-vacuum.png new file mode 100644 index 000000000..5dbc7edee Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-S-vacuum.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-XL-closed.png b/SchematicDrawing/Pictures/ValveSw-XL-closed.png new file mode 100644 index 000000000..bf00b396e Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-XL-closed.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-XL-open-dry.png b/SchematicDrawing/Pictures/ValveSw-XL-open-dry.png new file mode 100644 index 000000000..653a59064 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-XL-open-dry.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-XL-open.png b/SchematicDrawing/Pictures/ValveSw-XL-open.png new file mode 100644 index 000000000..937f352be Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-XL-open.png differ diff --git a/SchematicDrawing/Pictures/ValveSw-XL-vacuum.png b/SchematicDrawing/Pictures/ValveSw-XL-vacuum.png new file mode 100644 index 000000000..96674b408 Binary files /dev/null and b/SchematicDrawing/Pictures/ValveSw-XL-vacuum.png differ diff --git a/SchematicDrawing/SchematicDrawing.csproj b/SchematicDrawing/SchematicDrawing.csproj index 09adf733c..c438c5178 100644 --- a/SchematicDrawing/SchematicDrawing.csproj +++ b/SchematicDrawing/SchematicDrawing.csproj @@ -98,6 +98,16 @@ + + + + + + + + + + @@ -191,6 +201,12 @@ + + + + + + diff --git a/SharedComponents/LiveLogCache.cs b/SharedComponents/LiveLogCache.cs new file mode 100644 index 000000000..656974152 --- /dev/null +++ b/SharedComponents/LiveLogCache.cs @@ -0,0 +1,234 @@ +using System.IO.MemoryMappedFiles; +using System.Text; + +namespace SharedComponents +{ + public class LiveLogCache + { + /*// Staticka instancia pre singleton + private static readonly LiveLogCache _instance = new LiveLogCache(); + + // Uzamykaci objekt pre bezpecny pristup z viacerych vlakien + private static readonly object _lock = new object(); + + // Zoznam na ukladanie logov + private readonly List _logs = new List(); + + // Udalost pre notifikaciu pri zmene logov + public event Action LogAdded; + + // Sukromny konstruktor (singleton pattern) + private LiveLogCache() { } + + // Metoda na ziskanie poctu logov + public int GetCount() + { + return _logs.Count; + } + + // Staticka metoda na ziskanie instancie + public static LiveLogCache Instance + { + get + { + lock (_lock) + { + return _instance; + } + } + } + + // Verejna vlastnost na ziskanie logov + public IReadOnlyList Logs + { + get + { + lock (_lock) + { + return _logs.AsReadOnly(); + } + } + } + + // Metoda na pridanie logu + public void AddLog(string log) + { + // Ziskanie aktualneho casu v pozadovanom formate + string timeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:"); + + // Vytvorenie noveho logu s casovou peciatkou na zaciatku + string logWithTimeStamp = $"{timeStamp} {log}"; + + // Pridanie logu do zoznamu + lock (_lock) + { + _logs.Add(logWithTimeStamp); + } + + // Spustenie udalosti pre notifikaciu + LogAdded?.Invoke(logWithTimeStamp); + } + + // Metoda na vycistenie logov + public void ClearLogs() + { + lock (_lock) + { + _logs.Clear(); + } + + // Volitelne: Spusti udalost, ak by sme chceli notifikovat aj o vymazani logov + LogAdded?.Invoke("Logs were cleared."); + } + + // Ziskanie vsetkych logov + public List GetAllLogs() + { + return new List(_logs); // Vratime kopiu logov + } + + // Ziskanie logov medzi urcitou poziciou (s upravenym indexovanim) + public List GetLogs(int startIndex, int count) + { + // Zabezpeci, ze indexy nebudu mimo rozsahu + return _logs.Skip(startIndex).Take(count).ToList(); + }*/ + + // Zlepseny kod, ktory umoznuje pracu s MemoryMappedFile, namiesto len priestoru vo virtualnej pamati, navyse je tento subor velkostne obmedzeny na 1MB + + // Staticka instancia pre singleton + private static readonly LiveLogCache _instance = new LiveLogCache(true); + + // Uzamykaci objekt pre bezpecny pristup z viacerych vlakien + private static readonly object _lock = new object(); + + // Zoznam na ukladanie logov + private readonly List _logs = new List(); + + // Udalost pre notifikaciu pri zmene logov + public event Action LogAdded; + + // Memory-mapped file + private MemoryMappedFile _mmf; + private MemoryMappedViewAccessor _accessor; + private const int MaxLogSize = 1024 * 1024; // 1MB + + // Sukromny konstruktor (singleton pattern) + private LiveLogCache(bool itsLogsCreator) + { + if (itsLogsCreator == true) + { + _mmf = MemoryMappedFile.CreateOrOpen("LiveLogCacheMMF", MaxLogSize); + } + else + { + _mmf = MemoryMappedFile.OpenExisting("LiveLogCacheMMF"); + } + + _accessor = _mmf.CreateViewAccessor(); + } + + // Metoda na ziskanie poctu logov + public int GetCount() + { + return _logs.Count; + } + + // Staticka metoda na ziskanie instancie + public static LiveLogCache Instance + { + get + { + lock (_lock) + { + return _instance; + } + } + } + + // Verejna vlastnost na ziskanie logov + public IReadOnlyList Logs + { + get + { + lock (_lock) + { + return _logs.AsReadOnly(); + } + } + } + + // Metoda na pridanie logu + public void AddLog(string log) + { + // Ziskanie aktualneho casu v pozadovanom formate + string timeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:"); + + // Vytvorenie noveho logu s casovou peciatkou na zaciatku + string logWithTimeStamp = $"{timeStamp} {log}"; + + // Pridanie logu do zoznamu + lock (_lock) + { + _logs.Add(logWithTimeStamp); + WriteLogToMemoryMappedFile(logWithTimeStamp); + } + + // Spustenie udalosti pre notifikaciu + LogAdded?.Invoke(logWithTimeStamp); + } + + // Metoda na vycistenie logov + public void ClearLogs() + { + lock (_lock) + { + _logs.Clear(); + ClearMemoryMappedFile(); + } + + // Volitelne: Spusti udalost, ak by sme chceli notifikovat aj o vymazani logov + LogAdded?.Invoke("Logs were cleared."); + } + + // Ziskanie vsetkych logov + public List GetAllLogs() + { + return new List(_logs); // Vratime kopiu logov + } + + // Ziskanie logov medzi urcitou poziciou (s upravenym indexovanim) + public List GetLogs(int startIndex, int count) + { + // Zabezpeci, ze indexy nebudu mimo rozsahu + return _logs.Skip(startIndex).Take(count).ToList(); + } + + // Metoda na naplnenie ListView + /*public void PopulateListView(ListView listView) + { + listView.Items.Clear(); + lock (_lock) + { + foreach (var log in _logs) + { + listView.Items.Add(new ListView Item(log)); + } + } + }*/ + + // Write log to memory-mapped file + private void WriteLogToMemoryMappedFile(string log) + { + byte[] logBytes = Encoding.UTF8.GetBytes(log + Environment.NewLine); + _accessor.WriteArray(0, logBytes, 0, logBytes.Length); + } + + // Clear memory-mapped file + private void ClearMemoryMappedFile() + { + byte[] emptyBytes = new byte[MaxLogSize]; + _accessor.WriteArray(0, emptyBytes, 0, emptyBytes.Length); + } + } +} diff --git a/SharedComponents/SharedComponents.csproj b/SharedComponents/SharedComponents.csproj new file mode 100644 index 000000000..06e92ea43 --- /dev/null +++ b/SharedComponents/SharedComponents.csproj @@ -0,0 +1,10 @@ + + + + net472 + 10.0 + enable + enable + + + diff --git a/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 000000000..a02128ff9 --- /dev/null +++ b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,5 @@ +is_global = true +build_property.RootNamespace = SharedComponents +build_property.ProjectDir = C:\Users\micha\git\tbf\SharedComponents\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs b/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs new file mode 100644 index 000000000..c11666bec --- /dev/null +++ b/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs @@ -0,0 +1,7 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/SharedComponents/obj/SharedComponents.csproj.nuget.g.props b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props new file mode 100644 index 000000000..c2b287e0d --- /dev/null +++ b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\micha\.nuget\packages\ + PackageReference + 6.14.0 + + + + + \ No newline at end of file diff --git a/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets b/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets new file mode 100644 index 000000000..babc2c6b6 --- /dev/null +++ b/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/TBF/Tools/ByteFormatter.cs b/TBF/Tools/ByteFormatter.cs new file mode 100644 index 000000000..0e1d3ff99 --- /dev/null +++ b/TBF/Tools/ByteFormatter.cs @@ -0,0 +1,62 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System; +using System.Globalization; + +namespace TBF.Tools +{ + public class ByteFormatter : IFormatProvider, ICustomFormatter + { + public ByteFormatter() + { + } + + public object GetFormat(Type formatType) + { + if (formatType == typeof(ICustomFormatter)) + return this; + else + return null; + } + + public string Format(string fmt, object arg, IFormatProvider formatProvider) + { + if (arg.GetType() != typeof(byte)) + { + try + { + return HandleOtherFormats(fmt, arg); + } + catch (FormatException e) + { + throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e); + } + } + + /// + /// Convert one byte to string + /// + byte b = (byte)arg; + if ((32 <= b) && (b <= 127)) return "'" + ((char)b).ToString() + "'"; + if ((char)b == '\r') return "'\\r'"; + if ((char)b == '\n') return "'\\n'"; + if ((char)b == '\t') return "'\\t'"; + if (b == 17) return "XON"; + if (b == 19) return "XOFF"; + if (b == 26) return "Ctrl-Z"; + return b.ToString(); + } + + + private string HandleOtherFormats(string format, object arg) + { + if (arg is IFormattable) + return ((IFormattable)arg).ToString(format, CultureInfo.CurrentCulture); + else if (arg != null) + return arg.ToString(); + else + return String.Empty; + } + } +} diff --git a/TBF/Tools/IniUtil.cs b/TBF/Tools/IniUtil.cs new file mode 100644 index 000000000..a190050a9 --- /dev/null +++ b/TBF/Tools/IniUtil.cs @@ -0,0 +1,108 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections.Specialized; + +namespace TBF.Tools +{ + /// + /// The class use the WinApi GetPrivateProfileSectionNames, + /// GetPrivateProfileSection, GetPrivateProfileString, WritePrivateProfileString + /// and present easy methods to work from a NET point of view. + /// Usage: + /// IniUtil ini = new IniUtil(@"C:\program files (x86)\myapp\myapp.ini"); + /// string country = ini.GetValue("Carrier","Country","NoCountry"); + /// + public class IniUtil + { + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileSectionNames(byte[] lpszReturnBuffer, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileSection(string lpAppName, byte[] lpReturnedString, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileString(string lpApplicationName, string lpKeyName, string lpDefault, byte[] lpReturnedString, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern bool WritePrivateProfileString(string lpApplicationName, string lpKeyName, string lpString, string lpFileName); + + private const int VALUE_BUFFER = 511; + private const int SECTION_BUFFER = (1024 * 16); + private string m_sIniFile; + + /// + /// .ctor with INI file name + /// + /// Fullpath to the INI file + public IniUtil(string fileName) + { + m_sIniFile = fileName; + } + + /// + /// Set the value for a specific key in a section + /// + /// Section containing the key to write to + /// Key to insert/update + /// Value for the key + /// True if OK + public bool SetValue(string section, string key, string keyvalue) + { + return WritePrivateProfileString(section, key, keyvalue, m_sIniFile); + } + + /// + /// Gets the value of the specidied key in the specified section, + /// If the key doesn't exists returns the default value + /// + /// Section containing the key to read from + /// Required key + /// Value to return in case the key is missing + /// string value of the key or missing value + public string GetValue(string section, string key, string ifMissing) + { + byte[] by = new byte[VALUE_BUFFER]; + int n = GetPrivateProfileString(section, key, ifMissing, by, VALUE_BUFFER, m_sIniFile); + string s = Encoding.ASCII.GetString(by); + return s.Substring(0, n); + } + + /// + /// Returns the NameValueCollection for every key in the section + /// + /// Section name + /// NameValueCollection with nake=Key and value=value + public NameValueCollection GetSectionKeysvalues(string section) + { + NameValueCollection n = new NameValueCollection(); + if(section.Length > 0) + { + byte[] by = new byte[SECTION_BUFFER]; + int x = GetPrivateProfileSection(section, by, SECTION_BUFFER, m_sIniFile); + if(x > 0) x--; + string keysvalues = Encoding.ASCII.GetString(by, 0, x); + string[] temp = keysvalues.Split('\0'); + foreach(string s in temp) + { + string[] t = s.Split('='); + n.Add(t[0], t[1]); + } + } + return n; + } + + /// + /// Get the names of all sections in .INI + /// + /// string array with all the key names + public string[] GetSectionNames() + { + byte[] by = new byte[SECTION_BUFFER]; + int x = GetPrivateProfileSectionNames(by, SECTION_BUFFER, m_sIniFile); + if(x > 0) x--; + string keys = Encoding.ASCII.GetString(by, 0, x); + return keys.Split('\0'); + } + } +} diff --git a/TBF/Tools/LogChecker.cs b/TBF/Tools/LogChecker.cs new file mode 100644 index 000000000..fa76f96e9 --- /dev/null +++ b/TBF/Tools/LogChecker.cs @@ -0,0 +1,50 @@ +using log4net.Appender; +using log4net.Core; +using log4net.Repository.Hierarchy; +using log4net; +using System; +using System.Collections.Generic; + +namespace TBF.Tools +{ + public class LogChecker : IDisposable + { + readonly Logger _logger; + readonly Level _previousLevel; + readonly MemoryAppender _appender = new MemoryAppender(); + + public LogChecker(string logName, Level levelToCheck) + { + _logger = (Logger)LogManager.GetLogger(logName).Logger; + _logger.AddAppender(_appender); + _previousLevel = _logger.Level; + _logger.Level = levelToCheck; + } + + public List Messages + { + get + { + return new List(_appender.GetEvents()) + .ConvertAll(x => x.RenderedMessage); + } + } + + public void Dispose() + { + _logger.Level = _previousLevel; + _logger.RemoveAppender(_appender); + } + } +} + +// Example of use +// +// using (Tools.LogChecker logChecker = new Tools.LogChecker("RfidData", log4net.Core.Level.Debug)) +// { +// Execute query using NHibernate +// .... +// +// MessageBox.Show(String.Join(Environment.NewLine,logChecker.Messages)); +// } +// diff --git a/TBF/Tools/XmlTools.cs b/TBF/Tools/XmlTools.cs new file mode 100644 index 000000000..5deff02a9 --- /dev/null +++ b/TBF/Tools/XmlTools.cs @@ -0,0 +1,30 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System.IO; +using System.Xml.Serialization; + +namespace TBF.Tools +{ + public static class XmlTools + { + public static string ToXmlString(this T input) + { + using (var writer = new StringWriter()) + { + input.ToXml(writer); + return writer.ToString(); + } + } + + public static void ToXml(this T objectToSerialize, Stream stream) + { + new XmlSerializer(typeof(T)).Serialize(stream, objectToSerialize); + } + + public static void ToXml(this T objectToSerialize, StringWriter writer) + { + new XmlSerializer(typeof(T)).Serialize(writer, objectToSerialize); + } + } +} diff --git a/TBF/UI/Shared/SuggestComboBox.cs b/TBF/UI/Shared/SuggestComboBox.cs new file mode 100644 index 000000000..0ece9c518 --- /dev/null +++ b/TBF/UI/Shared/SuggestComboBox.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Linq.Expressions; +using System.Windows.Forms; + +namespace TBF.UI.Shared +{ + public class SuggestComboBox : ComboBox + { + #region fields and properties + + private readonly ListBox _suggLb = new ListBox { Visible = false, TabStop = false }; + private readonly BindingList _suggBindingList = new BindingList(); + private Expression>> _propertySelector; + private Func> _propertySelectorCompiled; + private Expression> _filterRule; + private Func _filterRuleCompiled; + private Expression> _suggestListOrderRule; + private Func _suggestListOrderRuleCompiled; + + public int SuggestBoxHeight + { + get { return _suggLb.Height; } + set { if (value > 0) _suggLb.Height = value; } + } + + /// + /// If the item-type of the ComboBox is not string, + /// you can set here which property should be used + /// + public Expression>> PropertySelector + { + get { return _propertySelector; } + set + { + if (value == null) return; + _propertySelector = value; + _propertySelectorCompiled = value.Compile(); + } + } + + /// + /// Lambda-Expression to determine the suggested items + /// (as Expression here because simple lamda (func) is not serializable) + /// default: case-insensitive contains search + /// 1st string: list item + /// 2nd string: typed text + /// + public Expression> FilterRule + { + get { return _filterRule; } + set + { + if (value == null) return; + _filterRule = value; + _filterRuleCompiled = item => value.Compile()(item, Text); + } + } + + /// + /// Lambda-Expression to order the suggested items + /// (as Expression here because simple lamda (func) is not serializable) + /// default: alphabetic ordering + /// + public Expression> SuggestListOrderRule + { + get { return _suggestListOrderRule; } + set + { + if (value == null) return; + _suggestListOrderRule = value; + _suggestListOrderRuleCompiled = value.Compile(); + } + } + + #endregion + + /// + /// ctor + /// + public SuggestComboBox() + { + // set the standard rules: + _filterRuleCompiled = s => s.ToLower().Contains(Text.Trim().ToLower()); + _suggestListOrderRuleCompiled = s => s; + _propertySelectorCompiled = collection => collection.Cast(); + + _suggLb.DataSource = _suggBindingList; + _suggLb.Click += SuggLbOnClick; + + ParentChanged += OnParentChanged; + } + + /// + /// the magic happens here ;-) + /// + /// + protected override void OnTextChanged(EventArgs e) + { + base.OnTextChanged(e); + + if (!Focused) return; + + _suggBindingList.Clear(); + _suggBindingList.RaiseListChangedEvents = false; + _propertySelectorCompiled(Items) + .Where(_filterRuleCompiled) + .OrderBy(_suggestListOrderRuleCompiled) + .ToList() + .ForEach(_suggBindingList.Add); + _suggBindingList.RaiseListChangedEvents = true; + _suggBindingList.ResetBindings(); + + _suggLb.Visible = _suggBindingList.Any(); + + if (_suggBindingList.Count == 1 && + _suggBindingList.Single().Length == Text.Trim().Length) + { + Text = _suggBindingList.Single(); + Select(0, Text.Length); + _suggLb.Visible = false; + } + } + + #region size and position of suggest box + + /// + /// suggest-ListBox is added to parent control + /// (in ctor parent isn't already assigned) + /// + /// + /// + private void OnParentChanged(object sender, EventArgs e) + { + Parent.Controls.Add(_suggLb); + Parent.Controls.SetChildIndex(_suggLb, 0); + _suggLb.Top = Top + Height - 3; + _suggLb.Left = Left + 3; + _suggLb.Width = Width - 20; + _suggLb.Font = new Font("Segoe UI", 9); + } + + protected override void OnLocationChanged(EventArgs e) + { + base.OnLocationChanged(e); + _suggLb.Top = Top + Height + 0; + _suggLb.Left = Left + 3; + } + + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + _suggLb.Width = Width; + } + + #endregion + + #region visibility of suggest box + + protected override void OnLostFocus(EventArgs e) + { + // _suggLb can only getting focused by clicking (because TabStop is off) + // --> click-eventhandler 'SuggLbOnClick' is called + if (!_suggLb.Focused) + HideSuggBox(); + base.OnLostFocus(e); + } + + private void SuggLbOnClick(object sender, EventArgs eventArgs) + { + Text = _suggLb.Text; + Focus(); + } + + private void HideSuggBox() + { + _suggLb.Visible = false; + } + + protected override void OnDropDown(EventArgs e) + { + HideSuggBox(); + base.OnDropDown(e); + } + + #endregion + + #region keystroke events + + /// + /// if the suggest-ListBox is visible some keystrokes + /// should behave in a custom way + /// + /// + protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e) + { + if (!_suggLb.Visible) + { + base.OnPreviewKeyDown(e); + return; + } + + switch (e.KeyCode) + { + case Keys.Down: + if (_suggLb.SelectedIndex < _suggBindingList.Count - 1) + _suggLb.SelectedIndex++; + return; + case Keys.Up: + if (_suggLb.SelectedIndex > 0) + _suggLb.SelectedIndex--; + return; + case Keys.Enter: + Text = _suggLb.Text; + Select(0, Text.Length); + _suggLb.Visible = false; + return; + case Keys.Escape: + HideSuggBox(); + return; + } + + base.OnPreviewKeyDown(e); + } + + private static readonly Keys[] KeysToHandle = new[] { Keys.Down, Keys.Up, Keys.Enter, Keys.Escape }; + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + // the keysstrokes of our interest should not be processed be base class: + if (_suggLb.Visible && KeysToHandle.Contains(keyData)) + return true; + return base.ProcessCmdKey(ref msg, keyData); + } + + #endregion + } +}