using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using DJ.Extensions; using DJ.Resolver; using DJ.Targets; using NLog; namespace DJ { /// /// Interaktionslogik für NLogViewer.xaml /// public partial class NLogViewer : UserControl { // ############################################################################################################################## // Dependency Properties // ############################################################################################################################## #region Dependency Properties // ########################################################################################## // Colors // ########################################################################################## #region Colors /// /// The background for the trace output /// [Category("NLogViewerColors")] public Brush TraceBackground { get => (Brush) GetValue(TraceBackgroundProperty); set => SetValue(TraceBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty TraceBackgroundProperty = DependencyProperty.Register("TraceBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#D3D3D3")))); /// /// The foreground for the trace output /// [Category("NLogViewerColors")] public Brush TraceForeground { get => (Brush) GetValue(TraceForegroundProperty); set => SetValue(TraceForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty TraceForegroundProperty = DependencyProperty.Register("TraceForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#042271")))); /// /// The background for the debug output /// [Category("NLogViewerColors")] public Brush DebugBackground { get => (Brush) GetValue(DebugBackgroundProperty); set => SetValue(DebugBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty DebugBackgroundProperty = DependencyProperty.Register("DebugBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#90EE90")))); /// /// The foreground for the debug output /// [Category("NLogViewerColors")] public Brush DebugForeground { get => (Brush) GetValue(DebugForegroundProperty); set => SetValue(DebugForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty DebugForegroundProperty = DependencyProperty.Register("DebugForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#042271")))); /// /// The background for the info output /// [Category("NLogViewerColors")] public Brush InfoBackground { get => (Brush) GetValue(InfoBackgroundProperty); set => SetValue(InfoBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty InfoBackgroundProperty = DependencyProperty.Register("InfoBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#0000FF")))); /// /// The foreground for the info output /// [Category("NLogViewerColors")] public Brush InfoForeground { get => (Brush) GetValue(InfoForegroundProperty); set => SetValue(InfoForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty InfoForegroundProperty = DependencyProperty.Register("InfoForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.White)); /// /// The background for the warn output /// [Category("NLogViewerColors")] public Brush WarnBackground { get => (Brush) GetValue(WarnBackgroundProperty); set => SetValue(WarnBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty WarnBackgroundProperty = DependencyProperty.Register("WarnBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#FFFF00")))); /// /// The foreground for the warn output /// [Category("NLogViewerColors")] public Brush WarnForeground { get => (Brush) GetValue(WarnForegroundProperty); set => SetValue(WarnForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty WarnForegroundProperty = DependencyProperty.Register("WarnForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata((Brush) (new BrushConverter().ConvertFrom("#324B5C")))); /// /// The background for the error output /// [Category("NLogViewerColors")] public Brush ErrorBackground { get => (Brush) GetValue(ErrorBackgroundProperty); set => SetValue(ErrorBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty ErrorBackgroundProperty = DependencyProperty.Register("ErrorBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.Red)); /// /// The foreground for the error output /// [Category("NLogViewerColors")] public Brush ErrorForeground { get => (Brush) GetValue(ErrorForegroundProperty); set => SetValue(ErrorForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty ErrorForegroundProperty = DependencyProperty.Register("ErrorForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.White)); /// /// The background for the fatal output /// [Category("NLogViewerColors")] public Brush FatalBackground { get => (Brush) GetValue(FatalBackgroundProperty); set => SetValue(FatalBackgroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty FatalBackgroundProperty = DependencyProperty.Register("FatalBackground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.Black)); /// /// The foreground for the fatal output /// [Category("NLogViewerColors")] public Brush FatalForeground { get => (Brush) GetValue(FatalForegroundProperty); set => SetValue(FatalForegroundProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty FatalForegroundProperty = DependencyProperty.Register("FatalForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.Yellow)); #endregion // ########################################################################################## // NLogViewer // ########################################################################################## #region NLogViewer /// /// Is looking if any target with this name is configured and tries to link it /// [Category("NLogViewer")] public string TargetName { get => (string)GetValue(TargetNameProperty); set => SetValue(TargetNameProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty TargetNameProperty = DependencyProperty.Register("TargetName", typeof(string), typeof(NLogViewer), new PropertyMetadata(null)); /// /// Private DP to bind to the gui /// [Category("NLogViewer")] public CollectionViewSource LogEvents { get => (CollectionViewSource) GetValue(LogEventsProperty); private set => SetValue(LogEventsProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty LogEventsProperty = DependencyProperty.Register("LogEvents", typeof(CollectionViewSource), typeof(NLogViewer), new PropertyMetadata(null)); /// /// Automatically scroll to the newest entry /// [Category("NLogViewer")] public bool AutoScroll { get => (bool)GetValue(AutoScrollProperty); set => SetValue(AutoScrollProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.Register("AutoScroll", typeof(bool), typeof(NLogViewer), new PropertyMetadata(true, AutoScrollChangedCallback)); private static void AutoScrollChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args) { if (d is NLogViewer instance) { instance.OnAutoScrollChanged(); } } protected virtual void OnAutoScrollChanged() { if (AutoScroll) ListView?.ScrollToEnd(); } /// /// Delele all entries /// [Category("NLogViewer")] public ICommand ClearCommand { get => (ICommand) GetValue(ClearCommandProperty); set => SetValue(ClearCommandProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty ClearCommandProperty = DependencyProperty.Register("ClearCommand", typeof(ICommand), typeof(NLogViewer), new PropertyMetadata(null)); /// /// Stop logging /// [Category("NLogViewer")] public bool Pause { get => (bool)GetValue(PauseProperty); set => SetValue(PauseProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty PauseProperty = DependencyProperty.Register("Pause", typeof(bool), typeof(NLogViewer), new PropertyMetadata(false)); /// /// The maximum number of entries before automatic cleaning is performed. There is a hysteresis of 100 entries which must be exceeded. /// Example: is '1000'. Then after '1100' entries, everything until '1000' is deleted. /// If set to '0' or less, it is deactivated /// [Category("NLogViewer")] public int MaxCount { get => (int)GetValue(MaxCountProperty); set => SetValue(MaxCountProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty MaxCountProperty = DependencyProperty.Register("MaxCount", typeof(int), typeof(NLogViewer), new PropertyMetadata(5000)); #endregion // ########################################################################################## // Resolver // ########################################################################################## #region Resolver /// /// The to format the id /// [Category("NLogViewerResolver")] public ILogEventInfoResolver IdResolver { get => (ILogEventInfoResolver)GetValue(IdResolverProperty); set => SetValue(IdResolverProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty IdResolverProperty = DependencyProperty.Register("IdResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new IdResolver())); /// /// The to format the timestamp output /// [Category("NLogViewerResolver")] public ILogEventInfoResolver TimeStampResolver { get => (ILogEventInfoResolver)GetValue(TimeStampResolverProperty); set => SetValue(TimeStampResolverProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty TimeStampResolverProperty = DependencyProperty.Register("TimeStampResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new TimeStampResolver())); /// /// The to format the loggername /// [Category("NLogViewerResolver")] public ILogEventInfoResolver LoggerNameResolver { get => (ILogEventInfoResolver)GetValue(LoggerNameResolverProperty); set => SetValue(LoggerNameResolverProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty LoggerNameResolverProperty = DependencyProperty.Register("LoggerNameResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new LoggerNameResolver())); /// /// The to format the message /// [Category("NLogViewerResolver")] public ILogEventInfoResolver MessageResolver { get => (ILogEventInfoResolver)GetValue(MessageResolverProperty); set => SetValue(MessageResolverProperty, value); } /// /// The DependencyProperty. /// public static readonly DependencyProperty MessageResolverProperty = DependencyProperty.Register("MessageResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new MessageResolver())); #endregion #endregion // ############################################################################################################################## // Properties // ############################################################################################################################## #region Properties // ########################################################################################## // Public Properties // ########################################################################################## // ########################################################################################## // Private Properties // ########################################################################################## private ObservableCollection _LogEventInfos { get; } = new ObservableCollection(); private IDisposable _Subscription; private Window _ParentWindow; #endregion // ############################################################################################################################## // Constructor // ############################################################################################################################## #region Constructor public NLogViewer() { InitializeComponent(); DataContext = this; // save instance UID Uid = GetHashCode().ToString(); if (DesignerProperties.GetIsInDesignMode(this)) return; LogEvents = new CollectionViewSource {Source = _LogEventInfos}; Loaded += _OnLoaded; Unloaded += _OnUnloaded; ClearCommand = new ActionCommand(_LogEventInfos.Clear); } private void _OnUnloaded(object sender, RoutedEventArgs e) { // look in logical and visual tree if the control has been removed // If there is no parent window found before, we have a special case (https://github.com/dojo90/NLogViewer/issues/30) and just dispose it anyway if (_ParentWindow.FindChildByUid(Uid) == null) { _Dispose(); } } private void _ParentWindowOnClosed(object sender, EventArgs e) { _Dispose(); } private void _Dispose() { _Subscription?.Dispose(); } private void _OnLoaded(object sender, RoutedEventArgs e) { // removed loaded handler to prevent duplicate subscribing Loaded -= _OnLoaded; // add hook to parent window to dispose subscription // use case: // NLogViewer is used in a new window inside of a TabControl. If you switch the TabItems, // the unloaded event is called and would dispose the subscription, even if the control is still alive. if (Window.GetWindow(this) is { } window) { _ParentWindow = window; _ParentWindow.Closed += _ParentWindowOnClosed; } ListView.ScrollToEnd(); var target = CacheTarget.GetInstance(targetName: TargetName); _Subscription = target.Cache.SubscribeOn(Scheduler.Default).Buffer(TimeSpan.FromMilliseconds(100)).Where (x => x.Any()).ObserveOn(new DispatcherSynchronizationContext(_ParentWindow.Dispatcher)).Subscribe(infos => { if (Pause) return; using (LogEvents.DeferRefresh()) { foreach (LogEventInfo info in infos) { _LogEventInfos.Add(info); } if (MaxCount >= 0 & _LogEventInfos.Count - 100 > MaxCount) { for (int i = 0; i < _LogEventInfos.Count - MaxCount; i++) { _LogEventInfos.RemoveAt(0); } } } if (AutoScroll) { ListView?.ScrollToEnd(); } }); } #endregion } }