На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: maxim84_
  
> Компонент для логирования событий от контролов на форме
    Здравствуйте,

    Накатал тут небольшой компонентик, для логирования действий пользователя. Думаю, что кому-то может оказаться полезным. Компонет просто кладется на форму и логирует события от контролов на форме.

    Исходный код компонента:

    ExpandedWrap disabled
      namespace ControlsLogger
      {
          public partial class ControlsLogger : Component, ISupportInitialize
          {
              private Form _hostingForm;
       
              public static Action<string> Log { get; set; }
              private readonly List<Control> _controls = new List<Control>();
       
              // this hack is used to get component's hosting (parent) form
              // http://www.informit.com/articles/article.aspx?p=169528&seqNum=2 has an explanation of this hack.
              // if the site above is not available, search the code below in google
              [BrowsableAttribute(false)]
              public Form HostingForm
              {
                  // Used to populate InitializeComponent at design time
                  get
                  {
                      if ((_hostingForm == null) && DesignMode)
                      {
                          // Access designer host and obtain reference to root component
                          var designer = GetService(typeof(IDesignerHost)) as IDesignerHost;
                          if (designer != null)
                              _hostingForm = designer.RootComponent as Form;
                      }
                      return _hostingForm;
                  }
       
                  set { _hostingForm = value; }
              }
       
              public ControlsLogger()
              {
                  InitializeComponent();
              }
       
              public ControlsLogger(IContainer container)
              {
                  container.Add(this);
                  InitializeComponent();
              }
       
              public void BeginInit()
              {
                  // do nothing
              }
       
              public void EndInit()
              {
                  // we access parent form from this method, because in constructor we don't have
                  // a reference to the parent form, and the parent form doesn't have controls added yet.
                  if (_hostingForm == null) return;
       
                  foreach (Control control in _hostingForm.Controls)
                  {
                      AddChildControls(control);
                  }
       
                  SubscribeToControls();
              }
       
              private void SubscribeToControls()
              {
                  _hostingForm.Closed += Form_Closed;
                  _hostingForm.Activated += Form_Activated;
       
                  foreach (Control control in _controls)
                  {
                      if (control is Button)
                      {
                          control.Click += Control_Click;
                      }
                      else if (control is MenuStrip)
                      {
                          MenuStripHelper menuStripHelper = new MenuStripHelper();
                          foreach (ToolStripMenuItem menuItem in menuStripHelper.GetAllMenuItems((MenuStrip)control))
                          {
                              menuItem.Click += MenuItem_Click;
                          }
                      }
                      else if (control is ComboBox)
                      {
                          ((ComboBox)control).SelectedIndexChanged += ComboBox_SelectedIndexChanged;
                      }
                      else if (control is CheckBox)
                      {
                          ((CheckBox)control).CheckedChanged += CheckBox_CheckedChanged;
                      }
                  }
              }
       
              private void AddChildControls(Control parentControl)
              {
                  _controls.Add(parentControl);
                  foreach (Control childControl in parentControl.Controls)
                  {
                      AddChildControls(childControl);
                  }
              }
       
              private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
              {
                  var comboBox = sender as ComboBox;
                  if (comboBox == null || Log == null) return;
                  Log(string.Format("ComboBox '{0}', selected item changed: '{1}'", comboBox.Name, comboBox.Text));
              }
       
              private void Form_Activated(object sender, EventArgs e)
              {
                  Form form = sender as Form;
                  if (form == null || Log == null) return;
                  Log(string.Format("Form '{0}' activated", form.Text));
              }
       
       
              private void Form_Closed(object sender, EventArgs e)
              {
                  Form form = sender as Form;
                  if (form == null || Log == null) return;
                  Log(string.Format("Form '{0}' closed", form.Text));
              }
       
              private void Control_Click(object sender, EventArgs e)
              {
                  Control control = sender as Control;
                  if(control == null || Log == null) return;
                  Log(string.Format("{0} '{1}' pressed", control.GetType().Name, control.Text));
              }
       
              private void MenuItem_Click(object sender, EventArgs e)
              {
                  ToolStripMenuItem control = sender as ToolStripMenuItem;
                  if (control == null || Log == null) return;
                  Log(string.Format("Menu Item '{0}' pressed", control.Text));
              }
       
              private void CheckBox_CheckedChanged(object sender, EventArgs e)
              {
                  CheckBox control = sender as CheckBox;
                  if (control == null || Log == null) return;
                  Log(string.Format("CheckBox '{0}' new checked state: {1}", control.Text, control.Checked));
              }
       
          }
       
      }

    ExpandedWrap disabled
      namespace ControlsLogger
      {
          public class MenuStripHelper
          {
              private readonly IList<ToolStripMenuItem> _menuItems = new List<ToolStripMenuItem>();
       
              public IList<ToolStripMenuItem> GetAllMenuItems(MenuStrip menuStrip)
              {
                  foreach (ToolStripMenuItem menuItem in menuStrip.Items)
                  {
                      AddSubMenus(menuItem);
                  }
                  return _menuItems;
              }
       
              public void AddSubMenus(ToolStripMenuItem menuItem)
              {
                  _menuItems.Add(menuItem);
                  foreach (var submenuItem in menuItem.DropDownItems)
                  {
                      if (submenuItem is ToolStripMenuItem)
                          AddSubMenus((ToolStripMenuItem)submenuItem);
                  }
              }
          }
      }

    Перед началом работы, нужно задать статическое поле Log в классе ControlsLogger, например так:

    ExpandedWrap disabled
      ControlsLogger.ControlsLogger.Log = Log.Info;

    Класс Log у меня использует log4net и выглядит так:

    ExpandedWrap disabled
      public class Log
      {
          private static volatile ILog _log;
          private static object locker = new object();
       
          private static ILog GetInstance()
          {
              if (_log == null)
              {
                  lock (locker)
                  {
                      if (_log == null)
                      {
                          _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                          var appender = new log4net.Appender.RollingFileAppender();
                          appender.AppendToFile = true;
                          appender.File = string.Format("{0}\\full-log.txt", Config.GetInstance().LogsDirectory);
                          appender.MaximumFileSize = "1MB";
                          appender.MaxSizeRollBackups = 10;
                          appender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size;
                          appender.Threshold = log4net.Core.Level.All;
                          appender.Layout = new log4net.Layout.PatternLayout("%date{dd-MM-yyyy HH:mm:ss} %message %n");
                          appender.ActivateOptions();
                          var bufferAppender = new log4net.Appender.BufferingForwardingAppender();
                          bufferAppender.BufferSize = 10;
                          bufferAppender.AddAppender(appender);
                          bufferAppender.ActivateOptions();
                          log4net.Config.BasicConfigurator.Configure(bufferAppender);
                      }
                  }
              }
              return _log;
          }
       
          public static void Info(string logMessage)
          {
              GetInstance().Info(logMessage);
          }
      }

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

    ExpandedWrap disabled
      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) Хотелось бы избавиться от инверсии записей в логе, т.к. хоть разработчику и будет по этим записям понятно как все было на самом деле, все равно это делает чтение лога более напряжным. Но пока я не придумал как можно от этого избавиться.

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


    Рейтинг@Mail.ru
    [ Script execution time: 0,0582 ]   [ 16 queries used ]   [ Generated: 24.04.24, 10:27 GMT ]