Created
September 1, 2015 21:19
-
-
Save mikhail-barg/a9df4732868272cd9de5 to your computer and use it in GitHub Desktop.
A proposal for the RichTextBoxTarget modification or descendant
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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