improve samsung controller, add NLogViewer
No files matched your search
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="XForms\LogWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
@@ -93,8 +96,6 @@
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.1.2" />
|
||||
<PackageReference Include="NStandard" Version="0.24.0" />
|
||||
@@ -104,6 +105,12 @@
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.13" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="XForms\LogWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NLogViewer\NLogViewer.csproj" />
|
||||
<ProjectReference Include="..\novideo_srgb\novideo_srgb.csproj" />
|
||||
<ProjectReference Include="..\Nspector\Nspector.csproj" />
|
||||
<ProjectReference Include="..\NvAPIWrapper\NvAPIWrapper.csproj" />
|
||||
|
||||
@@ -1117,6 +1117,46 @@ The best and suggested method to provide this is via a Windows Service. Only whe
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<string> ReadLines(string fileName, bool reverse = false)
|
||||
{
|
||||
var exists = File.Exists(fileName);
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var lines = new List<string>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<Module> Modules { get; set; }
|
||||
|
||||
public Config()
|
||||
{
|
||||
LogLevel = "DEBUG";
|
||||
DisplaySettingsDelay = 1000;
|
||||
ScreenSaverShortcut = string.Empty;
|
||||
FormWidth = 900;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<bool> HandleStartupParams(StartUpParams startUpParams, Process existingProcess)
|
||||
|
||||
@@ -244,6 +244,10 @@ namespace ColorControl.Services.Samsung
|
||||
// taskCompletion.TrySetResult(new { clientKey = key });
|
||||
//}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
//else if (_tokens.TryGetValue(id, out taskCompletion))
|
||||
//{
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
KEY_9,
|
||||
KEY_DTV,
|
||||
KEY_HDMI,
|
||||
KEY_CONTENTS
|
||||
KEY_CONTENTS,
|
||||
KEY_TV_MODE,
|
||||
KEY_PC_MODE
|
||||
}
|
||||
}
|
||||
@@ -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<IEnumerable<SamsungApp>> 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<SamsungApp>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace ColorControl.Services.Samsung
|
||||
private bool _poweredOffByScreenSaver;
|
||||
private int _poweredOffByScreenSaverProcessId;
|
||||
private object _lastTriggeredPreset;
|
||||
private List<SamsungApp> _samsungApps = new List<SamsungApp>();
|
||||
|
||||
public List<SamsungDevice> 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<SamsungPreset> GetDefaultPresets()
|
||||
{
|
||||
return new List<SamsungPreset>();
|
||||
@@ -430,11 +443,27 @@ namespace ColorControl.Services.Samsung
|
||||
}
|
||||
}
|
||||
|
||||
internal List<SamsungApp> GetApps()
|
||||
public async Task RefreshAppsAsync(bool force = false)
|
||||
{
|
||||
return new List<SamsungApp>();
|
||||
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<SamsungApp> GetApps()
|
||||
{
|
||||
return _samsungApps;
|
||||
}
|
||||
|
||||
public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, CancellationToken token)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<Window x:Class="ColorControl.XForms.LogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dj="clr-namespace:DJ;assembly=NLogViewer"
|
||||
mc:Ignorable="d"
|
||||
Title="Logging"
|
||||
ResizeMode="CanResize"
|
||||
Width="800"
|
||||
Height="600"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SnapsToDevicePixels="True"
|
||||
d:DesignHeight="450" d:DesignWidth="800" Loaded="Window_Loaded">
|
||||
<DockPanel Margin="6 0 6 6" LastChildFill="True">
|
||||
<GroupBox DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,6,0,0" VerticalAlignment="Bottom" Header="Log settings" Padding="6">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock>Log level:</TextBlock>
|
||||
<ComboBox Width="117" Margin="6,0,0,0" SelectedIndex="{Binding LogLevelIndex}" SelectionChanged="LogLevel_Changed">
|
||||
<ComboBoxItem>Trace</ComboBoxItem>
|
||||
<ComboBoxItem>Debug</ComboBoxItem>
|
||||
<ComboBoxItem>Info</ComboBoxItem>
|
||||
<ComboBoxItem>Warning</ComboBoxItem>
|
||||
<ComboBoxItem>Error</ComboBoxItem>
|
||||
<ComboBoxItem>Fatal</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<DockPanel DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Margin="0,6,0,0" VerticalAlignment="Bottom">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Click="LoadOlder_Click" Content="Load older..." Width="75" Margin="0,0,6,0"/>
|
||||
<Button Width="75" Click="RawLog_Click" Margin="0,0,6,0">View raw log</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Width="75" Click="Close_Click" IsCancel="True">Close</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<dj:NLogViewer x:Name="logViewer" />
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace DJ
|
||||
{
|
||||
public sealed class ActionCommand : ICommand
|
||||
{
|
||||
private readonly Action _Action;
|
||||
private readonly Action<object> _ObjectAction;
|
||||
|
||||
public ActionCommand(Action action)
|
||||
{
|
||||
_Action = action;
|
||||
}
|
||||
|
||||
public ActionCommand(Action<object> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace DJ.Extensions
|
||||
{
|
||||
public static class DependencyObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes both visual and logical tree in order to find all elements of a given
|
||||
/// type that are descendants of the <paramref name="source"/> item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queried items.</typeparam>
|
||||
/// <param name="source">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.</param>
|
||||
/// <param name="uid">The UID of the <see cref="UIElement"/></param>
|
||||
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
|
||||
public static T FindChildByUid<T>(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<T>(child, uid);
|
||||
if (descendant != null)
|
||||
return descendant;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is an alternative to WPF's
|
||||
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
|
||||
/// supports content elements. Keep in mind that for content elements,
|
||||
/// this method falls back to the logical tree of the element.
|
||||
/// </summary>
|
||||
/// <param name="parent">The item to be processed.</param>
|
||||
/// <returns>The submitted item's child elements, if available.</returns>
|
||||
public static IEnumerable<DependencyObject> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
using NLog;
|
||||
|
||||
namespace DJ.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 -------------------------------------------------------------------
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DJ.Helper
|
||||
{
|
||||
public static class PredicateBuilder
|
||||
{
|
||||
public static Expression<Func<T, bool>> True<T> () { return f => true; }
|
||||
public static Expression<Func<T, bool>> False<T> () { return f => false; }
|
||||
|
||||
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
|
||||
Expression<Func<T, bool>> expr2)
|
||||
{
|
||||
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
|
||||
return Expression.Lambda<Func<T, bool>>
|
||||
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
|
||||
Expression<Func<T, bool>> expr2)
|
||||
{
|
||||
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
|
||||
return Expression.Lambda<Func<T, bool>>
|
||||
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 264 B |
|
After Width: | Height: | Size: 278 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 365 B |
|
After Width: | Height: | Size: 451 B |
|
After Width: | Height: | Size: 774 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 943 B |
|
After Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 589 B |
|
After Width: | Height: | Size: 778 B |
|
After Width: | Height: | Size: 372 B |
|
After Width: | Height: | Size: 520 B |
|
After Width: | Height: | Size: 506 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 631 B |
|
After Width: | Height: | Size: 582 B |
|
After Width: | Height: | Size: 813 B |
@@ -0,0 +1,56 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.20348.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RootNamespace>DJ</RootNamespace>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>Sentinel.NLogViewer</PackageId>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NLogViewer.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.1.2" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Images\Glyphs\SortDownArrow.png" />
|
||||
<Resource Include="Images\Glyphs\SortUpArrow.png" />
|
||||
<Resource Include="Images\Small\Add.png" />
|
||||
<Resource Include="Images\Small\Clear.png" />
|
||||
<Resource Include="Images\Small\Clock.png" />
|
||||
<Resource Include="Images\Small\Context.png" />
|
||||
<Resource Include="Images\Small\Debug.png" />
|
||||
<Resource Include="Images\Small\DebugSource.png" />
|
||||
<Resource Include="Images\Small\Error.png" />
|
||||
<Resource Include="Images\Small\Exception.png" />
|
||||
<Resource Include="Images\Small\Exit.png" />
|
||||
<Resource Include="Images\Small\Export.png" />
|
||||
<Resource Include="Images\Small\Fatal.png" />
|
||||
<Resource Include="Images\Small\Info.png" />
|
||||
<Resource Include="Images\Small\Layout.png" />
|
||||
<Resource Include="Images\Small\MonoLightning.png" />
|
||||
<Resource Include="Images\Small\Network.png" />
|
||||
<Resource Include="Images\Small\Open.png" />
|
||||
<Resource Include="Images\Small\Pause.png" />
|
||||
<Resource Include="Images\Small\Save.png" />
|
||||
<Resource Include="Images\Small\ScrollDown.png" />
|
||||
<Resource Include="Images\Small\Settings.png" />
|
||||
<Resource Include="Images\Small\Thread.png" />
|
||||
<Resource Include="Images\Small\Trace.png" />
|
||||
<Resource Include="Images\Small\Unknown.png" />
|
||||
<Resource Include="Images\Small\Warning.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="NLogView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,213 @@
|
||||
<UserControl x:Class="DJ.NLogViewer"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dj="clr-namespace:DJ"
|
||||
xmlns:nLog="clr-namespace:NLog;assembly=NLog"
|
||||
xmlns:listViewLayoutManager="clr-namespace:DJ.Helper.ListViewLayoutManager"
|
||||
xmlns:xamlMultiValueConverter="clr-namespace:DJ.XamlMultiValueConverter"
|
||||
xmlns:helper="clr-namespace:DJ.Helper"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="0,5,0,5">
|
||||
<ToggleButton
|
||||
Width="120"
|
||||
Height="20"
|
||||
IsChecked="{Binding Path=AutoScroll, Mode=TwoWay}">
|
||||
<ToggleButton.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Images/Small/ScrollDown.png"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Stretch="Uniform" />
|
||||
<TextBlock Text="Auto-Scroll" VerticalAlignment="Center" Margin="5,0,0,0" />
|
||||
</StackPanel>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Button
|
||||
Margin="5,0,0,0"
|
||||
Width="120"
|
||||
Height="20"
|
||||
Command="{Binding Path=ClearCommand}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Images/Small/Clear.png"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Stretch="Uniform" />
|
||||
<TextBlock Text="Clear" VerticalAlignment="Center" Margin="5,0,0,0" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<ToggleButton
|
||||
Margin="5,0,0,0"
|
||||
Width="120"
|
||||
Height="20"
|
||||
IsChecked="{Binding Path=Pause, Mode=TwoWay}">
|
||||
<ToggleButton.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Images/Small/Pause.png"
|
||||
RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform" />
|
||||
<TextBlock Text="Pause" VerticalAlignment="Center" Margin="5,0,0,0" />
|
||||
</StackPanel>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<ListView
|
||||
x:Name="ListView"
|
||||
Grid.Row="2"
|
||||
ItemsSource="{Binding Path=LogEvents.View, IsAsync=True}"
|
||||
BorderThickness="0"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
listViewLayoutManager:ListViewLayoutManager.Enabled="true">
|
||||
<ListView.Resources>
|
||||
<xamlMultiValueConverter:ILogEventResolverToStringConverter x:Key="ILogEventResolverToStringConverter"/>
|
||||
</ListView.Resources>
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="BorderBrush" Value="Black" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Trace}">
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=TraceBackground}" />
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=TraceForeground}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Debug}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=DebugForeground}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=DebugBackground}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Info}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=InfoForeground}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=InfoBackground}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Warn}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=WarnForeground}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=WarnBackground}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Error}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=ErrorForeground}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=ErrorBackground}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Fatal}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=FatalForeground}" />
|
||||
<Setter Property="Background"
|
||||
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}, Path=FatalBackground}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.View>
|
||||
<helper:AutoSizedGridView>
|
||||
<GridViewColumn Header="ID" Width="40">
|
||||
<GridViewColumn.DisplayMemberBinding>
|
||||
<MultiBinding Converter="{StaticResource ILogEventResolverToStringConverter}">
|
||||
<Binding Path="."/>
|
||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}" Path="IdResolver"/>
|
||||
</MultiBinding>
|
||||
</GridViewColumn.DisplayMemberBinding>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Header="Level" Width="Auto">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Image x:Name="Image" RenderOptions.BitmapScalingMode="Linear"
|
||||
Height="18" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Trace}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Trace.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Debug}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Debug.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Info}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Info.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Warn}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Warning.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Error}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Error.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Path=Level}"
|
||||
Value="{x:Static nLog:LogLevel.Fatal}">
|
||||
<Setter TargetName="Image" Property="Source"
|
||||
Value="Images/Small/Fatal.png" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Header="TimeStamp" Width="Auto">
|
||||
<GridViewColumn.DisplayMemberBinding>
|
||||
<MultiBinding Converter="{StaticResource ILogEventResolverToStringConverter}">
|
||||
<Binding Path="."/>
|
||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}" Path="TimeStampResolver"/>
|
||||
</MultiBinding>
|
||||
</GridViewColumn.DisplayMemberBinding>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Header="LoggerName" Width="Auto">
|
||||
<GridViewColumn.DisplayMemberBinding>
|
||||
<MultiBinding Converter="{StaticResource ILogEventResolverToStringConverter}">
|
||||
<Binding Path="."/>
|
||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}" Path="LoggerNameResolver"/>
|
||||
</MultiBinding>
|
||||
</GridViewColumn.DisplayMemberBinding>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Header="Message" Width="Auto" listViewLayoutManager:RangeColumn.IsFillColumn="True">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListViewItem}, Path=Foreground}">
|
||||
<TextBox.Text>
|
||||
<MultiBinding Converter="{StaticResource ILogEventResolverToStringConverter}">
|
||||
<Binding Path="." Mode="OneWay"/>
|
||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=dj:NLogViewer}" Path="MessageResolver"/>
|
||||
</MultiBinding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
</helper:AutoSizedGridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaktionslogik für NLogViewer.xaml
|
||||
/// </summary>
|
||||
public partial class NLogViewer : UserControl
|
||||
{
|
||||
// ##############################################################################################################################
|
||||
// Dependency Properties
|
||||
// ##############################################################################################################################
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
// ##########################################################################################
|
||||
// Colors
|
||||
// ##########################################################################################
|
||||
|
||||
#region Colors
|
||||
|
||||
/// <summary>
|
||||
/// The background for the trace output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush TraceBackground
|
||||
{
|
||||
get => (Brush)GetValue(TraceBackgroundProperty);
|
||||
set => SetValue(TraceBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TraceBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TraceBackgroundProperty =
|
||||
DependencyProperty.Register("TraceBackground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#D3D3D3"))));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the trace output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush TraceForeground
|
||||
{
|
||||
get => (Brush)GetValue(TraceForegroundProperty);
|
||||
set => SetValue(TraceForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TraceForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TraceForegroundProperty =
|
||||
DependencyProperty.Register("TraceForeground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#042271"))));
|
||||
|
||||
/// <summary>
|
||||
/// The background for the debug output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush DebugBackground
|
||||
{
|
||||
get => (Brush)GetValue(DebugBackgroundProperty);
|
||||
set => SetValue(DebugBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DebugBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DebugBackgroundProperty =
|
||||
DependencyProperty.Register("DebugBackground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#90EE90"))));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the debug output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush DebugForeground
|
||||
{
|
||||
get => (Brush)GetValue(DebugForegroundProperty);
|
||||
set => SetValue(DebugForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DebugForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DebugForegroundProperty =
|
||||
DependencyProperty.Register("DebugForeground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#042271"))));
|
||||
|
||||
/// <summary>
|
||||
/// The background for the info output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush InfoBackground
|
||||
{
|
||||
get => (Brush)GetValue(InfoBackgroundProperty);
|
||||
set => SetValue(InfoBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InfoBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty InfoBackgroundProperty = DependencyProperty.Register("InfoBackground",
|
||||
typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#0000FF"))));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the info output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush InfoForeground
|
||||
{
|
||||
get => (Brush)GetValue(InfoForegroundProperty);
|
||||
set => SetValue(InfoForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InfoForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty InfoForegroundProperty = DependencyProperty.Register("InfoForeground",
|
||||
typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.White));
|
||||
|
||||
/// <summary>
|
||||
/// The background for the warn output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush WarnBackground
|
||||
{
|
||||
get => (Brush)GetValue(WarnBackgroundProperty);
|
||||
set => SetValue(WarnBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="WarnBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty WarnBackgroundProperty = DependencyProperty.Register("WarnBackground",
|
||||
typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#FFFF00"))));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the warn output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush WarnForeground
|
||||
{
|
||||
get => (Brush)GetValue(WarnForegroundProperty);
|
||||
set => SetValue(WarnForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="WarnForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty WarnForegroundProperty = DependencyProperty.Register("WarnForeground",
|
||||
typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata((Brush)(new BrushConverter().ConvertFrom("#324B5C"))));
|
||||
|
||||
/// <summary>
|
||||
/// The background for the error output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush ErrorBackground
|
||||
{
|
||||
get => (Brush)GetValue(ErrorBackgroundProperty);
|
||||
set => SetValue(ErrorBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ErrorBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorBackgroundProperty =
|
||||
DependencyProperty.Register("ErrorBackground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata(Brushes.Red));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the error output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush ErrorForeground
|
||||
{
|
||||
get => (Brush)GetValue(ErrorForegroundProperty);
|
||||
set => SetValue(ErrorForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ErrorForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorForegroundProperty =
|
||||
DependencyProperty.Register("ErrorForeground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata(Brushes.White));
|
||||
|
||||
/// <summary>
|
||||
/// The background for the fatal output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush FatalBackground
|
||||
{
|
||||
get => (Brush)GetValue(FatalBackgroundProperty);
|
||||
set => SetValue(FatalBackgroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FatalBackground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FatalBackgroundProperty =
|
||||
DependencyProperty.Register("FatalBackground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata(Brushes.Black));
|
||||
|
||||
/// <summary>
|
||||
/// The foreground for the fatal output
|
||||
/// </summary>
|
||||
[Category("NLogViewerColors")]
|
||||
public Brush FatalForeground
|
||||
{
|
||||
get => (Brush)GetValue(FatalForegroundProperty);
|
||||
set => SetValue(FatalForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FatalForeground"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FatalForegroundProperty =
|
||||
DependencyProperty.Register("FatalForeground", typeof(Brush), typeof(NLogViewer),
|
||||
new PropertyMetadata(Brushes.Yellow));
|
||||
|
||||
#endregion
|
||||
|
||||
// ##########################################################################################
|
||||
// NLogViewer
|
||||
// ##########################################################################################
|
||||
|
||||
#region NLogViewer
|
||||
|
||||
/// <summary>
|
||||
/// Is looking if any target with this name is configured and tries to link it
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public string TargetName
|
||||
{
|
||||
get => (string)GetValue(TargetNameProperty);
|
||||
set => SetValue(TargetNameProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TargetName"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TargetNameProperty = DependencyProperty.Register("TargetName", typeof(string), typeof(NLogViewer), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Private DP to bind to the gui
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public CollectionViewSource LogEvents
|
||||
{
|
||||
get => (CollectionViewSource)GetValue(LogEventsProperty);
|
||||
private set => SetValue(LogEventsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LogEvents"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty LogEventsProperty = DependencyProperty.Register("LogEvents",
|
||||
typeof(CollectionViewSource), typeof(NLogViewer), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Automatically scroll to the newest entry
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public bool AutoScroll
|
||||
{
|
||||
get => (bool)GetValue(AutoScrollProperty);
|
||||
set => SetValue(AutoScrollProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AutoScroll"/> DependencyProperty.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delele all entries
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public ICommand ClearCommand
|
||||
{
|
||||
get => (ICommand)GetValue(ClearCommandProperty);
|
||||
set => SetValue(ClearCommandProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ClearCommand"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ClearCommandProperty = DependencyProperty.Register("ClearCommand",
|
||||
typeof(ICommand), typeof(NLogViewer), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Stop logging
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public bool Pause
|
||||
{
|
||||
get => (bool)GetValue(PauseProperty);
|
||||
set => SetValue(PauseProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Pause"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PauseProperty = DependencyProperty.Register("Pause", typeof(bool), typeof(NLogViewer), new PropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of entries before automatic cleaning is performed. There is a hysteresis of 100 entries which must be exceeded.
|
||||
/// Example: <see cref="MaxCount"/> is '1000'. Then after '1100' entries, everything until '1000' is deleted.
|
||||
/// If set to '0' or less, it is deactivated
|
||||
/// </summary>
|
||||
[Category("NLogViewer")]
|
||||
public int MaxCount
|
||||
{
|
||||
get => (int)GetValue(MaxCountProperty);
|
||||
set => SetValue(MaxCountProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MaxCount"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxCountProperty = DependencyProperty.Register("MaxCount", typeof(int), typeof(NLogViewer), new PropertyMetadata(5000));
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
// ##########################################################################################
|
||||
// Resolver
|
||||
// ##########################################################################################
|
||||
|
||||
#region Resolver
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogEventInfoResolver"/> to format the id
|
||||
/// </summary>
|
||||
[Category("NLogViewerResolver")]
|
||||
public ILogEventInfoResolver IdResolver
|
||||
{
|
||||
get => (ILogEventInfoResolver)GetValue(IdResolverProperty);
|
||||
set => SetValue(IdResolverProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IdResolver"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IdResolverProperty = DependencyProperty.Register("IdResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new IdResolver()));
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogEventInfoResolver"/> to format the timestamp output
|
||||
/// </summary>
|
||||
[Category("NLogViewerResolver")]
|
||||
public ILogEventInfoResolver TimeStampResolver
|
||||
{
|
||||
get => (ILogEventInfoResolver)GetValue(TimeStampResolverProperty);
|
||||
set => SetValue(TimeStampResolverProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TimeStampResolver"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TimeStampResolverProperty = DependencyProperty.Register("TimeStampResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new TimeStampResolver()));
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogEventInfoResolver"/> to format the loggername
|
||||
/// </summary>
|
||||
[Category("NLogViewerResolver")]
|
||||
public ILogEventInfoResolver LoggerNameResolver
|
||||
{
|
||||
get => (ILogEventInfoResolver)GetValue(LoggerNameResolverProperty);
|
||||
set => SetValue(LoggerNameResolverProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoggerNameResolver"/> DependencyProperty.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty LoggerNameResolverProperty = DependencyProperty.Register("LoggerNameResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new LoggerNameResolver()));
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogEventInfoResolver"/> to format the message
|
||||
/// </summary>
|
||||
[Category("NLogViewerResolver")]
|
||||
public ILogEventInfoResolver MessageResolver
|
||||
{
|
||||
get => (ILogEventInfoResolver)GetValue(MessageResolverProperty);
|
||||
set => SetValue(MessageResolverProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MessageResolver"/> DependencyProperty.
|
||||
/// </summary>
|
||||
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<LogEventInfo> _LogEventInfos { get; } = new ObservableCollection<LogEventInfo>();
|
||||
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<NLogViewer>(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<string> 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using NLog;
|
||||
|
||||
namespace DJ.Resolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Default interface to resolve a <see cref="LogEventInfo"/> for your personal needs
|
||||
/// </summary>
|
||||
public interface ILogEventInfoResolver
|
||||
{
|
||||
string Resolve(LogEventInfo logEventInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using NLog;
|
||||
|
||||
namespace DJ.Resolver
|
||||
{
|
||||
public class IdResolver : ILogEventInfoResolver
|
||||
{
|
||||
public string Resolve(LogEventInfo logEventInfo)
|
||||
{
|
||||
return logEventInfo.SequenceID.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using NLog;
|
||||
|
||||
namespace DJ.Resolver
|
||||
{
|
||||
public class LoggerNameResolver : ILogEventInfoResolver
|
||||
{
|
||||
public string Resolve(LogEventInfo logEventInfo)
|
||||
{
|
||||
return logEventInfo.LoggerName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Look in the NLog.config if any target is already defined and returns it, otherwise a new one is registered
|
||||
/// </summary>
|
||||
/// <param name="defaultMaxCount">The maximum entries which should be buffered. Is only used if no target is defined</param>
|
||||
/// <param name="targetName">The name of the target you want to link with</param>
|
||||
/// <returns></returns>
|
||||
public static CacheTarget GetInstance(int defaultMaxCount = 0, string targetName = null)
|
||||
{
|
||||
if(LogManager.Configuration == null)
|
||||
LogManager.Configuration = new LoggingConfiguration();
|
||||
|
||||
var predicate = PredicateBuilder.True<Target>().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
|
||||
// ##########################################################################################
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of entries held in buffer/cache
|
||||
/// </summary>
|
||||
public int MaxCount { get; set; } = 100;
|
||||
|
||||
public IObservable<LogEventInfo> Cache => _CacheSubject.AsObservable();
|
||||
private readonly ReplaySubject<LogEventInfo> _CacheSubject;
|
||||
|
||||
// ##########################################################################################
|
||||
// Private Properties
|
||||
// ##########################################################################################
|
||||
|
||||
#endregion
|
||||
|
||||
// ##############################################################################################################################
|
||||
// Constructor
|
||||
// ##############################################################################################################################
|
||||
|
||||
#region Constructor
|
||||
|
||||
public CacheTarget()
|
||||
{
|
||||
_CacheSubject = new ReplaySubject<LogEventInfo>(MaxCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// ##############################################################################################################################
|
||||
// override
|
||||
// ##############################################################################################################################
|
||||
|
||||
#region override
|
||||
|
||||
protected override void Write(LogEventInfo logEvent)
|
||||
{
|
||||
_CacheSubject.OnNext(logEvent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.101",
|
||||
"rollForward": "disable"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
<packageRestore>
|
||||
<!-- The 'automatic' key is set to True when the "Automatically check for missing packages during
|
||||
build in Visual Studio" checkbox is set. Clearing the box sets this to False and disables
|
||||
automatic restore. -->
|
||||
<add key="automatic" value="True" />
|
||||
</packageRestore>
|
||||
</configuration>
|
||||