Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.141.8.247] |
|
Сообщ.
#1
,
|
|
|
Здравствуйте,
Накатал тут небольшой компонентик, для логирования действий пользователя. Думаю, что кому-то может оказаться полезным. Компонет просто кладется на форму и логирует события от контролов на форме. Исходный код компонента: 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)); } } } 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, например так: ControlsLogger.ControlsLogger.Log = Log.Info; Класс Log у меня использует log4net и выглядит так: 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); } } Результирующий лог может выглядеть так: 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) Хотелось бы избавиться от инверсии записей в логе, т.к. хоть разработчику и будет по этим записям понятно как все было на самом деле, все равно это делает чтение лога более напряжным. Но пока я не придумал как можно от этого избавиться. В общем, в итоге получается очень простой и рабочий компонент-логгер. Желаю всем быстрого отлова и исправления багов. |