Skip to content

Instantly share code, notes, and snippets.

@mikhail-barg
Created September 1, 2015 21:19
Show Gist options
  • Save mikhail-barg/a9df4732868272cd9de5 to your computer and use it in GitHub Desktop.
Save mikhail-barg/a9df4732868272cd9de5 to your computer and use it in GitHub Desktop.
A proposal for the RichTextBoxTarget modification or descendant
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace NLog.Windows.Forms.Ext
{
[Target("RichTextBoxExt")]
public class RichTextBoxExtTarget : TargetWithLayout
{
internal static string CreateKey(string formName, string controlName)
{
return formName + "." + controlName;
}
internal static string CreateKey(RichTextBox tb)
{
Form parentForm = GetParentForm(tb);
if (parentForm == null)
{
throw new ArgumentException("Text box should be on a form!");
}
string key = CreateKey(parentForm.Name, tb.Name);
return key;
}
internal static ConcurrentDictionary<string, RichTextBoxExtTarget> s_controlsToTargets = new ConcurrentDictionary<string, RichTextBoxExtTarget>();
public static void RegisterTextBox(RichTextBox tb)
{
string key = CreateKey(tb);
RichTextBoxExtTarget target;
if (s_controlsToTargets.TryGetValue(key, out target))
{
target.TargetRichTextBox = tb;
}
}
public static void UnregisterTextBox(RichTextBox tb)
{
string key = CreateKey(tb);
RichTextBoxExtTarget target;
if (s_controlsToTargets.TryGetValue(key, out target))
{
target.TargetRichTextBox = null;
}
}
internal static Form GetParentForm(Control control)
{
Form form = control as Form;
if (form != null)
{
return form;
}
if (control != null)
{
// Walk up the control hierarchy
return GetParentForm(control.Parent);
}
return null; // Control is not on a Form
}
/// <summary>
/// Finds control embedded on searchControl.
/// </summary>
/// <param name="name">Name of the control.</param>
/// <param name="searchControl">Control in which we're searching for control.</param>
/// <returns>A value of null if no control has been found.</returns>
internal static Control FindControl(string name, Control searchControl)
{
if (searchControl.Name == name)
{
return searchControl;
}
foreach (Control childControl in searchControl.Controls)
{
Control foundControl = FindControl(name, childControl);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
/// <summary>
/// Finds control of specified type embended on searchControl.
/// </summary>
/// <typeparam name="TControl">The type of the control.</typeparam>
/// <param name="name">Name of the control.</param>
/// <param name="searchControl">Control in which we're searching for control.</param>
/// <returns>
/// A value of null if no control has been found.
/// </returns>
internal static TControl FindControl<TControl>(string name, Control searchControl)
where TControl : Control
{
if (searchControl.Name == name)
{
TControl foundControl = searchControl as TControl;
if (foundControl != null)
{
return foundControl;
}
}
foreach (Control childControl in searchControl.Controls)
{
TControl foundControl = FindControl<TControl>(name, childControl);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
private int lineCount;
/// <summary>
/// Initializes static members of the RichTextBoxTarget class.
/// </summary>
/// <remarks>
/// The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
/// </remarks>
static RichTextBoxExtTarget()
{
var rules = new List<RichTextBoxRowColoringRule>()
{
new RichTextBoxRowColoringRule("level == LogLevel.Fatal", "White", "Red", FontStyle.Bold),
new RichTextBoxRowColoringRule("level == LogLevel.Error", "Red", "Empty", FontStyle.Bold | FontStyle.Italic),
new RichTextBoxRowColoringRule("level == LogLevel.Warn", "Orange", "Empty", FontStyle.Underline),
new RichTextBoxRowColoringRule("level == LogLevel.Info", "Black", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Debug", "Gray", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyle.Italic),
};
DefaultRowColoringRules = rules.AsReadOnly();
}
/// <summary>
/// Initializes a new instance of the <see cref="RichTextBoxTarget" /> class.
/// </summary>
/// <remarks>
/// The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
/// </remarks>
public RichTextBoxExtTarget()
{
WordColoringRules = new List<RichTextBoxWordColoringRule>();
RowColoringRules = new List<RichTextBoxRowColoringRule>();
}
private delegate void DelSendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule, RichTextBox rtbx);
private delegate void FormCloseDelegate();
/// <summary>
/// Gets the default set of row coloring rules which applies when <see cref="UseDefaultRowColoringRules"/> is set to true.
/// </summary>
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; private set; }
/// <summary>
/// Gets or sets the Name of RichTextBox to which Nlog will write.
/// </summary>
/// <docgen category='Form Options' order='10' />
public string ControlName { get; set; }
/// <summary>
/// Gets or sets the name of the Form on which the control is located.
/// If there is no open form of a specified name than NLog will create a new one.
/// </summary>
/// <docgen category='Form Options' order='10' />
public string FormName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use default coloring rules.
/// </summary>
/// <docgen category='Highlighting Options' order='10' />
[DefaultValue(false)]
public bool UseDefaultRowColoringRules { get; set; }
/// <summary>
/// Gets the row coloring rules.
/// </summary>
/// <docgen category='Highlighting Options' order='10' />
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; private set; }
/// <summary>
/// Gets the word highlighting rules.
/// </summary>
/// <docgen category='Highlighting Options' order='10' />
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether scroll bar will be moved automatically to show most recent log entries.
/// </summary>
/// <docgen category='Form Options' order='10' />
public bool AutoScroll { get; set; }
/// <summary>
/// Gets or sets the maximum number of lines the rich text box will store (or 0 to disable this feature).
/// </summary>
/// <remarks>
/// After exceeding the maximum number, first line will be deleted.
/// </remarks>
/// <docgen category='Form Options' order='10' />
public int MaxLines { get; set; }
/// <summary>
/// Gets or sets the rich text box to log to.
/// </summary>
public RichTextBox TargetRichTextBox { get; set; }
/// <summary>
/// Initializes the target. Can be used by inheriting classes
/// to initialize logging.
/// </summary>
protected override void InitializeTarget()
{
if (string.IsNullOrEmpty(FormName))
{
throw new NLogConfigurationException("Form name must be specified for " + GetType().Name + ".");
}
if (string.IsNullOrEmpty(ControlName))
{
throw new NLogConfigurationException("Rich text box control name must be specified for " + GetType().Name + ".");
}
string key = CreateKey(FormName, ControlName);
if (!s_controlsToTargets.TryAdd(key, this))
{
throw new NLogConfigurationException("Two targets cannot refer to same control: " + key + ".");
}
//try getting the control in case it is
Form openFormByName = Application.OpenForms[FormName];
if (openFormByName != null)
{
Form targetForm = openFormByName;
TargetRichTextBox = FindControl<RichTextBox>(ControlName, targetForm);
}
}
/// <summary>
/// Closes the target and releases any unmanaged resources.
/// </summary>
protected override void CloseTarget()
{
string key = CreateKey(FormName, ControlName);
RichTextBoxExtTarget value;
s_controlsToTargets.TryRemove(key, out value);
if (value != this)
{
throw new NLogConfigurationException("Two targets cannot refer to same control: " + key + ".");
}
}
/// <summary>
/// Log message to RichTextBox.
/// </summary>
/// <param name="logEvent">The logging event.</param>
protected override void Write(LogEventInfo logEvent)
{
RichTextBox rtbx = TargetRichTextBox;
if (rtbx == null)
{
return;
}
RichTextBoxRowColoringRule matchingRule = null;
foreach (RichTextBoxRowColoringRule rr in RowColoringRules)
{
if (rr.CheckCondition(logEvent))
{
matchingRule = rr;
break;
}
}
if (UseDefaultRowColoringRules && matchingRule == null)
{
foreach (RichTextBoxRowColoringRule rr in DefaultRowColoringRules)
{
if (rr.CheckCondition(logEvent))
{
matchingRule = rr;
break;
}
}
}
if (matchingRule == null)
{
matchingRule = RichTextBoxRowColoringRule.Default;
}
string logMessage = Layout.Render(logEvent);
TargetRichTextBox.BeginInvoke(new DelSendTheMessageToRichTextBox(SendTheMessageToRichTextBox), logMessage, matchingRule, rtbx);
}
private static Color GetColorFromString(string color, Color defaultColor)
{
if (color == "Empty")
{
return defaultColor;
}
return Color.FromName(color);
}
private void SendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule, RichTextBox rtbx)
{
int startIndex = rtbx.Text.Length;
rtbx.SelectionStart = startIndex;
rtbx.SelectionBackColor = GetColorFromString(rule.BackgroundColor, rtbx.BackColor);
rtbx.SelectionColor = GetColorFromString(rule.FontColor, rtbx.ForeColor);
rtbx.SelectionFont = new Font(rtbx.SelectionFont, rtbx.SelectionFont.Style ^ rule.Style);
rtbx.AppendText(logMessage + "\n");
rtbx.SelectionLength = rtbx.Text.Length - rtbx.SelectionStart;
// find word to color
foreach (RichTextBoxWordColoringRule wordRule in WordColoringRules)
{
MatchCollection mc = wordRule.CompiledRegex.Matches(rtbx.Text, startIndex);
foreach (Match m in mc)
{
rtbx.SelectionStart = m.Index;
rtbx.SelectionLength = m.Length;
rtbx.SelectionBackColor = GetColorFromString(wordRule.BackgroundColor, rtbx.BackColor);
rtbx.SelectionColor = GetColorFromString(wordRule.FontColor, rtbx.ForeColor);
rtbx.SelectionFont = new Font(rtbx.SelectionFont, rtbx.SelectionFont.Style ^ wordRule.Style);
}
}
if (MaxLines > 0)
{
lineCount++;
if (lineCount > MaxLines)
{
rtbx.SelectionStart = 0;
rtbx.SelectionLength = rtbx.GetFirstCharIndexFromLine(1);
rtbx.SelectedRtf = "{\\rtf1\\ansi}";
lineCount--;
}
}
if (AutoScroll)
{
rtbx.Select(rtbx.TextLength, 0);
rtbx.ScrollToCaret();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment