Задача: Логирование в GUI
Исходник: Компонент для логирования событий элементов формы, язык: C# [code #580, hits: 12304]
автор: - [добавлен: 25.01.2009]
  1. // Исходный код компонента:
  2. namespace ControlsLogger
  3. {
  4. public partial class ControlsLogger : Component, ISupportInitialize
  5. {
  6. private Form _hostingForm;
  7.  
  8. public static Action<string> Log { get; set; }
  9. private readonly List<Control> _controls = new List<Control>();
  10.  
  11. // this hack is used to get component's hosting (parent) form
  12. // http://www.informit.com/articles/article.aspx?p=169528&seqNum=2 has an explanation of this hack.
  13. // if the site above is not available, search the code below in google
  14. [BrowsableAttribute(false)]
  15. public Form HostingForm
  16. {
  17. // Used to populate InitializeComponent at design time
  18. get
  19. {
  20. if ((_hostingForm == null) && DesignMode)
  21. {
  22. // Access designer host and obtain reference to root component
  23. var designer = GetService(typeof(IDesignerHost)) as IDesignerHost;
  24. if (designer != null)
  25. _hostingForm = designer.RootComponent as Form;
  26. }
  27. return _hostingForm;
  28. }
  29.  
  30. set { _hostingForm = value; }
  31. }
  32.  
  33. public ControlsLogger()
  34. {
  35. InitializeComponent();
  36. }
  37.  
  38. public ControlsLogger(IContainer container)
  39. {
  40. container.Add(this);
  41. InitializeComponent();
  42. }
  43.  
  44. public void BeginInit()
  45. {
  46. // do nothing
  47. }
  48.  
  49. public void EndInit()
  50. {
  51. // we access parent form from this method, because in constructor we don't have
  52. // a reference to the parent form, and the parent form doesn't have controls added yet.
  53. if (_hostingForm == null) return;
  54.  
  55. foreach (Control control in _hostingForm.Controls)
  56. {
  57. AddChildControls(control);
  58. }
  59.  
  60. SubscribeToControls();
  61. }
  62.  
  63. private void SubscribeToControls()
  64. {
  65. _hostingForm.Closed += Form_Closed;
  66. _hostingForm.Activated += Form_Activated;
  67.  
  68. foreach (Control control in _controls)
  69. {
  70. if (control is Button)
  71. {
  72. control.Click += Control_Click;
  73. }
  74. else if (control is MenuStrip)
  75. {
  76. MenuStripHelper menuStripHelper = new MenuStripHelper();
  77. foreach (ToolStripMenuItem menuItem in menuStripHelper.GetAllMenuItems((MenuStrip)control))
  78. {
  79. menuItem.Click += MenuItem_Click;
  80. }
  81. }
  82. else if (control is ComboBox)
  83. {
  84. ((ComboBox)control).SelectedIndexChanged += ComboBox_SelectedIndexChanged;
  85. }
  86. else if (control is CheckBox)
  87. {
  88. ((CheckBox)control).CheckedChanged += CheckBox_CheckedChanged;
  89. }
  90. }
  91. }
  92.  
  93. private void AddChildControls(Control parentControl)
  94. {
  95. _controls.Add(parentControl);
  96. foreach (Control childControl in parentControl.Controls)
  97. {
  98. AddChildControls(childControl);
  99. }
  100. }
  101.  
  102. private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
  103. {
  104. var comboBox = sender as ComboBox;
  105. if (comboBox == null || Log == null) return;
  106. Log(string.Format("ComboBox '{0}', selected item changed: '{1}'", comboBox.Name, comboBox.Text));
  107. }
  108.  
  109. private void Form_Activated(object sender, EventArgs e)
  110. {
  111. Form form = sender as Form;
  112. if (form == null || Log == null) return;
  113. Log(string.Format("Form '{0}' activated", form.Text));
  114. }
  115.  
  116.  
  117. private void Form_Closed(object sender, EventArgs e)
  118. {
  119. Form form = sender as Form;
  120. if (form == null || Log == null) return;
  121. Log(string.Format("Form '{0}' closed", form.Text));
  122. }
  123.  
  124. private void Control_Click(object sender, EventArgs e)
  125. {
  126. Control control = sender as Control;
  127. if(control == null || Log == null) return;
  128. Log(string.Format("{0} '{1}' pressed", control.GetType().Name, control.Text));
  129. }
  130.  
  131. private void MenuItem_Click(object sender, EventArgs e)
  132. {
  133. ToolStripMenuItem control = sender as ToolStripMenuItem;
  134. if (control == null || Log == null) return;
  135. Log(string.Format("Menu Item '{0}' pressed", control.Text));
  136. }
  137.  
  138. private void CheckBox_CheckedChanged(object sender, EventArgs e)
  139. {
  140. CheckBox control = sender as CheckBox;
  141. if (control == null || Log == null) return;
  142. Log(string.Format("CheckBox '{0}' new checked state: {1}", control.Text, control.Checked));
  143. }
  144.  
  145. }
  146.  
  147. }
  148.  
  149.  
  150.  
  151.  
  152. namespace ControlsLogger
  153. {
  154. public class MenuStripHelper
  155. {
  156. private readonly IList<ToolStripMenuItem> _menuItems = new List<ToolStripMenuItem>();
  157.  
  158. public IList<ToolStripMenuItem> GetAllMenuItems(MenuStrip menuStrip)
  159. {
  160. foreach (ToolStripMenuItem menuItem in menuStrip.Items)
  161. {
  162. AddSubMenus(menuItem);
  163. }
  164. return _menuItems;
  165. }
  166.  
  167. public void AddSubMenus(ToolStripMenuItem menuItem)
  168. {
  169. _menuItems.Add(menuItem);
  170. foreach (var submenuItem in menuItem.DropDownItems)
  171. {
  172. if (submenuItem is ToolStripMenuItem)
  173. AddSubMenus((ToolStripMenuItem)submenuItem);
  174. }
  175. }
  176. }
  177. }
  178.  
  179.  
  180.  
  181. // Класс Log у меня использует log4net и выглядит так:
  182. public class Log
  183. {
  184. private static volatile ILog _log;
  185. private static object locker = new object();
  186.  
  187. private static ILog GetInstance()
  188. {
  189. if (_log == null)
  190. {
  191. lock (locker)
  192. {
  193. if (_log == null)
  194. {
  195. _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  196. var appender = new log4net.Appender.RollingFileAppender();
  197. appender.AppendToFile = true;
  198. appender.File = string.Format("{0}\\full-log.txt", Config.GetInstance().LogsDirectory);
  199. appender.MaximumFileSize = "1MB";
  200. appender.MaxSizeRollBackups = 10;
  201. appender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size;
  202. appender.Threshold = log4net.Core.Level.All;
  203. appender.Layout = new log4net.Layout.PatternLayout("%date{dd-MM-yyyy HH:mm:ss} %message %n");
  204. appender.ActivateOptions();
  205. var bufferAppender = new log4net.Appender.BufferingForwardingAppender();
  206. bufferAppender.BufferSize = 10;
  207. bufferAppender.AddAppender(appender);
  208. bufferAppender.ActivateOptions();
  209. log4net.Config.BasicConfigurator.Configure(bufferAppender);
  210. }
  211. }
  212. }
  213. return _log;
  214. }
  215.  
  216. public static void Info(string logMessage)
  217. {
  218. GetInstance().Info(logMessage);
  219. }
  220. }
Перед началом работы, нужно задать статическое поле Log в классе ControlsLogger, например так:

ControlsLogger.ControlsLogger.Log = Log.Info;

Результирующий лог может выглядеть так:

05-11-2008 17:29:58 Form 'Maintain Customers' activated
05-11-2008 17:29:58 Menu Item '&Customers...' pressed
05-11-2008 17:30:15 CheckBox 'Send balance notifications' new checked state: True
05-11-2008 17:30:15 ComboBox 'cbCustomer', selected item changed: 'MAXTO'
05-11-2008 17:30:22 CheckBox 'English speaking customer' new checked state: False
05-11-2008 17:30:35 Button 'Save' pressed
05-11-2008 17:30:37 Form 'Maintain Customers' closed
05-11-2008 17:30:38 Button 'Close' pressed

По понятным причинам в некоторые моменты происходит "инверсия" записей в логе, т.е. как в приведенном примере Form Activated в логе идет раньше чем Menu Item pressed. Потому что обработчик события нажатия на пункт меню, создающий и показывающий форму, срабатывает раньше обработчика события нажатия на пункт меню, который логирует событие.


Теперь некоторые мысли:

1) Возможно стоит подписываться на события контролов не в EndInit(), а в обработчике OnLoad формы. Тогда из лога уйдут записи показывающую установку начальных значений контролов (например заполнение ComboBox'а в OnLoad формы приведет к появлению записи о смене item'а в combobox'е. Кому-то это может показаться лишним, ведь это не пользователь сменил значение). С другой стороны наоборот может быть важным знать начальные значения контролов на момент загрузки формы.
2) Можно добавить возможность выбора типа контролов и их событий, которые нужно логировать. Но в общем-то лично мне это пока не нужно.
3) Можно сделать чтобы подпись на события была не жестко закодированной, а использовать рефлекшен и подписываться например на все контролы у которых есть событие Click. Опять же, лично мне это пока не нужно.
4) Хотелось бы избавиться от инверсии записей в логе, т.к. хоть разработчику и будет по этим записям понятно как все было на самом деле, все равно это делает чтение лога более напряжным. Но пока я не придумал как можно от этого избавиться.

В общем, в итоге получается очень простой и рабочий компонент-логгер.
Желаю всем быстрого отлова и исправления багов.

http://www.rsdn.ru/forum/message/3163709.1.aspx
Тестировалось на: MS Visual Studio 2005, .NET Framework 2.0

+добавить реализацию