From 3ef21278cd11a8e0273ac2c1bd0ff6191dd501a1 Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Mon, 19 Jun 2023 16:29:26 +0200 Subject: [PATCH] improve samsung controller, add NLogViewer --- ColorControl.sln | 10 + ColorControl/AppContext.cs | 13 +- ColorControl/ColorControl.csproj | 11 +- ColorControl/Common/Utils.cs | 40 ++ ColorControl/Config.cs | 2 + ColorControl/MainForm.Designer.cs | 34 +- ColorControl/MainForm.cs | 6 + ColorControl/Program.cs | 17 +- .../Services/Samsung/SamTvConnection.cs | 4 + .../Services/Samsung/SamsungButtons.cs | 4 +- .../Services/Samsung/SamsungDevice.cs | 28 +- ColorControl/Services/Samsung/SamsungPanel.cs | 14 +- .../Services/Samsung/SamsungService.cs | 33 +- ColorControl/XForms/LogWindow.xaml | 40 ++ ColorControl/XForms/LogWindow.xaml.cs | 119 ++++ NLogViewer/ActionCommand.cs | 42 ++ .../Extensions/DependencyObjectExtensions.cs | 72 ++ NLogViewer/Helper/AutoSizedGridView.cs | 43 ++ .../ConverterGridViewColumn.cs | 59 ++ .../ListViewLayoutManager/FixedColumn.cs | 61 ++ .../ImageGridViewColumn.cs | 58 ++ .../ListViewLayoutManager/LayoutColumn.cs | 41 ++ .../ListViewLayoutManager.cs | 623 ++++++++++++++++++ .../ProportionalColumn.cs | 61 ++ .../ListViewLayoutManager/RangeColumn.cs | 136 ++++ NLogViewer/Helper/PredicateBuilder.cs | 28 + NLogViewer/Images/Glyphs/SortDownArrow.png | Bin 0 -> 264 bytes NLogViewer/Images/Glyphs/SortUpArrow.png | Bin 0 -> 278 bytes NLogViewer/Images/Small/Add.png | Bin 0 -> 1090 bytes NLogViewer/Images/Small/Clear.png | Bin 0 -> 475 bytes NLogViewer/Images/Small/Clock.png | Bin 0 -> 365 bytes NLogViewer/Images/Small/Context.png | Bin 0 -> 451 bytes NLogViewer/Images/Small/Debug.png | Bin 0 -> 774 bytes NLogViewer/Images/Small/DebugSource.png | Bin 0 -> 400 bytes NLogViewer/Images/Small/Error.png | Bin 0 -> 782 bytes NLogViewer/Images/Small/Exception.png | Bin 0 -> 943 bytes NLogViewer/Images/Small/Exit.png | Bin 0 -> 629 bytes NLogViewer/Images/Small/Export.png | Bin 0 -> 1196 bytes NLogViewer/Images/Small/Fatal.png | Bin 0 -> 589 bytes NLogViewer/Images/Small/Info.png | Bin 0 -> 778 bytes NLogViewer/Images/Small/Layout.png | Bin 0 -> 372 bytes NLogViewer/Images/Small/MonoLightning.png | Bin 0 -> 520 bytes NLogViewer/Images/Small/Network.png | Bin 0 -> 506 bytes NLogViewer/Images/Small/Open.png | Bin 0 -> 1227 bytes NLogViewer/Images/Small/Pause.png | Bin 0 -> 1261 bytes NLogViewer/Images/Small/Save.png | Bin 0 -> 1026 bytes NLogViewer/Images/Small/ScrollDown.png | Bin 0 -> 623 bytes NLogViewer/Images/Small/Settings.png | Bin 0 -> 1306 bytes NLogViewer/Images/Small/Thread.png | Bin 0 -> 1419 bytes NLogViewer/Images/Small/Trace.png | Bin 0 -> 631 bytes NLogViewer/Images/Small/Unknown.png | Bin 0 -> 582 bytes NLogViewer/Images/Small/Warning.png | Bin 0 -> 813 bytes NLogViewer/NLogViewer.csproj | 56 ++ NLogViewer/NLogViewer.xaml | 213 ++++++ NLogViewer/NLogViewer.xaml.cs | 590 +++++++++++++++++ NLogViewer/Resolver/ILogEventInfoResolver.cs | 12 + NLogViewer/Resolver/IdResolver.cs | 12 + NLogViewer/Resolver/LoggerNameResolver.cs | 12 + NLogViewer/Resolver/MessageResolver.cs | 21 + NLogViewer/Resolver/TimeStampResolver.cs | 12 + NLogViewer/ScrollingHelper.cs | 44 ++ NLogViewer/Targets/CacheTarget.cs | 93 +++ .../ILogEventResolverToStringConverter.cs | 23 + NLogViewer/global.json | 6 + NLogViewer/nuget.config | 12 + 65 files changed, 2675 insertions(+), 30 deletions(-) create mode 100644 ColorControl/XForms/LogWindow.xaml create mode 100644 ColorControl/XForms/LogWindow.xaml.cs create mode 100644 NLogViewer/ActionCommand.cs create mode 100644 NLogViewer/Extensions/DependencyObjectExtensions.cs create mode 100644 NLogViewer/Helper/AutoSizedGridView.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs create mode 100644 NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs create mode 100644 NLogViewer/Helper/PredicateBuilder.cs create mode 100644 NLogViewer/Images/Glyphs/SortDownArrow.png create mode 100644 NLogViewer/Images/Glyphs/SortUpArrow.png create mode 100644 NLogViewer/Images/Small/Add.png create mode 100644 NLogViewer/Images/Small/Clear.png create mode 100644 NLogViewer/Images/Small/Clock.png create mode 100644 NLogViewer/Images/Small/Context.png create mode 100644 NLogViewer/Images/Small/Debug.png create mode 100644 NLogViewer/Images/Small/DebugSource.png create mode 100644 NLogViewer/Images/Small/Error.png create mode 100644 NLogViewer/Images/Small/Exception.png create mode 100644 NLogViewer/Images/Small/Exit.png create mode 100644 NLogViewer/Images/Small/Export.png create mode 100644 NLogViewer/Images/Small/Fatal.png create mode 100644 NLogViewer/Images/Small/Info.png create mode 100644 NLogViewer/Images/Small/Layout.png create mode 100644 NLogViewer/Images/Small/MonoLightning.png create mode 100644 NLogViewer/Images/Small/Network.png create mode 100644 NLogViewer/Images/Small/Open.png create mode 100644 NLogViewer/Images/Small/Pause.png create mode 100644 NLogViewer/Images/Small/Save.png create mode 100644 NLogViewer/Images/Small/ScrollDown.png create mode 100644 NLogViewer/Images/Small/Settings.png create mode 100644 NLogViewer/Images/Small/Thread.png create mode 100644 NLogViewer/Images/Small/Trace.png create mode 100644 NLogViewer/Images/Small/Unknown.png create mode 100644 NLogViewer/Images/Small/Warning.png create mode 100644 NLogViewer/NLogViewer.csproj create mode 100644 NLogViewer/NLogViewer.xaml create mode 100644 NLogViewer/NLogViewer.xaml.cs create mode 100644 NLogViewer/Resolver/ILogEventInfoResolver.cs create mode 100644 NLogViewer/Resolver/IdResolver.cs create mode 100644 NLogViewer/Resolver/LoggerNameResolver.cs create mode 100644 NLogViewer/Resolver/MessageResolver.cs create mode 100644 NLogViewer/Resolver/TimeStampResolver.cs create mode 100644 NLogViewer/ScrollingHelper.cs create mode 100644 NLogViewer/Targets/CacheTarget.cs create mode 100644 NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs create mode 100644 NLogViewer/global.json create mode 100644 NLogViewer/nuget.config diff --git a/ColorControl.sln b/ColorControl.sln index 722c607..09a46d9 100644 --- a/ColorControl.sln +++ b/ColorControl.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvAPIWrapper", "NvAPIWrappe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "novideo_srgb", "novideo_srgb\novideo_srgb.csproj", "{A6A97834-7BE1-474A-B92F-A512DB1D5186}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogViewer", "NLogViewer\NLogViewer.csproj", "{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,14 @@ Global {A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.Build.0 = Release|Any CPU {A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.ActiveCfg = Release|x64 {A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.Build.0 = Release|x64 + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.Build.0 = Debug|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.Build.0 = Release|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.ActiveCfg = Release|Any CPU + {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ColorControl/AppContext.cs b/ColorControl/AppContext.cs index 8d57f0b..0ebcb81 100644 --- a/ColorControl/AppContext.cs +++ b/ColorControl/AppContext.cs @@ -1,4 +1,6 @@ using ColorControl.Common; +using NLog.Config; +using System; using System.ComponentModel; using System.Threading; @@ -13,12 +15,15 @@ namespace ColorControl public StartUpParams StartUpParams { get; private set; } public string DataPath { get; private set; } public SynchronizationContext SynchronizationContext { get; set; } = AsyncOperationManager.SynchronizationContext; + public LoggingRule LoggingRule { get; private set; } + public DateTime StartTime { get; private set; } = DateTime.Now; - public AppContext(Config config, StartUpParams startUpParams, string dataPath) + public AppContext(Config config, StartUpParams startUpParams, string dataPath, LoggingRule loggingRule) { Config = config; StartUpParams = startUpParams; DataPath = dataPath; + LoggingRule = loggingRule; CurrentContext = this; } @@ -27,5 +32,11 @@ namespace ColorControl { return Utils.IsServiceRunning(); } + + public void SetLogLevel(NLog.LogLevel logLevel) + { + Config.LogLevel = logLevel.ToString(); + LoggingRule.SetLoggingLevels(logLevel, NLog.LogLevel.Fatal); + } } } diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 5a89eee..0c6b14d 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -47,6 +47,9 @@ bin\x64\Release\ MinimumRecommendedRules.ruleset + + + True @@ -93,8 +96,6 @@ - - @@ -104,6 +105,12 @@ + + MSBuild:Compile + + + + diff --git a/ColorControl/Common/Utils.cs b/ColorControl/Common/Utils.cs index c2db8e0..9631d7c 100644 --- a/ColorControl/Common/Utils.cs +++ b/ColorControl/Common/Utils.cs @@ -1117,6 +1117,46 @@ The best and suggested method to provide this is via a Windows Service. Only whe return null; } + public static List ReadLines(string fileName, bool reverse = false) + { + var exists = File.Exists(fileName); + + if (!exists) + { + return null; + } + + try + { + var lines = new List(); + + using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + using var reader = new StreamReader(fs); + + string line; + + while ((line = reader.ReadLine()) != null) + { + if (reverse) + { + lines.Insert(0, line + "\r\n"); + continue; + } + + lines.Add(line); + } + + return lines; + } + catch (Exception e) + { + Logger.Error(e.ToLogString()); + } + + return null; + } + public static bool WriteObject(string fileName, object value) { var data = JsonConvert.SerializeObject(value); diff --git a/ColorControl/Config.cs b/ColorControl/Config.cs index eef87d7..1865167 100644 --- a/ColorControl/Config.cs +++ b/ColorControl/Config.cs @@ -45,10 +45,12 @@ namespace ColorControl public ElevationMethod ElevationMethod { get; set; } public bool ElevationMethodAsked { get; set; } public int ProcessMonitorPollingInterval { get; set; } + public string LogLevel { get; set; } public List Modules { get; set; } public Config() { + LogLevel = "DEBUG"; DisplaySettingsDelay = 1000; ScreenSaverShortcut = string.Empty; FormWidth = 900; diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index 86f3b3a..ca56c3b 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -41,6 +41,7 @@ lblFixChromeFontsDescription = new System.Windows.Forms.Label(); chkFixChromeFonts = new System.Windows.Forms.CheckBox(); grpGeneralOptions = new System.Windows.Forms.GroupBox(); + btnOptionsAdvanced = new System.Windows.Forms.Button(); chkOptionsUseDarkMode = new System.Windows.Forms.CheckBox(); chkAutoInstallUpdates = new System.Windows.Forms.CheckBox(); btnStartStopService = new System.Windows.Forms.Button(); @@ -70,7 +71,7 @@ label7 = new System.Windows.Forms.Label(); lblInfo = new System.Windows.Forms.Label(); btnUpdate = new System.Windows.Forms.Button(); - btnOptionsAdvanced = new System.Windows.Forms.Button(); + btnOptionsLog = new System.Windows.Forms.Button(); tcMain.SuspendLayout(); tabOptions.SuspendLayout(); grpOptionsModules.SuspendLayout(); @@ -216,6 +217,7 @@ // // grpGeneralOptions // + grpGeneralOptions.Controls.Add(btnOptionsLog); grpGeneralOptions.Controls.Add(btnOptionsAdvanced); grpGeneralOptions.Controls.Add(chkOptionsUseDarkMode); grpGeneralOptions.Controls.Add(chkAutoInstallUpdates); @@ -241,6 +243,17 @@ grpGeneralOptions.TabStop = false; grpGeneralOptions.Text = "General"; // + // btnOptionsAdvanced + // + btnOptionsAdvanced.Location = new System.Drawing.Point(7, 172); + btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnOptionsAdvanced.Name = "btnOptionsAdvanced"; + btnOptionsAdvanced.Size = new System.Drawing.Size(88, 27); + btnOptionsAdvanced.TabIndex = 11; + btnOptionsAdvanced.Text = "Advanced..."; + btnOptionsAdvanced.UseVisualStyleBackColor = true; + btnOptionsAdvanced.Click += btnOptionsAdvanced_Click; + // // chkOptionsUseDarkMode // chkOptionsUseDarkMode.AutoSize = true; @@ -591,16 +604,16 @@ btnUpdate.Visible = false; btnUpdate.Click += btnUpdate_Click; // - // btnOptionsAdvanced + // btnOptionsLog // - btnOptionsAdvanced.Location = new System.Drawing.Point(7, 172); - btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - btnOptionsAdvanced.Name = "btnOptionsAdvanced"; - btnOptionsAdvanced.Size = new System.Drawing.Size(88, 27); - btnOptionsAdvanced.TabIndex = 11; - btnOptionsAdvanced.Text = "Advanced..."; - btnOptionsAdvanced.UseVisualStyleBackColor = true; - btnOptionsAdvanced.Click += btnOptionsAdvanced_Click; + btnOptionsLog.Location = new System.Drawing.Point(103, 172); + btnOptionsLog.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnOptionsLog.Name = "btnOptionsLog"; + btnOptionsLog.Size = new System.Drawing.Size(88, 27); + btnOptionsLog.TabIndex = 68; + btnOptionsLog.Text = "Logging..."; + btnOptionsLog.UseVisualStyleBackColor = true; + btnOptionsLog.Click += btnOptionsLog_Click; // // MainForm // @@ -686,6 +699,7 @@ private System.Windows.Forms.Label label1; private System.Windows.Forms.CheckBox chkOptionsUseDarkMode; private System.Windows.Forms.Button btnOptionsAdvanced; + private System.Windows.Forms.Button btnOptionsLog; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 28b91cb..1de9884 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -9,6 +9,7 @@ using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; using ColorControl.Services.Samsung; using ColorControl.Svc; +using ColorControl.XForms; using Microsoft.Extensions.DependencyInjection; using NLog; using novideo_srgb; @@ -1501,5 +1502,10 @@ Currently ColorControl is {(Utils.IsAdministrator() ? "" : "not ")}running as ad _config.ProcessMonitorPollingInterval = processPollingIntervalField.ValueAsInt; } + + private void btnOptionsLog_Click(object sender, EventArgs e) + { + LogWindow.CreateAndShow(); + } } } \ No newline at end of file diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index 3354a1c..f705f64 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using NLog; +using NLog.Config; using NWin32; using System; using System.Diagnostics; @@ -39,6 +40,7 @@ namespace ColorControl private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static Mutex _mutex; private static MainForm _mainForm; + private static LoggingRule _loggingRule; [STAThread] // autogenerated public static void Main(string[] args) @@ -70,7 +72,7 @@ namespace ColorControl if (runAsService) { - AppContext = new AppContext(null, new StartUpParams(), DataDir); + AppContext = new AppContext(null, new StartUpParams(), DataDir, _loggingRule); await RunService(args); return; @@ -93,7 +95,7 @@ namespace ColorControl var startUpParams = StartUpParams.Parse(args); - AppContext = new AppContext(Config, startUpParams, DataDir); + AppContext = new AppContext(Config, startUpParams, DataDir, _loggingRule); var existingProcess = Utils.GetProcessByName("ColorControl"); @@ -178,7 +180,7 @@ namespace ColorControl private static void InitLogger(bool runAsService) { - var config = new NLog.Config.LoggingConfiguration(); + var config = new LoggingConfiguration(); // Targets where to log to: File and Console var logFileAppendix = runAsService ? "_svc" : ""; @@ -187,11 +189,10 @@ namespace ColorControl var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename }; - //var logconsole = new NLog.Targets.ConsoleTarget("logconsole"); + _loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile); // Rules for mapping loggers to targets - //config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole); - config.AddRule(LogLevel.Trace, LogLevel.Fatal, logfile); + config.AddRule(_loggingRule); // Apply config LogManager.Configuration = config; @@ -216,6 +217,10 @@ namespace ColorControl Config ??= new Config(); Utils.UseDedicatedElevatedProcess = Config.UseDedicatedElevatedProcess; + + var minLogLevel = LogLevel.FromString(Config.LogLevel); + + _loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal); } public static async Task HandleStartupParams(StartUpParams startUpParams, Process existingProcess) diff --git a/ColorControl/Services/Samsung/SamTvConnection.cs b/ColorControl/Services/Samsung/SamTvConnection.cs index 258f9f6..786197b 100644 --- a/ColorControl/Services/Samsung/SamTvConnection.cs +++ b/ColorControl/Services/Samsung/SamTvConnection.cs @@ -244,6 +244,10 @@ namespace ColorControl.Services.Samsung // taskCompletion.TrySetResult(new { clientKey = key }); //} + } + else + { + } //else if (_tokens.TryGetValue(id, out taskCompletion)) //{ diff --git a/ColorControl/Services/Samsung/SamsungButtons.cs b/ColorControl/Services/Samsung/SamsungButtons.cs index e3a9f08..34c4814 100644 --- a/ColorControl/Services/Samsung/SamsungButtons.cs +++ b/ColorControl/Services/Samsung/SamsungButtons.cs @@ -37,6 +37,8 @@ KEY_9, KEY_DTV, KEY_HDMI, - KEY_CONTENTS + KEY_CONTENTS, + KEY_TV_MODE, + KEY_PC_MODE } } diff --git a/ColorControl/Services/Samsung/SamsungDevice.cs b/ColorControl/Services/Samsung/SamsungDevice.cs index cab3133..9454daf 100644 --- a/ColorControl/Services/Samsung/SamsungDevice.cs +++ b/ColorControl/Services/Samsung/SamsungDevice.cs @@ -39,6 +39,8 @@ namespace ColorControl.Services.Samsung External } + public delegate void GenericDelegate(object sender); + class SamsungDevice { public class InvokableAction @@ -71,9 +73,13 @@ namespace ColorControl.Services.Samsung [JsonIgnore] public bool PoweredOn { get; private set; } + [JsonIgnore] public DateTimeOffset PoweredOffAt { get; private set; } + [JsonIgnore] public PowerOffSource PoweredOffBy { get; internal set; } + public event GenericDelegate Connected; + [JsonIgnore] private SamTvConnection _samTvConnection; @@ -188,6 +194,11 @@ namespace ColorControl.Services.Samsung finally { _connectSemaphore.Release(); + + if (true) + { + Connected?.Invoke(this); + } } } @@ -336,7 +347,7 @@ namespace ColorControl.Services.Samsung var powerState = (string)json.device?.PowerState; - Logger.Error($"Current power state: {powerState}"); + Logger.Debug($"Current power state: {powerState}"); return powerState; } @@ -563,6 +574,21 @@ namespace ColorControl.Services.Samsung return await WakeAndConnectWithRetries(powerOnRetries); } + + internal async Task> GetAppsAsync(bool force) + { + var dynamic = new + { + @event = "ed.installedApp.get", + to = "host", + }; + + var message = new RequestMessage("ms.channel.emit", dynamic); + + var result = await _samTvConnection.SendCommandAsync(message, true); + + return new List(); + } } } diff --git a/ColorControl/Services/Samsung/SamsungPanel.cs b/ColorControl/Services/Samsung/SamsungPanel.cs index 06618c6..2fec244 100644 --- a/ColorControl/Services/Samsung/SamsungPanel.cs +++ b/ColorControl/Services/Samsung/SamsungPanel.cs @@ -364,10 +364,10 @@ namespace ColorControl.Services.Samsung } } - if (cbxSamsungApps.Items.Count == 0 && device != null) - { - //_samsungService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false))); - } + //if (cbxSamsungApps.Items.Count == 0 && device != null) + //{ + // _samsungService.RefreshAppsAsync().ContinueWith((task) => BeginInvoke(() => FillApps(false))); + //} btnSamsungDeviceConvertToCustom.Enabled = devices.Any(); @@ -402,7 +402,7 @@ namespace ColorControl.Services.Samsung } } - private void FillLgApps(bool forced) + private void FillApps(bool forced) { var lgApps = _samsungService?.GetApps(); if (forced && (lgApps == null || !lgApps.Any())) @@ -410,10 +410,10 @@ namespace ColorControl.Services.Samsung MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); return; } - InitLgApps(); + InitApps(); } - private void InitLgApps() + private void InitApps() { var lgApps = _samsungService?.GetApps(); diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs index d7b3532..d513651 100644 --- a/ColorControl/Services/Samsung/SamsungService.cs +++ b/ColorControl/Services/Samsung/SamsungService.cs @@ -37,6 +37,7 @@ namespace ColorControl.Services.Samsung private bool _poweredOffByScreenSaver; private int _poweredOffByScreenSaverProcessId; private object _lastTriggeredPreset; + private List _samsungApps = new List(); public List Devices { get; private set; } @@ -184,6 +185,8 @@ namespace ColorControl.Services.Samsung var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0]; SelectedDevice = preferredDevice; + + SelectedDevice.Connected += SelectedDevice_Connected; } else { @@ -216,6 +219,16 @@ namespace ColorControl.Services.Samsung } } + private async void SelectedDevice_Connected(object sender) + { + if (_samsungApps.Any()) + { + return; + } + + //await RefreshAppsAsync(); + } + protected override List GetDefaultPresets() { return new List(); @@ -430,11 +443,27 @@ namespace ColorControl.Services.Samsung } } - internal List GetApps() + public async Task RefreshAppsAsync(bool force = false) { - return new List(); + if (SelectedDevice == null) + { + Logger.Debug("Cannot refresh apps: no device has been selected"); + return; + } + + var apps = await SelectedDevice.GetAppsAsync(force); + + if (apps.Any()) + { + _samsungApps.Clear(); + _samsungApps.AddRange(apps); + } } + internal List GetApps() + { + return _samsungApps; + } public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, CancellationToken token) { diff --git a/ColorControl/XForms/LogWindow.xaml b/ColorControl/XForms/LogWindow.xaml new file mode 100644 index 0000000..ea2fe17 --- /dev/null +++ b/ColorControl/XForms/LogWindow.xaml @@ -0,0 +1,40 @@ + + + + + Log level: + + Trace + Debug + Info + Warning + Error + Fatal + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ColorControl/XForms/LogWindow.xaml.cs b/ColorControl/XForms/LogWindow.xaml.cs new file mode 100644 index 0000000..a6c3b14 --- /dev/null +++ b/ColorControl/XForms/LogWindow.xaml.cs @@ -0,0 +1,119 @@ +using ColorControl.Common; +using NLog; +using System; +using System.ComponentModel; +using System.IO; +using System.Windows; +using System.Windows.Navigation; + +namespace ColorControl.XForms +{ + public partial class LogWindow : Window + { + private static LogWindow _window; + + private int _logLevelIndex; + + public int LogLevelIndex { get => _logLevelIndex; set => _logLevelIndex = value; } + + public LogWindow() + { + InitializeComponent(); + + LogLevelIndex = LogLevel.FromString(AppContext.CurrentContext.Config.LogLevel).Ordinal; + + DataContext = this; + } + + private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri) + { + UseShellExecute = true, + }; + System.Diagnostics.Process.Start(processStartInfo); + } + + public static void CreateAndShow(bool show = true) + { + if (Application.Current == null) + { + new Application(); + } + + _window ??= new LogWindow(); + + if (show) + { + _window.WindowState = WindowState.Normal; + _window.Show(); + _window.Topmost = true; + _window.Topmost = false; + } + } + + protected override void OnClosing(CancelEventArgs e) + { + e.Cancel = true; + + Hide(); + } + + protected override void OnStateChanged(EventArgs e) + { + if (WindowState == WindowState.Minimized) + { + Hide(); + } + + base.OnStateChanged(e); + } + + private void RawLog_Click(object sender, RoutedEventArgs e) + { + var context = AppContext.CurrentContext; + var logFile = Path.Combine(context.DataPath, "LogFile.txt"); + + Utils.StartProcess(logFile); + } + + private void LoadOlder_Click(object sender, RoutedEventArgs e) + { + var context = AppContext.CurrentContext; + var logFile = Path.Combine(context.DataPath, "LogFile.txt"); + + var lines = Utils.ReadLines(logFile); + + logViewer.LoadLines(lines); + } + + private void Close_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + private void LogLevel_Changed(object sender, System.Windows.Controls.SelectionChangedEventArgs e) + { + var context = AppContext.CurrentContext; + + var logLevel = LogLevel.FromOrdinal(_logLevelIndex); + + context.SetLogLevel(logLevel); + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + if (logViewer.HasEvents()) + { + return; + } + + var context = AppContext.CurrentContext; + var logFile = Path.Combine(context.DataPath, "LogFile.txt"); + + var lines = Utils.ReadLines(logFile); + + logViewer.LoadLines(lines, context.StartTime); + } + } +} \ No newline at end of file diff --git a/NLogViewer/ActionCommand.cs b/NLogViewer/ActionCommand.cs new file mode 100644 index 0000000..67db679 --- /dev/null +++ b/NLogViewer/ActionCommand.cs @@ -0,0 +1,42 @@ +using System; +using System.Windows.Input; + +namespace DJ +{ + public sealed class ActionCommand : ICommand + { + private readonly Action _Action; + private readonly Action _ObjectAction; + + public ActionCommand(Action action) + { + _Action = action; + } + + public ActionCommand(Action objectAction) + { + _ObjectAction = objectAction; + } + + private event EventHandler CanExecuteChanged; + + event EventHandler ICommand.CanExecuteChanged + { + add => CanExecuteChanged += value; + remove => CanExecuteChanged -= value; + } + + bool ICommand.CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + if (_ObjectAction != null) + _ObjectAction(parameter); + else + _Action(); + } + } +} \ No newline at end of file diff --git a/NLogViewer/Extensions/DependencyObjectExtensions.cs b/NLogViewer/Extensions/DependencyObjectExtensions.cs new file mode 100644 index 0000000..8843d89 --- /dev/null +++ b/NLogViewer/Extensions/DependencyObjectExtensions.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; + +namespace DJ.Extensions +{ + public static class DependencyObjectExtensions + { + /// + /// Analyzes both visual and logical tree in order to find all elements of a given + /// type that are descendants of the item. + /// + /// The type of the queried items. + /// The root element that marks the source of the search. If the + /// source is already of the requested type, it will not be included in the result. + /// The UID of the + /// All descendants of that match the requested type. + public static T FindChildByUid(this DependencyObject source, string uid) where T : UIElement + { + if (source != null) + { + var childs = GetChildObjects(source); + foreach (DependencyObject child in childs) + { + //analyze if children match the requested type + if (child != null && child is T dependencyObject && dependencyObject.Uid.Equals(uid)) + { + return dependencyObject; + } + + var descendant = FindChildByUid(child, uid); + if (descendant != null) + return descendant; + } + } + + return null; + } + + /// + /// This method is an alternative to WPF's + /// method, which also + /// supports content elements. Keep in mind that for content elements, + /// this method falls back to the logical tree of the element. + /// + /// The item to be processed. + /// The submitted item's child elements, if available. + public static IEnumerable GetChildObjects(this DependencyObject parent) + { + if (parent == null) yield break; + + if (parent is ContentElement || parent is FrameworkElement) + { + //use the logical tree for content / framework elements + foreach (object obj in LogicalTreeHelper.GetChildren(parent)) + { + var depObj = obj as DependencyObject; + if (depObj != null) yield return (DependencyObject) obj; + } + } + else + { + //use the visual tree per default + int count = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + yield return VisualTreeHelper.GetChild(parent, i); + } + } + } + } +} diff --git a/NLogViewer/Helper/AutoSizedGridView.cs b/NLogViewer/Helper/AutoSizedGridView.cs new file mode 100644 index 0000000..ea2459e --- /dev/null +++ b/NLogViewer/Helper/AutoSizedGridView.cs @@ -0,0 +1,43 @@ +using System; +using System.Reactive.Linq; +using System.Threading; +using System.Windows.Controls; +using NLog; + +namespace DJ.Helper +{ + /// + /// Represents a view mode that displays data items in columns for a System.Windows.Controls.ListView control with auto sized columns based on the column content + /// Used to fix the column width: https://stackoverflow.com/questions/60147905/column-width-adjustment-is-broken-if-using-multibinding-on-displaymemberbinding + /// + public class AutoSizedGridView : GridView + { + private int _MaxLoggerNameLength; + + protected override void PrepareItem(ListViewItem item) + { + if (item.DataContext is LogEventInfo info) + { + if (info.LoggerName.Length > _MaxLoggerNameLength) + { + _MaxLoggerNameLength = info.LoggerName.Length; + Observable.Timer(TimeSpan.FromMilliseconds(1)).ObserveOn(SynchronizationContext.Current).Subscribe(l => + { + foreach (GridViewColumn column in Columns) + { + //setting NaN for the column width automatically determines the required width enough to hold the content completely. + //if column width was set to NaN already, set it ActualWidth temporarily and set to NaN. This raises the property change event and re computes the width. + if (double.IsNaN(column.Width)) + { + column.Width = column.ActualWidth; + column.Width = double.NaN; + } + } + }); + } + } + + base.PrepareItem(item); + } + } +} diff --git a/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs new file mode 100644 index 0000000..54febf0 --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs @@ -0,0 +1,59 @@ +using System; +using System.Globalization; +using System.Windows.Controls; +using System.Windows.Data; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class ConverterGridViewColumn : GridViewColumn, IValueConverter + { + public Type BindingType => _BindingType; + private readonly Type _BindingType; + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + protected ConverterGridViewColumn(Type bindingType) + { + if (bindingType == null) + { + throw new ArgumentNullException(nameof(bindingType)); + } + + this._BindingType = bindingType; + + Binding binding = new Binding {Mode = BindingMode.OneWay, Converter = this}; + DisplayMemberBinding = binding; + } + + #endregion + + // ############################################################################################################################## + // IValueConverter + // ############################################################################################################################## + + #region IValueConverter + + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!_BindingType.IsInstanceOfType(value)) + { + throw new InvalidOperationException(); + } + + return ConvertValue(value); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + protected abstract object ConvertValue(object value); + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs new file mode 100644 index 0000000..5a64ab5 --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs @@ -0,0 +1,61 @@ +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public sealed class FixedColumn : LayoutColumn + { + public static bool IsFixedColumn(GridViewColumn column) + { + if (column == null) + { + return false; + } + + return HasPropertyValue(column, WidthProperty); + } + + public static double? GetFixedWidth(GridViewColumn column) + { + return GetColumnWidth(column, WidthProperty); + } + + public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double width) + { + SetWidth(gridViewColumn, width); + return gridViewColumn; + } + + // ############################################################################################################################## + // AttachedProperties + // ############################################################################################################################## + + #region AttachedProperties + + public static double GetWidth(DependencyObject obj) + { + return (double) obj.GetValue(WidthProperty); + } + + public static void SetWidth(DependencyObject obj, double width) + { + obj.SetValue(WidthProperty, width); + } + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached("Width", typeof(double), typeof(FixedColumn)); + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + private FixedColumn() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs new file mode 100644 index 0000000..cf81e4a --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class ImageGridViewColumn : GridViewColumn, IValueConverter + { + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + protected ImageGridViewColumn(Stretch imageStretch) + { + FrameworkElementFactory imageElement = new FrameworkElementFactory(typeof(Image)); + + Binding imageSourceBinding = new Binding {Converter = this, Mode = BindingMode.OneWay}; + imageElement.SetBinding(Image.SourceProperty, imageSourceBinding); + + Binding imageStretchBinding = new Binding {Source = imageStretch}; + imageElement.SetBinding(Image.StretchProperty, imageStretchBinding); + + DataTemplate template = new DataTemplate {VisualTree = imageElement}; + CellTemplate = template; + } + + protected ImageGridViewColumn() : this(Stretch.None) + { + } + + #endregion + + // ############################################################################################################################## + // IValueConverter + // ############################################################################################################################## + + #region IValueConverter + + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return GetImageSource(value); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + protected abstract ImageSource GetImageSource(object value); + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs new file mode 100644 index 0000000..d94d28d --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs @@ -0,0 +1,41 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class LayoutColumn + { + protected static bool HasPropertyValue(GridViewColumn column, DependencyProperty dp) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + object value = column.ReadLocalValue(dp); + if (value?.GetType() == dp.PropertyType) + { + return true; + } + + return false; + } + + protected static double? GetColumnWidth(GridViewColumn column, DependencyProperty dp) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + object value = column.ReadLocalValue(dp); + if (value?.GetType() == dp.PropertyType) + { + return (double) value; + } + + return null; + } + } +} diff --git a/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs b/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs new file mode 100644 index 0000000..a850ed9 --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs @@ -0,0 +1,623 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; + +namespace DJ.Helper.ListViewLayoutManager +{ + public class ListViewLayoutManager + { + // ############################################################################################################################## + // Properties + // ############################################################################################################################## + + #region Properties + + // ########################################################################################## + // Public Properties + // ########################################################################################## + + public ListView ListView => _ListView; + + public ScrollBarVisibility VerticalScrollBarVisibility + { + get => _VerticalScrollBarVisibility; + set => _VerticalScrollBarVisibility = value; + } + + // ########################################################################################## + // Private Properties + // ########################################################################################## + + private readonly ListView _ListView; + private ScrollViewer _ScrollViewer; + private bool _Loaded; + private bool _Resizing; + private Cursor _ResizeCursor; + private ScrollBarVisibility _VerticalScrollBarVisibility = ScrollBarVisibility.Auto; + private GridViewColumn _AutoSizedColumn; + + private const double _ZERO_WIDTH_RANGE = 0.1; + + #endregion + + // ############################################################################################################################## + // AttachedProperties + // ############################################################################################################################## + + #region AttachedProperties + + public static void SetEnabled(DependencyObject dependencyObject, bool enabled) + { + dependencyObject.SetValue(EnabledProperty, enabled); + } + + public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", + typeof(bool), typeof(ListViewLayoutManager), new FrameworkPropertyMetadata(_OnLayoutManagerEnabledChanged)); + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + public ListViewLayoutManager(ListView listView) + { + _ListView = listView ?? throw new ArgumentNullException(nameof(listView)); + _ListView.Loaded += ListView_Loaded; + _ListView.Unloaded += ListView_Unloaded; + } + + private void ListView_Loaded(object sender, RoutedEventArgs e) + { + _RegisterEvents(_ListView); + _InitColumns(); + _DoResizeColumns(); + _Loaded = true; + } + + + private void ListView_Unloaded(object sender, RoutedEventArgs e) + { + if (!_Loaded) + { + return; + } + + _UnRegisterEvents(_ListView); + _Loaded = false; + } + + #endregion + + // ############################################################################################################################## + // public methods + // ############################################################################################################################## + + #region public methods + + public void Refresh() + { + _InitColumns(); + _DoResizeColumns(); + } + + protected virtual void ResizeColumns() + { + GridView view = _ListView.View as GridView; + if (view == null || view.Columns.Count == 0) + { + return; + } + + // listview width + double actualWidth = double.PositiveInfinity; + if (_ScrollViewer != null) + { + actualWidth = _ScrollViewer.ViewportWidth; + } + + if (double.IsInfinity(actualWidth)) + { + actualWidth = _ListView.ActualWidth; + } + + if (double.IsInfinity(actualWidth) || actualWidth <= 0) + { + return; + } + + double resizeableRegionCount = 0; + double otherColumnsWidth = 0; + // determine column sizes + foreach (GridViewColumn gridViewColumn in view.Columns) + { + if (ProportionalColumn.IsProportionalColumn(gridViewColumn)) + { + double? proportionalWidth = ProportionalColumn.GetProportionalWidth(gridViewColumn); + if (proportionalWidth != null) + { + resizeableRegionCount += proportionalWidth.Value; + } + } + else + { + otherColumnsWidth += gridViewColumn.ActualWidth; + } + } + + if (resizeableRegionCount <= 0) + { + // no proportional columns present: commit the regulation to the scroll viewer + if (_ScrollViewer != null) + { + _ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + } + + // search the first fill column + GridViewColumn fillColumn = null; + for (int i = 0; i < view.Columns.Count; i++) + { + GridViewColumn gridViewColumn = view.Columns[i]; + if (_IsFillColumn(gridViewColumn)) + { + fillColumn = gridViewColumn; + break; + } + } + + if (fillColumn != null) + { + double otherColumnsWithoutFillWidth = otherColumnsWidth - fillColumn.ActualWidth; + double fillWidth = actualWidth - otherColumnsWithoutFillWidth; + if (fillWidth > 0) + { + double? minWidth = RangeColumn.GetRangeMinWidth(fillColumn); + double? maxWidth = RangeColumn.GetRangeMaxWidth(fillColumn); + + bool setWidth = !(minWidth.HasValue && fillWidth < minWidth.Value); + if (maxWidth.HasValue && fillWidth > maxWidth.Value) + { + setWidth = false; + } + + if (setWidth) + { + if (_ScrollViewer != null) + { + _ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden; + } + + fillColumn.Width = fillWidth; + } + } + } + + return; + } + + double resizeableColumnsWidth = actualWidth - otherColumnsWidth; + if (resizeableColumnsWidth <= 0) + { + return; // missing space + } + + // resize columns + double resizeableRegionWidth = resizeableColumnsWidth / resizeableRegionCount; + foreach (GridViewColumn gridViewColumn in view.Columns) + { + if (ProportionalColumn.IsProportionalColumn(gridViewColumn)) + { + double? proportionalWidth = ProportionalColumn.GetProportionalWidth(gridViewColumn); + if (proportionalWidth != null) + { + gridViewColumn.Width = proportionalWidth.Value * resizeableRegionWidth; + } + } + } + } + + #endregion + + // ############################################################################################################################## + // private methods + // ############################################################################################################################## + + #region private methods + + private void _DoResizeColumns() + { + if (_Resizing) + { + return; + } + + _Resizing = true; + try + { + ResizeColumns(); + } + finally + { + _Resizing = false; + } + } + + private void _RegisterEvents(DependencyObject start) + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) + { + Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual; + if (childVisual is Thumb) + { + GridViewColumn gridViewColumn = _FindParentColumn(childVisual); + if (gridViewColumn != null) + { + Thumb thumb = childVisual as Thumb; + if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || + FixedColumn.IsFixedColumn(gridViewColumn) || _IsFillColumn(gridViewColumn)) + { + thumb.IsHitTestVisible = false; + } + else + { + thumb.PreviewMouseMove += _ThumbPreviewMouseMove; + thumb.PreviewMouseLeftButtonDown += + _ThumbPreviewMouseLeftButtonDown; + DependencyPropertyDescriptor.FromProperty( + GridViewColumn.WidthProperty, + typeof(GridViewColumn)).AddValueChanged(gridViewColumn, _GridColumnWidthChanged); + } + } + } + else if (childVisual is GridViewColumnHeader) + { + GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader; + columnHeader.SizeChanged += _GridColumnHeaderSizeChanged; + } + else if (_ScrollViewer == null && childVisual is ScrollViewer) + { + _ScrollViewer = childVisual as ScrollViewer; + _ScrollViewer.ScrollChanged += _ScrollViewerScrollChanged; + // assume we do the regulation of the horizontal scrollbar + _ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden; + _ScrollViewer.VerticalScrollBarVisibility = _VerticalScrollBarVisibility; + } + + _RegisterEvents(childVisual); + } + } + + private void _UnRegisterEvents(DependencyObject start) + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) + { + Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual; + if (childVisual is Thumb) + { + GridViewColumn gridViewColumn = _FindParentColumn(childVisual); + if (gridViewColumn != null) + { + Thumb thumb = childVisual as Thumb; + if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || + FixedColumn.IsFixedColumn(gridViewColumn) || _IsFillColumn(gridViewColumn)) + { + thumb.IsHitTestVisible = true; + } + else + { + thumb.PreviewMouseMove -= _ThumbPreviewMouseMove; + thumb.PreviewMouseLeftButtonDown -= + _ThumbPreviewMouseLeftButtonDown; + DependencyPropertyDescriptor.FromProperty( + GridViewColumn.WidthProperty, + typeof(GridViewColumn)).RemoveValueChanged(gridViewColumn, _GridColumnWidthChanged); + } + } + } + else if (childVisual is GridViewColumnHeader) + { + GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader; + columnHeader.SizeChanged -= _GridColumnHeaderSizeChanged; + } + else if (_ScrollViewer == null && childVisual is ScrollViewer) + { + _ScrollViewer = childVisual as ScrollViewer; + _ScrollViewer.ScrollChanged -= _ScrollViewerScrollChanged; + } + + _UnRegisterEvents(childVisual); + } + } + + private GridViewColumn _FindParentColumn(DependencyObject element) + { + if (element == null) + { + return null; + } + + while (element != null) + { + GridViewColumnHeader gridViewColumnHeader = element as GridViewColumnHeader; + if (gridViewColumnHeader != null) + { + return (gridViewColumnHeader).Column; + } + + element = VisualTreeHelper.GetParent(element); + } + + return null; + } + + private GridViewColumnHeader _FindColumnHeader(DependencyObject start, GridViewColumn gridViewColumn) + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) + { + Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual; + if (childVisual is GridViewColumnHeader) + { + GridViewColumnHeader gridViewHeader = childVisual as GridViewColumnHeader; + if (gridViewHeader.Column == gridViewColumn) + { + return gridViewHeader; + } + } + + GridViewColumnHeader childGridViewHeader = _FindColumnHeader(childVisual, gridViewColumn); + if (childGridViewHeader != null) + { + return childGridViewHeader; + } + } + + return null; + } + + private void _InitColumns() + { + GridView view = _ListView.View as GridView; + if (view == null) + { + return; + } + + foreach (GridViewColumn gridViewColumn in view.Columns) + { + if (!RangeColumn.IsRangeColumn(gridViewColumn)) + { + continue; + } + + double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn); + double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn); + if (!minWidth.HasValue && !maxWidth.HasValue) + { + continue; + } + + GridViewColumnHeader columnHeader = _FindColumnHeader(_ListView, gridViewColumn); + if (columnHeader == null) + { + continue; + } + + double actualWidth = columnHeader.ActualWidth; + if (minWidth.HasValue) + { + columnHeader.MinWidth = minWidth.Value; + if (!double.IsInfinity(actualWidth) && actualWidth < columnHeader.MinWidth) + { + gridViewColumn.Width = columnHeader.MinWidth; + } + } + + if (maxWidth.HasValue) + { + columnHeader.MaxWidth = maxWidth.Value; + if (!double.IsInfinity(actualWidth) && actualWidth > columnHeader.MaxWidth) + { + gridViewColumn.Width = columnHeader.MaxWidth; + } + } + } + } + + // returns the delta + private double _SetRangeColumnToBounds(GridViewColumn gridViewColumn) + { + double startWidth = gridViewColumn.Width; + + double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn); + double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn); + + if ((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth)) + { + return 0; // invalid case + } + + if (minWidth.HasValue && gridViewColumn.Width < minWidth.Value) + { + gridViewColumn.Width = minWidth.Value; + } + else if (maxWidth.HasValue && gridViewColumn.Width > maxWidth.Value) + { + gridViewColumn.Width = maxWidth.Value; + } + + return gridViewColumn.Width - startWidth; + } + + private bool _IsFillColumn(GridViewColumn gridViewColumn) + { + if (gridViewColumn == null) + { + return false; + } + + GridView view = _ListView.View as GridView; + if (view == null || view.Columns.Count == 0) + { + return false; + } + + bool? isFillColumn = RangeColumn.GetRangeIsFillColumn(gridViewColumn); + return isFillColumn.HasValue && isFillColumn.Value; + } + + private void _ThumbPreviewMouseMove(object sender, MouseEventArgs e) + { + Thumb thumb = sender as Thumb; + if (thumb == null) + { + return; + } + + GridViewColumn gridViewColumn = _FindParentColumn(thumb); + if (gridViewColumn == null) + { + return; + } + + // suppress column resizing for proportional, fixed and range fill columns + if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || + FixedColumn.IsFixedColumn(gridViewColumn) || + _IsFillColumn(gridViewColumn)) + { + thumb.Cursor = null; + return; + } + + // check range column bounds + if (thumb.IsMouseCaptured && RangeColumn.IsRangeColumn(gridViewColumn)) + { + double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn); + double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn); + + if ((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth)) + { + return; // invalid case + } + + if (_ResizeCursor == null) + { + _ResizeCursor = thumb.Cursor; // save the resize cursor + } + + if (minWidth.HasValue && gridViewColumn.Width <= minWidth.Value) + { + thumb.Cursor = Cursors.No; + } + else if (maxWidth.HasValue && gridViewColumn.Width >= maxWidth.Value) + { + thumb.Cursor = Cursors.No; + } + else + { + thumb.Cursor = _ResizeCursor; // between valid min/max + } + } + } + + private void _ThumbPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + Thumb thumb = sender as Thumb; + GridViewColumn gridViewColumn = _FindParentColumn(thumb); + + // suppress column resizing for proportional, fixed and range fill columns + if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || + FixedColumn.IsFixedColumn(gridViewColumn) || + _IsFillColumn(gridViewColumn)) + { + e.Handled = true; + } + } + + private void _GridColumnWidthChanged(object sender, EventArgs e) + { + if (!_Loaded) + { + return; + } + + GridViewColumn gridViewColumn = sender as GridViewColumn; + + // suppress column resizing for proportional and fixed columns + if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || FixedColumn.IsFixedColumn(gridViewColumn)) + { + return; + } + + // ensure range column within the bounds + if (RangeColumn.IsRangeColumn(gridViewColumn)) + { + // special case: auto column width - maybe conflicts with min/max range + if (gridViewColumn != null && gridViewColumn.Width.Equals(double.NaN)) + { + _AutoSizedColumn = gridViewColumn; + return; // handled by the change header size event + } + + // ensure column bounds + if (Math.Abs(_SetRangeColumnToBounds(gridViewColumn) - 0) > _ZERO_WIDTH_RANGE) + { + return; + } + } + + _DoResizeColumns(); + } + + // handle autosized column + private void _GridColumnHeaderSizeChanged(object sender, SizeChangedEventArgs e) + { + if (_AutoSizedColumn == null) + { + return; + } + + GridViewColumnHeader gridViewColumnHeader = sender as GridViewColumnHeader; + if (gridViewColumnHeader != null && gridViewColumnHeader.Column == _AutoSizedColumn) + { + if (gridViewColumnHeader.Width.Equals(double.NaN)) + { + // sync column with + gridViewColumnHeader.Column.Width = gridViewColumnHeader.ActualWidth; + _DoResizeColumns(); + } + + _AutoSizedColumn = null; + } + } + + private void _ScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e) + { + if (_Loaded && Math.Abs(e.ViewportWidthChange - 0) > _ZERO_WIDTH_RANGE) + { + _DoResizeColumns(); + } + } + + private static void _OnLayoutManagerEnabledChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs e) + { + ListView listView = dependencyObject as ListView; + if (listView != null) + { + bool enabled = (bool) e.NewValue; + if (enabled) + { + new ListViewLayoutManager(listView); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs new file mode 100644 index 0000000..f43fea7 --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs @@ -0,0 +1,61 @@ +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public sealed class ProportionalColumn : LayoutColumn + { + public static bool IsProportionalColumn(GridViewColumn column) + { + if (column == null) + { + return false; + } + + return HasPropertyValue(column, WidthProperty); + } + + public static double? GetProportionalWidth(GridViewColumn column) + { + return GetColumnWidth(column, WidthProperty); + } + + public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double width) + { + SetWidth(gridViewColumn, width); + return gridViewColumn; + } + + // ############################################################################################################################## + // AttachedProperties + // ############################################################################################################################## + + #region AttachedProperties + + public static double GetWidth(DependencyObject obj) + { + return (double) obj.GetValue(WidthProperty); + } + + public static void SetWidth(DependencyObject obj, double width) + { + obj.SetValue(WidthProperty, width); + } + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached("Width", typeof(double), typeof(ProportionalColumn)); + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + private ProportionalColumn() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs b/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs new file mode 100644 index 0000000..66df102 --- /dev/null +++ b/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs @@ -0,0 +1,136 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + + + public sealed class RangeColumn : LayoutColumn + { + + + public static readonly DependencyProperty MinWidthProperty = + DependencyProperty.RegisterAttached( + "MinWidth", + typeof( double ), + typeof( RangeColumn ) ); + + + public static readonly DependencyProperty MaxWidthProperty = + DependencyProperty.RegisterAttached( + "MaxWidth", + typeof( double ), + typeof( RangeColumn ) ); + + + public static readonly DependencyProperty IsFillColumnProperty = + DependencyProperty.RegisterAttached( + "IsFillColumn", + typeof( bool ), + typeof( RangeColumn ) ); + + + private RangeColumn() + { + } // RangeColumn + + + public static double GetMinWidth( DependencyObject obj ) + { + return (double)obj.GetValue( MinWidthProperty ); + } // GetMinWidth + + + public static void SetMinWidth( DependencyObject obj, double minWidth ) + { + obj.SetValue( MinWidthProperty, minWidth ); + } // SetMinWidth + + + public static double GetMaxWidth( DependencyObject obj ) + { + return (double)obj.GetValue( MaxWidthProperty ); + } // GetMaxWidth + + + public static void SetMaxWidth( DependencyObject obj, double maxWidth ) + { + obj.SetValue( MaxWidthProperty, maxWidth ); + } // SetMaxWidth + + + public static bool GetIsFillColumn( DependencyObject obj ) + { + return (bool)obj.GetValue( IsFillColumnProperty ); + } // GetIsFillColumn + + + public static void SetIsFillColumn( DependencyObject obj, bool isFillColumn ) + { + obj.SetValue( IsFillColumnProperty, isFillColumn ); + } // SetIsFillColumn + + + public static bool IsRangeColumn( GridViewColumn column ) + { + if ( column == null ) + { + return false; + } + return + HasPropertyValue( column, MinWidthProperty ) || + HasPropertyValue( column, MaxWidthProperty ) || + HasPropertyValue( column, IsFillColumnProperty ); + } // IsRangeColumn + + + public static double? GetRangeMinWidth( GridViewColumn column ) + { + return GetColumnWidth( column, MinWidthProperty ); + } // GetRangeMinWidth + + + public static double? GetRangeMaxWidth( GridViewColumn column ) + { + return GetColumnWidth( column, MaxWidthProperty ); + } // GetRangeMaxWidth + + + public static bool? GetRangeIsFillColumn( GridViewColumn column ) + { + if ( column == null ) + { + throw new ArgumentNullException( nameof(column) ); + } + object value = column.ReadLocalValue( IsFillColumnProperty ); + if ( value != null && value.GetType() == IsFillColumnProperty.PropertyType ) + { + return (bool)value; + } + + return null; + } // GetRangeIsFillColumn + + + public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth, + double width, double maxWidth ) + { + return ApplyWidth( gridViewColumn, minWidth, width, maxWidth, false ); + } // ApplyWidth + + + public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth, + double width, double maxWidth, bool isFillColumn ) + { + SetMinWidth( gridViewColumn, minWidth ); + gridViewColumn.Width = width; + SetMaxWidth( gridViewColumn, maxWidth ); + SetIsFillColumn( gridViewColumn, isFillColumn ); + return gridViewColumn; + } // ApplyWidth + + } // class RangeColumn + +} // namespace Itenso.Windows.Controls.ListViewLayout +// -- EOF ------------------------------------------------------------------- diff --git a/NLogViewer/Helper/PredicateBuilder.cs b/NLogViewer/Helper/PredicateBuilder.cs new file mode 100644 index 0000000..b69f93d --- /dev/null +++ b/NLogViewer/Helper/PredicateBuilder.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace DJ.Helper +{ + public static class PredicateBuilder + { + public static Expression> True () { return f => true; } + public static Expression> False () { return f => false; } + + public static Expression> Or (this Expression> expr1, + Expression> expr2) + { + var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast ()); + return Expression.Lambda> + (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters); + } + + public static Expression> And (this Expression> expr1, + Expression> expr2) + { + var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast ()); + return Expression.Lambda> + (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters); + } + } +} diff --git a/NLogViewer/Images/Glyphs/SortDownArrow.png b/NLogViewer/Images/Glyphs/SortDownArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5a1a581bb500878cb6b24fed7382b0e54cb89f GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CJ!3HGRcAO0XQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`6_P!Id>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4NyY7 z#5JNMI6tkVJh3R1As{g`uSCz!HAJDzSkH8?>*A9@CCQ#Hjv*Ss$q5U%Goor9*c(c` zlbGFD$X;ir;zxShj ziV{cj4GU4_>{~UDuGI^ut&oTmzS!kB%VD1LM}?pmMux6yoiCESUV*G*@O1TaS?83{ F1OU^4TV4PF literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Glyphs/SortUpArrow.png b/NLogViewer/Images/Glyphs/SortUpArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..3c0cae48df463b294a40a90426f97518cd9a93d3 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CJ!3HGRcAO0XQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`6_P!Id>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4NyY7 z#5JNMI6tkVJh3R1As{g`uSCz!HAJDzSkH8?>*A9@C54_Yjv*Ss$q5VCGNNi8*mDS{ zig`SICcOQ?j0I{Fn!dW{|Ed4{Q(U6?hJ|UP$~BpN|Ng%_z5f4t``@I2UpN2& literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Add.png b/NLogViewer/Images/Small/Add.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f4cd9ddbe630840e1a43610758b384b72cc778 GIT binary patch literal 1090 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl-tmOGnnF)=VO@do&W zxB|r`Q@Ny4xaBKE<%@(RtAr#9_{D1k#EN*N>UqT~xhz&QXv}4>U(2OGpUZHTpvDwI z;c5o{3I+ya1_m7l;aDccLUGMXMa?Qjr6LJ|5C-mK#-PPMHTxDR`8NYZBvp@lZY#oDYD}!bi zgLXHAPCrA$VX=VSLR!5H+C2=aZ45H?417fl3_=VHDhv#&3=GP^KxL3DW(YsY>VJrp zGm?Qpf`LH|7_MMagh6c@L;NL%u+t2JjSL#oSc|SpWnPpLZe*72;7Hu75wlx^uZE2~ zpFyRU!DJ4DQV#=j1_Of@1A`p{gA)S-Fh*>Ealp`bv9IfVSL50Gy3^G)X9_D$ z<-OXZcdG3-EL(3_2yHkd2uu*oB|(0m1cM0}1tlf<8HHyaKin2m22RKS1x+9RRd4K@m7oba@xZCs4PvN8gUvQGUbKsRs}ctjQhX%8@VJDCj(IEGuE zE{-7*my;71nA`#l3pqVC)z~BwrcIkTb#iz@BliX^Ez|89t2Z2Ey%Bmf>h={=iw8_| zW<|Y~y=4`X(6?^hzQ%h@p{{shO3Dv95usm4SiEV~1)K4Y~O#nQ4`{HH7Z_aU7_D!PC{xWt~$(698}j Ba1Q_g literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Clear.png b/NLogViewer/Images/Small/Clear.png new file mode 100644 index 0000000000000000000000000000000000000000..375b8bf6a09803b8fcee625fc5f38a949b5a1439 GIT binary patch literal 475 zcmV<10VMv3P)rCP_(<-a*jb_Hyre&wGyNNooK906+!+3lsI|p?tm?Ex#0xHV4K10Z9#DVK`pc ze*7w-lmAYiqVQ^K;s*qy{gNXl3K__@vT?o(V&K zcsjVVE#qaQIuQ57;gih|+H#dz(D2^m2pQGkc(XiPHv7qxcY;izAvom~vS-bB@7UaK zL)NX}h(uIcx2`~1o@>ob`KK>8W8A9k5@R}Q0cp50?UzkmTM})nYN>`oEg%iMGT-&} z_59V;^#>H9fVBA5b5zGCT|Ep8R4PHGL|Q!gtm(MZas<&1m0H7?fb{C)mF3rRxlv=@ zIH+Z>+^mjrjf~>_+vVTI zy0UHU5%!JGx)ekz-aozi-Es3zrOt`&vaJe*^)|wc3AS7fJ2Rzr#^iT0Y><0p#L@Gv z|t%b z_|oeaLz}LIhjjO4UX_M3^$qjapPTn|$Jq-DW__7ir(4B*=jVaKXXn0^0{z6`>FVdQ I&MBb@08n?0Qvd(} literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Context.png b/NLogViewer/Images/Small/Context.png new file mode 100644 index 0000000000000000000000000000000000000000..7d79c0ad388dd8acdac94336c71f75439efba9cf GIT binary patch literal 451 zcmV;!0X+VRP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^XO*0N*(00A>eL_t(IPmPl?FGFDzhA$C=se_IN5{sdO zZUzGhf5VRw1B0o1+d(Qwx^ysMG)VXXW`cz0x$cqE-Zu3nPkQf{^SxQ==g4R|+W%H{`fXdE8p*=@fIZ%_-FG&`=2?HPn(-4U;a#GfgO tLV4QqH_su*E_j=L$mV%6J literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Debug.png b/NLogViewer/Images/Small/Debug.png new file mode 100644 index 0000000000000000000000000000000000000000..2d5fb90ec6ee08f53947e0266a87b03f75893446 GIT binary patch literal 774 zcmV+h1Nr=kP) zlgUq0Q544c8(ae&UR$8ps&snq6^bPY3v3xAmMW74Di$h~GCH6E3TaYs2#6A<7K*gC z777H71_Wa;(dfp+g-drPCSWu)#PInZi72LJ;o?i~$-U=y&UbQ89Dul3%3P+Axkzc* zbH-y;QF=hR{qLItf%ci2_&e5wNo0gnVatG?ul6Zw=o$I9Ljfn*ic3`U?>IfEim3g{ zujU&$-hy6wn;w(xme|zJm;lWJxtTFfM)q0`kX!Vu0+d${$}LCddK1<^htTe-fUYL3 zB`SdNsZD>RgvLj1<^@h6_+cDRK2Brcr2~>%$*5S)hyV33PV^teac3%|4lz@8p4?)5 z?t5o^?q+%^%)Yygo~I^U4VR!bTnWuE35hcWrfCDR3q+sxJ79e7Fg`&)RCqLA^2^y^ z0laVfadW90_Fz8Brm|r47sB^u1VgI>kanj)Z4`zMSfHlm8>CwXa$JVM`$2RrmZB-3 zN10m-!;BvH*Br3V8t`DH7m`jf#2upVDXl{5ff18_pzCPK1Zu$$CKKvd8FGeFf)+K<|x33pc7P&S#3GZT4mEw;nr(Ze*F z3&*?-4U-lm*#tber5 z%S_ceqB`b3ko6r~BbvDwdohTvP(3a(pq{x#T$yQsu#OKwEe}KuH^Mh@nxg_(Nw136 zq#a^3xNBke)In+!?qk3%4wB69{pF`Tzg`07*qoM6N<$ Eg55P&8UO$Q literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/DebugSource.png b/NLogViewer/Images/Small/DebugSource.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4fe4fc8fddfcc7ebd835282f2783d20aff98aa GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv>0#LT=By}Z;C1rt33 zJ=4@yqg0@wjGiuzAr}5~C)sixGLUF%=HqJGVHI7nN%b%D+Y2X!r{w8X@rNI^ezC0D z*4Z{dU*T8X)9fC$tzVnjFYU2klG&OXDSQ9B@P<4Or^tu9@}5=R`zx=ZyNo65jXZmtDL3i{|ILV|O^Vt3 zxwU{U*3-Fe#rSur^54sCi8sKs_BFSsM?3CGbl=EpP`xLf+9*bP$m8&)|oK5jG5 qIy}?q!3*suhSEq=%_UP-v!`agHon9+`!+DZ7(8A5T-G@yGywoC#+ljx literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Error.png b/NLogViewer/Images/Small/Error.png new file mode 100644 index 0000000000000000000000000000000000000000..3e5ae9f710121753895a07b08d02e5054cd753f6 GIT binary patch literal 782 zcmV+p1M&QcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGVx&QzHx&i2`0R{j702p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n80)I(FK~y+Tt&?3yQ(+j#4@#C{7waNN+J$se zLst>5+?qC{PaUp_g%VksYZ#Jdq|?D_QGT=~(p+p>peM2XKoSkHkBcm$IFR8CKf~n* z5o=*V=#D%8-ZSA0LA>(7;eBtO-}8U||L3Ks_5Sc{F8N1ayLsYqHRGzUX1>^NF~e1b z^i;vMMA{+**(M zwmP7>0&tcB_bvfOEl`jIT#UnXmJ}~D=lNC}(B%N0T7fsuz;RsQ zN8@cEM+$#>G$vA`(Vi-0T%B}=03kEevrVLk@5~p|mEeZn3jppz50D*;C2bUZN5avo zibiv;+9?2d!}ys4vKx$mgWTUe4+46dz@6I#HZ2khs*M;_M8G1C=@tOI+q_|@&I9i2 z0gDdktP76dG{Ap02JA@@77|17Js}h~_CFH&|Oc~4Po$~^~U!Vl55o7V_ zX8cu9n3RR!dYt&P02r<;WW3i>mfgOpB4;9ypFq^)gR961%+8+Jf(0dop9vIR?+-(n zR2)<=)o7(hNzS}cd|b$m16P$RC}0g5EGC7*E2l6cqhObW!>&;TP2{FW3FW&*-?|Js z{hIHAofwMm(52NpQ0`<(_C|dzij5pIO1F*GrpTUra#&8S_v5Ah15ek*em$svuK)l5 M07*qoM6N<$g2^^rkN^Mx literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Exception.png b/NLogViewer/Images/Small/Exception.png new file mode 100644 index 0000000000000000000000000000000000000000..bc2ec8429477901e1bf908862b32926bccf8464e GIT binary patch literal 943 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl480Q7}gt!8^^XJcBuwcRT>C?At z*|KZbt{F3COrAVBI5^nT({tj)i4!JFm@;LGlarI3o!zWivkn|MaOlvXg+8F3-`>xkKOH=H0O(m&RaJR;c_}F=Wo2a(6O$=Z zCV%?$@&5gL_4V~Y8;y;Pw6(Rx#KdfEZ6-~cxN+l#_3PKon>TOCl10;|O)f1h0eVM7 zL_|YF!_d&s#>S?wuwc`s4RhwqQczG37Z(>25>i)JkBp31xNu%lQUcH~Iy%}wkB5W= z2L$*>M@OcnCb_w}8X6gfM}!Fq3R+oPX=!PRii%D&by*CIA-j?wKVXbu05(!_CY2t=0W`O+EA2d|h?L_4DQ9?f)|@cgS^>WfOdwsU## zlF7;5*@+3WSI*>Pp1yOITSCZ`2OC z7#SFv=o%X88d!uFnp>F~S(#es8kkub82nn>&x@iVH$NpatrE9}?hoc0ff^V*UHx3v IIVCg!01jeZTL1t6 literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Exit.png b/NLogViewer/Images/Small/Exit.png new file mode 100644 index 0000000000000000000000000000000000000000..cf87d2c786d63a38b660aae36682a3c45c7c42d8 GIT binary patch literal 629 zcmV-*0*d{KP)L55e zb=0X{n;;dWh#fmrk|9v-rr^*)aIp{(OtuE8bu)H|yq2IKS}dZu$-VD7q|F~vBt3B9 zp6`C&x#xUGe7MXF1CI?%a*PnLCE%VYO|o@rED{?&H;!dHeTjXo*!!~0&z;Igr>=>G z1{`GREojx>_qIbQ9O0WLW=RFotI&{&hJuCy`%@ny#SosR0(xh^8s~p2y~oUtGL-i`Tgm4)yF;Gb)32zh`7ENr=KsAvhvQcDEw7De(;=u{b zRU7PH-~JmKc(1|HnsUWw;;iVuhz52IXhYetHS<+i_Ta*CI&k1~5a#~@`!MuT8?2?3 P00000NkvXXu0mjf=QS9< literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Export.png b/NLogViewer/Images/Small/Export.png new file mode 100644 index 0000000000000000000000000000000000000000..35f3c3965e1f33b117d933530d483d68e23f19de GIT binary patch literal 1196 zcmZ`%YfO`86#iht3FyE99paXmnI+1C6tn|bT!0j{lOZbz8$tq7O4+O~(6p3EH=H3W zDh>qNB4j9A0+XA{WhfVGX-k1#2;~l?rO=j-wzP$|^fK66vLD-zJ<0i=@4W9h&->)$ z&=V8RAM^_Hf*|N1<^mcE*w+>N-vHm^AwEff-pq~pA_jt<-uHH9c|edmHZeXq4xCeq zigtH*hlhuyQt9;6)F4kR5|0D4x3@PnHONy+Y&Ki1R!>Y!2m}HKgQ3xAW@l%)TrQKz1ph*zkk9AW z*SnN~!>v7-&1S3BYOz>=AK)1j)YjH!WMlxtJRT3ELqkJAwynK=U8~jVbin_H!2pU) znnFrU*{-8UIscm5czcq%Gu9^M2(X0y$<-OnA*qBRs$_0k%6 zC5)>3cS#tK-s zu!3U7Q|SqXT??drwzXm`B#O&mx(zq{~t0R(ybxxx*qtqTMN zJ{WXNa^OUErd;{1um3v;1me)406#`1F0-<#vZkW?L26<$7Mt>A4n7l`bm^jacx*%@ z5ROhuaBg0N0GvM}fKgRbol02m2uH5LIVmZZr=?Sq5vSoJ9bw4BL8!~9p3d$r3Wv7A{aCAg zKalxeeM(zeKVJ93eB_hs`Q)s(y#8#P?aNv3)_qFW&*@YmH-E#-bLyy!=&*bcsaabU zdiopOf@fY5Cm(SJXglc#qD!D(%8BR8_wDm2I0bpc#&TXC`>^qG_ElwJV6?l@lTN2& zFoF}!eMdiozR6ENd%MEg59aj|Defy0E{l{Cm7Sjh2nt7roen{s2?;-w92ORZiikQD n8G=MbA(6BbL4^M_5O3h}@YMfr@F6{xf(8f^mw;}I&AjzDyqKrp literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Fatal.png b/NLogViewer/Images/Small/Fatal.png new file mode 100644 index 0000000000000000000000000000000000000000..c26cd440c3bc9cdd4962242b7553ee54a8ad44f4 GIT binary patch literal 589 zcmV-T0Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy7j#8fbW?9;ba!ELWdKlNX>N2bPDNB8b~7$Dy+xzR0004~Nkl!kPZ-~Y!3 zJUl!u0Ts64Gytf%1ea#0fgj@IVvwG^uhR8&+Jp1E=b#SHW?scvcc zZ)9XthSdNyHMQB>w{QOsl>9HQuKs`i>h=H6T)6VzFD?!&791Y^zrMcyzlDWG7FGka zw6uDArcC+o?H~NVee(4GhfkmW4{`y>fa>~||JKgV{|_8E@ZZwXG6Smt4h{~n&!0d4 z4>AO#dBcVc|Mm6t|9}7f{r`*=EB^03eE2^|-ptG_4`*5cYJLO@1dxp&13(%<02Dqz zK6d}ZOtiPRH->4LvtS9>6?Yyy0edB@sOUd1v321~%FI9mp6BQ1gEb&ykl#V65U6<* z-n^itt=$V!42mw0zd;xjCLlvW7_VlSB+!}FAU`9!VE+91Am?`zWdKMKC}V-*9E3qm b$E6tnua>Nicm^zE00000NkvXXu0mjf6v6p8 literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Info.png b/NLogViewer/Images/Small/Info.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd1aef900803abba99b26920337ec01ad5c267 GIT binary patch literal 778 zcmV+l1NHogP)BVme|mWaqy4$_pJm?y9KM{-*hp?1+Ey3e-CEDooTa!B;e(Q>TSF?bj>5At13y1p zriN3w3x~5SfZj{@J4M{kp{?=M_Lh2bV+5LH)Q)5W!-ePA$RgE1@5f1cyHki0Y}JyVEYZF(LD$xXlt$7A5CgE@ zpV-&l%vf;=5kZ2-2gi@Y6J&=cuwt>!vJ^#(&n|LcZyUzi6Duj$$hJ1s*HD-#;k-w@ zpdrwAuoDG_N2bvb07G$Zk*?Hc)JLtW4yqOnic_$zO7NZ#l>Fm){;fE?b$IbOaX2fe z0la4g0Dfw2xk7Wi7NapVD8YMPCZu?A1QCK*67dgsvRKBLFtrM>?$%&_lD1882mzdO zWPdw5KWw6IT`m1b_8=lS5jt8D3=RDa=&jWzR-)S@56WMslZ~mKu1)-wpXB>rNBQ>N zU#K`#1B&v|_AQK;7I~B}OdGiUT9LX>f0xm6<;LeP!=vFjPsUQF*wCJ*dO)4YBypgdiuF!=i@6Zyi7F|q#K zz?tlSZULa@t1D?$e;f@b36&N!V2mjOHw|*P_N#{XBQPuD9>UDFT=LaHsAT!i5 zj^krd6dxw+EX$5EO%tVQ8kq>p^UM^-@llB^%85RxlO%ar_x29v;9>=a3#F!2Ez6>= z>uBBE1MYzzJeVLi5ZChHFMc3DWIk<43SJ(=KVR$oG*LCkM z9ALMmX{apA)2$4mD0&sO@0oyY+uDH&2o|pjIF5s?_`(6|--L*{&HyTCh{)M1YexeHTUh2A*s;W-d`7eMw-}m3y^So!Z&y$G*^%9?q5Ah#-3ornObN8$* SQn5e)0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv0004ZNklTl+$WWV26 zr_)irURSA9a{Scb(E&V80I%8Yb`226N4Z>9yWO@tS|?yM8p-oKHJeS6}&vs9z)5_=bDi(_tgrPy}{73{* zVE>RDAlt}hvj%Y0YSkPbnlE$wO&~ci_DC3DPVLXj6u%t*xz0bPV-~&v5-6Sk0000< KMNUMnLSTXdW8547 literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Network.png b/NLogViewer/Images/Small/Network.png new file mode 100644 index 0000000000000000000000000000000000000000..98024504d0bb8a4fb9a2441a3d273d05179d805b GIT binary patch literal 506 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;js(W8VM(0c%M_K~y+TjZ-^r!axu;#)V}YJNO64mh8$B zoFH8|Knkc*CP&~1R6$V+4v>PHiZVqCZbBC+w;=CrW(~1O%#%hto_X_jc6M!CLLA4d z;c&PajYi7oyRN&CbUU&v+lX5u2!f@!?clEB*68>9*9)X+N&)NV%P@+fuyKUz1$w>S zeW%k|(z@NQUZ;|7hv#{(uswvfShraBzVE+D`hT!po|lGM%=0{-bAYtTWa0_!gd|BA zoJxyowJhtFIf&g0%mdSgQmyNkd;53kHLM z$~CW%HGc}6J({|M=0l=zEr9lRBybn5j^pfvR|_!4oaV7c32;!D?&Gw{K8CjC)EzvYX$Jy8j(F=GiG6D2Za>y1zWLT(=K;NIN0!)@T)jQy z!3VfvixU~Z!P730XHTvF_;d{-qS`#9WJ^)mF}a8;6Om;il1xZ~=dgg-@f#uh!soY( zeX<)|>AhPMgorA(k5Ww4Kx7R>nuo~q5Jdw~RAMs3jccondC_0eHhAT{d}jgVq8R)| zD=(jsoE_vxEzC5lCz^-bFG$$v<;7=jR-m4Gx=h4aR15wxcFlDWRZnSZ8QZ86%!mjI zL6kyTtW(y?8bb#-cnO~&6Xy(au-%oIrjnCVK1EV}viaWEx0tBLvd|9p=?->eM|F66 z6~6ruPE;8rdKf4y4`?e35SID3!7TM_EcPzTcHXlK!H99lJL|%tBUyB37X1(_)rpnz z1uMnzTCyW6+3`GazvrQCCvTgdx`T+U1yQx&Odm*sK$;RTrhraSp?gvNa;k1&`iWLi zC7Y;goJinJMR6wx?IT%}vYt7$O0Qqk>kSLLn5Oex^YNTCkv9NT-(7L?9d!>DBn*DWYs%S}(ryseb>HmJdH+AkAP2Ejacaw}7 zo?(`~I9=H(^o(Oas4T;=o3$?lYicCw;1 zS}q{Sn?mK1i{o9)u@3sEFma@XG}06^%#Rr2;fJ`mp?d6K%b9^@+CUSz|4Dp*Lv$ZE zqK|`zYJ;UUr=)+t49e_4uKB#hf_$cIm#e z4+f3KxLBCkCa(&E8jP<@rWJv`^%cdsLAeg?xjZTTOusQ1)4Q>*EMMP{FxTFeV3$_u zE|ZIByIP&*J#@l@2q*7<;cSTv?zs*hw`O3XGGNF2qX21&7XW6Kfqp(=pN>+9L?TtC zC6T`c33hQ_m(9XYRQwo=!$kUp-L!D*bUM60D)xW_vInuN=;yl;zjN?mq2VC}ynJYk zJR}<)uRS+(9Cg&~n5+8{0g>0pZ%#_(H$6FbHi@2+#h^0MjV}#)F ze9Z~ZK2_nGAv1Vn=myqIRMT=Ck1?!v`0hS3z9D@)d2hU>_ojP_oHN5q8`UDC1@x^} zR4be54vMZ`d2!V^c}3NEwFsBbGhMsBW3R^#hBr6vvaw}_1=NBYqmV%Z1B@&{v@KbUea0(uM%P#)0V{ z$)u8)G*3!84L0D0ay^Pfc_7_9!d%@vUEMv8cpy>ko+#9C%QWXd0s1*=Qo@D*4?NT) Qd;|wTpg-2H-j|sBH!5FnMF0Q* literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Pause.png b/NLogViewer/Images/Small/Pause.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfefa8d34d5126d9c7cf76b39150bece436f42a GIT binary patch literal 1261 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl4m{tb(gt!8^3|!jGRudQ)*wnWD zXW-UkVBnUYewR_ukjJA^=l_5H^c74(&MZ1OR}&7rO*`@-aQ!`l)*bvFl|XxV+)CAY4jIh6 zBpB8OO05fRSV?uXDkrN+rXsc#buqwS-71&dq1mtJ$G;iOUxQp-#+e$ zNi5zIxkD$iy0r6zPGGTXVRdQY@oD7Pc;S;OeRirIZSgK;l|<@A8^t&z=# z*!)&)Idtd8_umIkKjQUR$Y4E_!DcG6aXEu@GJ|SGK<={e+FfjJ3$`4%b>PH39_tAV z+6@dEWeno+3{sf^sk6R({t;HRmd&hZ%ie4Ij@;zYsbG*!XH?E&P%C9nDi6q799q1A z&1%w?eK+irv&lbi^}DxUL&~?aInUdA@b;fyfA$=?!)4aZVARgw zGTShE+1gz<*YCY+l)Zx~a6`@9^Sh5esGfM3#cn!ti5CN~8s<)(S??jBGWqeJe;>Acu$Z5D zlgFxf@`p595;Yga29w(76WMyFm^kcZO6dCDDCOu7$R|*QGoINKaevRg+)Zg z#3dx9m_R%RW)^7~S-Hd{c?Cr!WmaIWVpma3PDxEm&&bSDQ`g{N;MCO0&dJToFDNW3 zF45NEV$jtqEi136tg5c5t*dX)H()R{YHVt5X>DuosO;?OHa1}}HS6i^Yw4dbaZ=^v zDO0DJGgwTYF>_W+|Li$)=gnWRaFHd0)#4>fm$mdSU$JuDs?}@OS~J+#u3NvMW#gvJ zTjp)uwta^kgT2GfUAtR0?%BI<-u?pz9i14QT@D>Sa@e?Ocoj&90#=!0FarWH# z3l}e4zH;@NrqG-s?PsvQH#H}Iu*6T))-JY&~F6*2UngHZc@HYSe literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Save.png b/NLogViewer/Images/Small/Save.png new file mode 100644 index 0000000000000000000000000000000000000000..a6cf4c901ea6562af4007f0ff0b4d3872e86cefb GIT binary patch literal 1026 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47%v9+gt!8^eWz};ow(k6{z3cM z`^{$`HJ^T1apZ30(R+D&ZshH`nY{UO@|MdHtIzo?I_bOkq|>Y;<`eguPuj23xl6HO zi(syrK|a~<_lym=sI?F`sIgg>f!mo8nteBr`{b7#(+K7D50x$Cp8 zKkYkzzi8{x&eL~h-g&#^?%P9WE?mBHG`&u;!U9UNKFK6F_CFdSKeDUVNi?{19JbeEa1Xi5B`})=M=m=4bxwPhbU zJ3UWouFQNXnf5G)=Z)C3nUOawcUJynZFLP3li`_j&FrY^($u9-MW=>d-BuREbLShM z>DJP(UwM0d&+77hv*Fp(=eIWZt;2zopr069qp0ssI2 literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/ScrollDown.png b/NLogViewer/Images/Small/ScrollDown.png new file mode 100644 index 0000000000000000000000000000000000000000..5891219e49603b6409d5479247ef1fa8a8cf71c2 GIT binary patch literal 623 zcmV-#0+9WQP)9KeakxIdTpje7cQjq<2c!^ z`Zm_SltDZPAt&#frRYyLt2KB&-G(%{q(iVQLTLEmE7;x%Q2E-zMx_cKcr*5=`6qt_ zJ8$Qx>E38HYSm`*8wpg%DHAF6Ak@qu=tdr9ZV&@0hK{|pLTM+EAS1zES!j~^>gMV) zSJgD~g*+^)fTA^sp+W%}T|?#b>p<{m+*@08kigJI_+;7}FTlfl=m%yDpG=!Ydu;Nd zxN@f?(x%l?jNxPW8<>{Ae&FyaVOl4ItmVe>FR)CpL^X>&_z8O)@9BsigdhL_002ov JPDHLkV1jC@76SkP literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Settings.png b/NLogViewer/Images/Small/Settings.png new file mode 100644 index 0000000000000000000000000000000000000000..a4fd920dd2659c454cc92011cc5acf14f5217007 GIT binary patch literal 1306 zcmaJ>YfMvT7(UQ4E|Ybg0>a29P7TgAwm>T|B(Q;kGAICw^Aw6wIgwl-+d+}sQ}cDvnbwE}s#wOB0O-QDTw=`AfSot>T4)zw2o zLvTJfH#a*wJ2Nu_;J}l`VnG6E92^{k5;!*)41ioyQ?qH)rktD{pfo-{4h33`hR5U8 z*Vot8)v?)Z3WWk4l}aVQcQEZ-Ik5U?>2$j3=^3R`g?7%Il+5Rf?}HqZ3B;M31)^Qq zqC-M6>$)YY?QUsliSD7!WHbTD*}Sukgkv5lP04L0w8MOyH`LhJ2v7#S0VV()0o*Bo z=|Sfu3yci0@km3zUatpV`uioI7c!fgn(%l$n2Zg~@}OU(@r@ZG!?9K=hyvA+Gchs2 z=ko~!0)lmS3Oea=xRoZf9_yF_7i=~gbOA>W95{fu`d}%!Ug@G>?#R9$JIcz+z(gQG zrBV^^5VtUn$JcCU1OYo>j4&&e>VbMfBb6(vyYE{qR&t2~#DbyZVwuTkhG|oZWt0+m zZ$OU=Xc*=s#jk&g|F_I3-&j*HIQJbrVlT$4w}lv=hQuv(<@k$# zN)vcb4*Om%{s4Wo_WTbWVQcFJV&B@@Gmf)=kMZrxpD*){;)rs^PA69f^4QvoOYWhH zHPh7ZKe#zWs7(iHw&!PE9HO<0N3lojt0$a7WgV6mY!_$N@=`ih@U%%+iE}-(IvvA% z`MBO|!}%xo_kQs`)AOU82+ZtV$*;iP0X?`s3R{zHPJ|cNyqMJQAjbT?_l=(lhXj0V zRAlmI9kZ5B&17VvXjWoUO5!0V3#A%0ljw0KYBXr|x;?}oltH49$n&$Aq+j|5`fuI1 z$z-56&gXILC_CT0`grNt-1w+W_H^;-yvn{jvU>~BKN#yjD%t(#RO5{HxD8m6COPFamy^sX${zM(-J*Geh87w0;j zDp_vfe|aWcT2fZ!xjXf9xoq3;>DQHkyXd6r8It^|i{7=TGWVRe4P1C%YRI*o?+Yw0 zwoRlDy+RPRn6@AZ^ZX5_w4D=un3H*&!wO}xSrCu_A}NSK3?T%BB$EO{gMvc?DFh-p llt^q(t+@5Cg4{EiC$ld6cfms}Y5gyV#74(Q^?b!B`3p<%eO>?n literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Thread.png b/NLogViewer/Images/Small/Thread.png new file mode 100644 index 0000000000000000000000000000000000000000..22bcf3aa01699c3fdc6cc0bac00ef0eee0eae5ab GIT binary patch literal 1419 zcmZ`%dr;B`9H%vHYr1SrM}-+FDUY8D@=yfR6yKQmz(>>53QcFDP%AAf(tKp8fdXbe z01+Lj6+TeNw-%vT`I-jGmPKpM-R-t(yW5TZ+JC# z3kvrfX?Wo%G?43*T)oXdYs_NV$>8peDsc=f{}g%J9&r}!EeK@|#GadqIzN8o(p2JA zJ@bN&maalE>cjJfm?hJ3l{&ge8{0CQRQK#CZ!U@dJow5m^>Pn`|AbkocWY}U_V)P= z4;>mG4lN&vlRS%QnMJ1EA#%ICZ};Lmdz_|)@Ohs1D+T%WJ>R!Ixb+T-S{>c z%NhIx%J$9e4y=BNlC@$crS3+t)ADuntDEj`Ydl`x#+${2g--v05qi~RME(fILx`da z0}E6!ylMN1VmAZ-;A#owrxKF+6#0j%)VDQQGY|KD8`Y$u<~|6?)A$|j2I3nIUsAzD zwXnxkE-!8(mU6LcSr|hU`8CU9Ez{*i7Gb?La6=QK(*_r5sA*j;M`}Rl@Wuv&z7ANg zMjB4J8xx_EDCC?k?z=d*I%7{%TLeU!3Z;_(h-L@kBW?bg!t#Z<}ul}f_;1>{N^d_D|k zigjH{L9FI`y=ua(v?JT4#4`$PvSdHGoSdPEubj5&EOeW{?YwlwL3eE5O!|SROqYdB z)M`0$jZb*lK`wki$ZkhQ3O=+-K{5nB1zm{xW@k+ue4!k%R^_!Ja5vu~ypf{k8(qg6 zXcH6UQYCPrWrrhY7ydjvjfX4~dv$g~$BUukrO?M^@VP3$&;%W*a27UU`CX{o=H2vC z_(>kAxSPS7vAC14bQ)fusD)5OzgYE7GRDcj06RVz&f@iZ_#-)X-~SX0Z(I0o68u{+79z>0=o*~dHBHP_MUDS@#OM?(^h zG&dG=xpN~=AAdVBKW!L${$yoJP&uk!9et)7Oq z+%CFY$-&ts8~b66liaGqE^fW-oy v-f>tGnE<92;7KHVdhRQ>Q~pbEihUw0qu~Dq?A7)ipa2p^iwf#GoObnZ-(`7? literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Trace.png b/NLogViewer/Images/Small/Trace.png new file mode 100644 index 0000000000000000000000000000000000000000..330e7f8541d63c941be419e50ad5b9eedd88e043 GIT binary patch literal 631 zcmV--0*L*IP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy8FWQhbW?9;ba!ELWdKlNX>N2bPDNB8b~7$DE;K%k%ys|(0nkZA zK~y+Ty^_1jv0)s?{SQjYAR9(8*ced4VsR`IQWjuW|Qytd_F0w zB9qBbC=@UnjlS-Fzu%Md`Aimz1(L}mI2L%lUi{#4xg=t-7z%{~8jS|rc7*O+OrqE8 z(d~9|zuzebO%QE5ol=QJ0=Zlc2ADGuCSYCJfKsUh1N7%*v92E})U!LebL zO2rGP)oRpgwYQh`csvZZ+f4}Vhm+Ikq*AFAp->3(`TYIR-wvMZ{%;RC;ZLJ)Ckzjo R?(zTt002ovPDHLkV1k*X59t5^ literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Unknown.png b/NLogViewer/Images/Small/Unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..74f0f574216cc00b4b0dd870d021e038ac0e6f7d GIT binary patch literal 582 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah2>S z4={E+nQh0wz}VsG;uxZF-gctB#}!5fxB2^jADpQfQ=(e2^!e2QSrZP$wXMP4lTBE3 z1-X)Vb$8AP_7KSxlobqgW;qzhr+ZPLu{AQV|8?(Et^Afv zy@EzM8$DZP_$@sBxl0%>oOY>Dn<~M@#u1TxQUCQ+_Za2-lgcJ{hU)z|=o`9P&pJ7N zuFh|UMQ4s^h3iHa9bD5N`(b_P``H-_xb&B-AI2S*Um95e|YWR zuSKiA&oOztBT)F<>iaU&(mzyQ*|aKE`<3+Xp5H-hE92_eRVJ4(U2ttleHZk=g1upa zN}`Sm$Bk3!=O!lic$P5X`g?rOnwuF^cDVGs&8?k# z{R5w0OLp+v&UrJ+j_g^*n|~`d#pY1|vvo_AKPvd1U^BhMbJ?}oa@o(RPpuBf$$h)F z>}=%LmDggwwl(|46;!-lQ>W+VFWV*m)yBKRt5&T m20(_v`TYwwplHa=PsvQH#I0f9BS{{h1_n=8KbLh*2~7Z^spY-^ literal 0 HcmV?d00001 diff --git a/NLogViewer/Images/Small/Warning.png b/NLogViewer/Images/Small/Warning.png new file mode 100644 index 0000000000000000000000000000000000000000..7616208a6083d3ac2e06ccf466ae17455fd10524 GIT binary patch literal 813 zcmV+|1JeA7P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@Gd8K`;w1n80-i}kK~y+Tjgo6flTjGQ&)&W7hHh()7TehvzxZ|92kFIfSgROtmD0f4k2$))S4EYT5h344?BW^)6c< zs3(^k{C50UI0chIDd@Af@7j|Q+?pVkW}>!ss0KyP5dd*i8kFX6t~(iA>Y4ywx=8l) zaEu@#5xgxFf*2}xH*r6Uvsufk09z*s9_bD;LCC_x>f_k9br(wXyXh1vl;ucWSG9p> z{|hlUkn|aA0ySJm@`XbnBmt-657K-++Upn;Wy*(E1PnYQs^L~mujtOFhBW94_kbZ< zJkal?xf=6!1xj?BTbHDXSr#(*&PnuO0Lf51WDb_8H@PzHLL?zF`4h`%6q1372! zk8&-U4+DZ4{U3dyjf=$ikPH|jaPjNlacsoXyNsBhrmX)1m@^W!t + + + net7.0-windows10.0.20348.0 + true + DJ + true + Sentinel.NLogViewer + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + diff --git a/NLogViewer/NLogViewer.xaml b/NLogViewer/NLogViewer.xaml new file mode 100644 index 0000000..664f7fc --- /dev/null +++ b/NLogViewer/NLogViewer.xaml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NLogViewer/NLogViewer.xaml.cs b/NLogViewer/NLogViewer.xaml.cs new file mode 100644 index 0000000..9c556ce --- /dev/null +++ b/NLogViewer/NLogViewer.xaml.cs @@ -0,0 +1,590 @@ +using DJ.Extensions; +using DJ.Resolver; +using DJ.Targets; +using NLog; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; + +namespace DJ +{ + /// + /// Interaktionslogik für NLogViewer.xaml + /// + public partial class NLogViewer : UserControl + { + // ############################################################################################################################## + // Dependency Properties + // ############################################################################################################################## + + #region Dependency Properties + + // ########################################################################################## + // Colors + // ########################################################################################## + + #region Colors + + /// + /// The background for the trace output + /// + [Category("NLogViewerColors")] + public Brush TraceBackground + { + get => (Brush)GetValue(TraceBackgroundProperty); + set => SetValue(TraceBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TraceBackgroundProperty = + DependencyProperty.Register("TraceBackground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#D3D3D3")))); + + /// + /// The foreground for the trace output + /// + [Category("NLogViewerColors")] + public Brush TraceForeground + { + get => (Brush)GetValue(TraceForegroundProperty); + set => SetValue(TraceForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TraceForegroundProperty = + DependencyProperty.Register("TraceForeground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#042271")))); + + /// + /// The background for the debug output + /// + [Category("NLogViewerColors")] + public Brush DebugBackground + { + get => (Brush)GetValue(DebugBackgroundProperty); + set => SetValue(DebugBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty DebugBackgroundProperty = + DependencyProperty.Register("DebugBackground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#90EE90")))); + + /// + /// The foreground for the debug output + /// + [Category("NLogViewerColors")] + public Brush DebugForeground + { + get => (Brush)GetValue(DebugForegroundProperty); + set => SetValue(DebugForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty DebugForegroundProperty = + DependencyProperty.Register("DebugForeground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#042271")))); + + /// + /// The background for the info output + /// + [Category("NLogViewerColors")] + public Brush InfoBackground + { + get => (Brush)GetValue(InfoBackgroundProperty); + set => SetValue(InfoBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty InfoBackgroundProperty = DependencyProperty.Register("InfoBackground", + typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#0000FF")))); + + /// + /// The foreground for the info output + /// + [Category("NLogViewerColors")] + public Brush InfoForeground + { + get => (Brush)GetValue(InfoForegroundProperty); + set => SetValue(InfoForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty InfoForegroundProperty = DependencyProperty.Register("InfoForeground", + typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.White)); + + /// + /// The background for the warn output + /// + [Category("NLogViewerColors")] + public Brush WarnBackground + { + get => (Brush)GetValue(WarnBackgroundProperty); + set => SetValue(WarnBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty WarnBackgroundProperty = DependencyProperty.Register("WarnBackground", + typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#FFFF00")))); + + /// + /// The foreground for the warn output + /// + [Category("NLogViewerColors")] + public Brush WarnForeground + { + get => (Brush)GetValue(WarnForegroundProperty); + set => SetValue(WarnForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty WarnForegroundProperty = DependencyProperty.Register("WarnForeground", + typeof(Brush), typeof(NLogViewer), + new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#324B5C")))); + + /// + /// The background for the error output + /// + [Category("NLogViewerColors")] + public Brush ErrorBackground + { + get => (Brush)GetValue(ErrorBackgroundProperty); + set => SetValue(ErrorBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty ErrorBackgroundProperty = + DependencyProperty.Register("ErrorBackground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata(Brushes.Red)); + + /// + /// The foreground for the error output + /// + [Category("NLogViewerColors")] + public Brush ErrorForeground + { + get => (Brush)GetValue(ErrorForegroundProperty); + set => SetValue(ErrorForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty ErrorForegroundProperty = + DependencyProperty.Register("ErrorForeground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata(Brushes.White)); + + /// + /// The background for the fatal output + /// + [Category("NLogViewerColors")] + public Brush FatalBackground + { + get => (Brush)GetValue(FatalBackgroundProperty); + set => SetValue(FatalBackgroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty FatalBackgroundProperty = + DependencyProperty.Register("FatalBackground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata(Brushes.Black)); + + /// + /// The foreground for the fatal output + /// + [Category("NLogViewerColors")] + public Brush FatalForeground + { + get => (Brush)GetValue(FatalForegroundProperty); + set => SetValue(FatalForegroundProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty FatalForegroundProperty = + DependencyProperty.Register("FatalForeground", typeof(Brush), typeof(NLogViewer), + new PropertyMetadata(Brushes.Yellow)); + + #endregion + + // ########################################################################################## + // NLogViewer + // ########################################################################################## + + #region NLogViewer + + /// + /// Is looking if any target with this name is configured and tries to link it + /// + [Category("NLogViewer")] + public string TargetName + { + get => (string)GetValue(TargetNameProperty); + set => SetValue(TargetNameProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TargetNameProperty = DependencyProperty.Register("TargetName", typeof(string), typeof(NLogViewer), new PropertyMetadata(null)); + + /// + /// Private DP to bind to the gui + /// + [Category("NLogViewer")] + public CollectionViewSource LogEvents + { + get => (CollectionViewSource)GetValue(LogEventsProperty); + private set => SetValue(LogEventsProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty LogEventsProperty = DependencyProperty.Register("LogEvents", + typeof(CollectionViewSource), typeof(NLogViewer), new PropertyMetadata(null)); + + /// + /// Automatically scroll to the newest entry + /// + [Category("NLogViewer")] + public bool AutoScroll + { + get => (bool)GetValue(AutoScrollProperty); + set => SetValue(AutoScrollProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.Register("AutoScroll", typeof(bool), typeof(NLogViewer), new PropertyMetadata(true, AutoScrollChangedCallback)); + + private static void AutoScrollChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args) + { + if (d is NLogViewer instance) + { + instance.OnAutoScrollChanged(); + } + } + + protected virtual void OnAutoScrollChanged() + { + if (AutoScroll) + ListView?.ScrollToEnd(); + } + + /// + /// Delele all entries + /// + [Category("NLogViewer")] + public ICommand ClearCommand + { + get => (ICommand)GetValue(ClearCommandProperty); + set => SetValue(ClearCommandProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty ClearCommandProperty = DependencyProperty.Register("ClearCommand", + typeof(ICommand), typeof(NLogViewer), new PropertyMetadata(null)); + + /// + /// Stop logging + /// + [Category("NLogViewer")] + public bool Pause + { + get => (bool)GetValue(PauseProperty); + set => SetValue(PauseProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty PauseProperty = DependencyProperty.Register("Pause", typeof(bool), typeof(NLogViewer), new PropertyMetadata(false)); + + /// + /// The maximum number of entries before automatic cleaning is performed. There is a hysteresis of 100 entries which must be exceeded. + /// Example: is '1000'. Then after '1100' entries, everything until '1000' is deleted. + /// If set to '0' or less, it is deactivated + /// + [Category("NLogViewer")] + public int MaxCount + { + get => (int)GetValue(MaxCountProperty); + set => SetValue(MaxCountProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty MaxCountProperty = DependencyProperty.Register("MaxCount", typeof(int), typeof(NLogViewer), new PropertyMetadata(5000)); + + + #endregion + + // ########################################################################################## + // Resolver + // ########################################################################################## + + #region Resolver + + /// + /// The to format the id + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver IdResolver + { + get => (ILogEventInfoResolver)GetValue(IdResolverProperty); + set => SetValue(IdResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty IdResolverProperty = DependencyProperty.Register("IdResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new IdResolver())); + + /// + /// The to format the timestamp output + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver TimeStampResolver + { + get => (ILogEventInfoResolver)GetValue(TimeStampResolverProperty); + set => SetValue(TimeStampResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TimeStampResolverProperty = DependencyProperty.Register("TimeStampResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new TimeStampResolver())); + + /// + /// The to format the loggername + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver LoggerNameResolver + { + get => (ILogEventInfoResolver)GetValue(LoggerNameResolverProperty); + set => SetValue(LoggerNameResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty LoggerNameResolverProperty = DependencyProperty.Register("LoggerNameResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new LoggerNameResolver())); + + /// + /// The to format the message + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver MessageResolver + { + get => (ILogEventInfoResolver)GetValue(MessageResolverProperty); + set => SetValue(MessageResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty MessageResolverProperty = DependencyProperty.Register("MessageResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new MessageResolver())); + + #endregion + + + #endregion + + // ############################################################################################################################## + // Properties + // ############################################################################################################################## + + #region Properties + + // ########################################################################################## + // Public Properties + // ########################################################################################## + + + + // ########################################################################################## + // Private Properties + // ########################################################################################## + + private ObservableCollection _LogEventInfos { get; } = new ObservableCollection(); + private IDisposable _Subscription; + private Window _ParentWindow; + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + public NLogViewer() + { + InitializeComponent(); + DataContext = this; + + // save instance UID + Uid = GetHashCode().ToString(); + + if (DesignerProperties.GetIsInDesignMode(this)) + return; + + LogEvents = new CollectionViewSource { Source = _LogEventInfos }; + + Loaded += _OnLoaded; + Unloaded += _OnUnloaded; + ClearCommand = new ActionCommand(_LogEventInfos.Clear); + } + + private void _OnUnloaded(object sender, RoutedEventArgs e) + { + // look in logical and visual tree if the control has been removed + // If there is no parent window found before, we have a special case (https://github.com/dojo90/NLogViewer/issues/30) and just dispose it anyway + if (_ParentWindow.FindChildByUid(Uid) == null) + { + _Dispose(); + } + } + + private void _ParentWindowOnClosed(object sender, EventArgs e) + { + _Dispose(); + } + + private void _Dispose() + { + _Subscription?.Dispose(); + } + + private void _OnLoaded(object sender, RoutedEventArgs e) + { + // removed loaded handler to prevent duplicate subscribing + Loaded -= _OnLoaded; + + // add hook to parent window to dispose subscription + // use case: + // NLogViewer is used in a new window inside of a TabControl. If you switch the TabItems, + // the unloaded event is called and would dispose the subscription, even if the control is still alive. + if (Window.GetWindow(this) is { } window) + { + _ParentWindow = window; + _ParentWindow.Closed += _ParentWindowOnClosed; + } + + ListView.ScrollToEnd(); + var target = CacheTarget.GetInstance(targetName: TargetName); + + _Subscription = target.Cache.SubscribeOn(Scheduler.Default).Buffer(TimeSpan.FromMilliseconds(100)).Where(x => x.Any()).ObserveOn(new DispatcherSynchronizationContext(_ParentWindow.Dispatcher)).Subscribe(infos => + { + if (Pause) return; + using (LogEvents.DeferRefresh()) + { + foreach (LogEventInfo info in infos) + { + _LogEventInfos.Add(info); + } + if (MaxCount >= 0 & _LogEventInfos.Count - 100 > MaxCount) + { + for (int i = 0; i < _LogEventInfos.Count - MaxCount; i++) + { + _LogEventInfos.RemoveAt(0); + } + } + } + + if (AutoScroll) + { + ListView?.ScrollToEnd(); + } + }); + } + + public void AddEventInfo(LogEventInfo logEvent) + { + _LogEventInfos.Add(logEvent); + } + + public bool HasEvents() + { + return _LogEventInfos.Any(); + } + + public void LoadLines(List lines, DateTime? fromDateTime = null) + { + _LogEventInfos.Clear(); + + var lastEventInfo = default(LogEventInfo); + + foreach (var line in lines) + { + var parts = line.Split("|"); + + if (parts.Length < 4) + { + if (lastEventInfo != null && !string.IsNullOrWhiteSpace(line)) + { + lastEventInfo.Message += "\r\n" + line; + } + + continue; + } + + var timeStamp = DateTime.Parse(parts[0]); + + if (fromDateTime > timeStamp) + { + continue; + } + + var logLevel = LogLevel.FromString(parts[1]); + + var logEvent = new LogEventInfo(logLevel, parts[2], parts[3]); + logEvent.TimeStamp = timeStamp; + + lastEventInfo = logEvent; + + AddEventInfo(logEvent); + } + } + + #endregion + } +} diff --git a/NLogViewer/Resolver/ILogEventInfoResolver.cs b/NLogViewer/Resolver/ILogEventInfoResolver.cs new file mode 100644 index 0000000..ea0e2f2 --- /dev/null +++ b/NLogViewer/Resolver/ILogEventInfoResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + /// + /// Default interface to resolve a for your personal needs + /// + public interface ILogEventInfoResolver + { + string Resolve(LogEventInfo logEventInfo); + } +} diff --git a/NLogViewer/Resolver/IdResolver.cs b/NLogViewer/Resolver/IdResolver.cs new file mode 100644 index 0000000..e427d29 --- /dev/null +++ b/NLogViewer/Resolver/IdResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class IdResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.SequenceID.ToString(); + } + } +} \ No newline at end of file diff --git a/NLogViewer/Resolver/LoggerNameResolver.cs b/NLogViewer/Resolver/LoggerNameResolver.cs new file mode 100644 index 0000000..c8773a8 --- /dev/null +++ b/NLogViewer/Resolver/LoggerNameResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class LoggerNameResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.LoggerName; + } + } +} \ No newline at end of file diff --git a/NLogViewer/Resolver/MessageResolver.cs b/NLogViewer/Resolver/MessageResolver.cs new file mode 100644 index 0000000..e6907e7 --- /dev/null +++ b/NLogViewer/Resolver/MessageResolver.cs @@ -0,0 +1,21 @@ +using System.Text; +using NLog; + +namespace DJ.Resolver +{ + public class MessageResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + StringBuilder builder = new StringBuilder(); + builder.Append(logEventInfo.FormattedMessage); + + if (logEventInfo.Exception != null) + { + builder.AppendLine().Append(logEventInfo.Exception); + } + + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/NLogViewer/Resolver/TimeStampResolver.cs b/NLogViewer/Resolver/TimeStampResolver.cs new file mode 100644 index 0000000..fc1d9b5 --- /dev/null +++ b/NLogViewer/Resolver/TimeStampResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class TimeStampResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.TimeStamp.ToString("dd-MM-yyyy HH:mm:ss.fff"); + } + } +} \ No newline at end of file diff --git a/NLogViewer/ScrollingHelper.cs b/NLogViewer/ScrollingHelper.cs new file mode 100644 index 0000000..ee70112 --- /dev/null +++ b/NLogViewer/ScrollingHelper.cs @@ -0,0 +1,44 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace DJ +{ + public static class ScrollingHelper + { + public static void ScrollToEnd(this ListView listView) + { + var scrollViewer = GetDescendantByType(listView, typeof(ScrollViewer)) as ScrollViewer; + scrollViewer?.ScrollToEnd(); + } + + public static Visual GetDescendantByType(Visual element, Type type) + { + if (element != null) + { + if (element.GetType() != type) + { + Visual foundElement = null; + (element as FrameworkElement)?.ApplyTemplate(); + + for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) + { + var visual = VisualTreeHelper.GetChild(element, i) as Visual; + foundElement = GetDescendantByType(visual, type); + if (foundElement != null) + { + break; + } + } + + return foundElement; + } + + return element; + } + + return null; + } + } +} \ No newline at end of file diff --git a/NLogViewer/Targets/CacheTarget.cs b/NLogViewer/Targets/CacheTarget.cs new file mode 100644 index 0000000..103f1f5 --- /dev/null +++ b/NLogViewer/Targets/CacheTarget.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using DJ.Helper; +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace DJ.Targets +{ + [Target(nameof(CacheTarget))] + public class CacheTarget : Target + { + /// + /// Look in the NLog.config if any target is already defined and returns it, otherwise a new one is registered + /// + /// The maximum entries which should be buffered. Is only used if no target is defined + /// The name of the target you want to link with + /// + public static CacheTarget GetInstance(int defaultMaxCount = 0, string targetName = null) + { + if(LogManager.Configuration == null) + LogManager.Configuration = new LoggingConfiguration(); + + var predicate = PredicateBuilder.True().And(t => t is CacheTarget); + if (!string.IsNullOrEmpty(targetName)) + { + predicate = predicate.And(t => t.Name.Equals(targetName, StringComparison.CurrentCultureIgnoreCase) ||t.Name.Equals($"{targetName}_wrapped", StringComparison.CurrentCultureIgnoreCase)); + } + + var target = (CacheTarget)LogManager.Configuration.AllTargets.FirstOrDefault(predicate.Compile()); + if (target == null) + { + target = new CacheTarget { MaxCount = defaultMaxCount, Name = targetName ?? nameof(CacheTarget)}; + LogManager.Configuration.AddTarget(target.Name, target); + LogManager.Configuration.LoggingRules.Insert(0, new LoggingRule("*", LogLevel.FromString("Trace"), target)); + LogManager.ReconfigExistingLoggers(); + } + return target; + } + + // ############################################################################################################################## + // Properties + // ############################################################################################################################## + + #region Properties + + // ########################################################################################## + // Public Properties + // ########################################################################################## + + /// + /// The maximum amount of entries held in buffer/cache + /// + public int MaxCount { get; set; } = 100; + + public IObservable Cache => _CacheSubject.AsObservable(); + private readonly ReplaySubject _CacheSubject; + + // ########################################################################################## + // Private Properties + // ########################################################################################## + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + public CacheTarget() + { + _CacheSubject = new ReplaySubject(MaxCount); + } + + #endregion + + // ############################################################################################################################## + // override + // ############################################################################################################################## + + #region override + + protected override void Write(LogEventInfo logEvent) + { + _CacheSubject.OnNext(logEvent); + } + + #endregion + } +} \ No newline at end of file diff --git a/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs b/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs new file mode 100644 index 0000000..e1bf42f --- /dev/null +++ b/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using DJ.Resolver; +using NLog; + +namespace DJ.XamlMultiValueConverter +{ + public class ILogEventResolverToStringConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is LogEventInfo logEventInfo && values[1] is ILogEventInfoResolver resolver) + return resolver.Resolve(logEventInfo); + return "####"; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/NLogViewer/global.json b/NLogViewer/global.json new file mode 100644 index 0000000..f45c840 --- /dev/null +++ b/NLogViewer/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "6.0.101", + "rollForward": "disable" + } +} \ No newline at end of file diff --git a/NLogViewer/nuget.config b/NLogViewer/nuget.config new file mode 100644 index 0000000..029051a --- /dev/null +++ b/NLogViewer/nuget.config @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file