Крекер Windows-сообщений в AOP-стиле
От: VladD2 Российская Империя www.nemerle.org
Дата: 10.05.04 21:43
Оценка: 111 (12)
Давно приводил его в качестве примера кому-то. Недавно понадобилось самому, и чтобы в дальнйшем не искать решил выложить сюда.

Общая идея очень проста. Вместо использования огромного switch-а можно использовать этот небольшой класс. Помечаете метод атрибутам содержащими идентификатор сообщения и управление автоматом приходит в этот метод.

Реализация крэкера сообщений (файл MsgCrecker.cs):
using System;
using System.Collections;
using System.Windows.Forms;
using System.Reflection;

/// <summary>
/// Атрибут используемый для пометки матода и ассоциации с ним 
/// нужного идентификатора сообщения.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class WinMsgAttribute : Attribute
{
    int _msgID;

    public WinMsgAttribute(int msgID)
    {
        _msgID = msgID;
    }

    public int MsgID
    { 
        get { return _msgID; }
        set { _msgID = value; }
    }
}

delegate bool WinMsgHandler(ref Message msg);

/// <summary>
/// Вскрывальщик сообщений. Находит метода помеченные атрибутом
/// WinMsgAttribute и ассоциирует их с сообщениями идентификаторы
/// которых хранятся в WinMsgAttribute.
/// </summary>
public class MsgCrecker
{
    Hashtable _msgMap = new Hashtable();
    static Type _attrType = typeof(WinMsgAttribute);
    static Type _delegateType = typeof(WinMsgHandler);

    public MsgCrecker(Form form)
    {
        // Получаем список методов формы.
        MethodInfo[] methods = form.GetType().GetMethods(
            BindingFlags.Instance | BindingFlags.Public 
            | BindingFlags.NonPublic);

        foreach (MethodInfo method in methods)
        {
            // Получаем список атрибутов WinMsgAttribute каждого метода.
            object[] attrs = method.GetCustomAttributes(_attrType, false);
            // Если атрибут WinMsgAttribute ассоциирован с методом...
            if (attrs.Length > 0)
            {
                // Перебираем их и...
                foreach (WinMsgAttribute attr in attrs)
                {
                    // Засандаливаем в хэш-таблицу, ассоциируя с делегатом содержащим
                    // ссылку на метод-обработчик.
                    _msgMap.Add(attr.MsgID,
                        Delegate.CreateDelegate(_delegateType, form, method.Name, true));
                }
            }
        }
    }

    /// <summary>
    /// Пытается обработать сообщение. Если не удается, возвращает true.
    /// Если удается, то возвращает значение возвращенное методом-обработчиком.
    /// </summary>
    /// <example>
    /// protected override void WndProc(ref Message msg)
    /// {
    ///     if (_msgCrecker.ProcessMsg(ref msg))
    ///         base.WndProc(ref msg);
    /// }
    /// </example>
    /// <param name="msg">Сообщение.</param>
    /// <returns>Если true, то нужно вызывать метод WndProc базового класса.</returns>
    public bool ProcessMsg(ref Message msg)
    {
        // Пытаемся нафти делегат ассоциированный с пришедшим сообщением.
        WinMsgHandler handler = (WinMsgHandler)_msgMap[msg.Msg];
        if (handler != null) // Если удается...
            return handler(ref msg); // Вызываем метод.
        return true; // Приказываем вызвать базовую реализацию WndProc.
    }
}


Ниже пример его использования. В нем обрабатываются сообщения WM_LBUTTONDOWN и WM_RBUTTONDOWN.
На WM_LBUTTONDOWN BackColor-у присваевается синий цвет, на WM_RBUTTONDOWN — расный. Или наоборот.
Уменее ничего не придумал :
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace AutoMsg
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            // TODO: Add any constructor code after InitializeComponent call
            _msgCrecker = new MsgCrecker(this);
        }

        /// <summary>Clean up any resources being used.</summary>
        protected override void Dispose(bool disposing)
        {
            if(disposing && components != null)
                components.Dispose();
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.Name = "Form1";
            this.Text = "Form1";
        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Form1());
        }

        // Крэкер. Инициализируется в конструкторе так как ему нужена форма.
        MsgCrecker _msgCrecker = null;

        // Чтобы крэкер работал нужно переопределить WndProc следующим образом:
        protected override void WndProc(ref Message msg)
        {
            if (_msgCrecker.ProcessMsg(ref msg))
                base.WndProc(ref msg);
        }

        // Костанты-идентификаторы сообщений. Можно надыбать на www.codeproject.com или в winuser.h
        const int WM_LBUTTONDOWN   =  0x0201;
        const int WM_RBUTTONDOWN   =  0x0204;

        // Обработчики сообщений. Атрибутом WinMsg с ними ассоциируются сами сообщения.
        
        [WinMsg(WM_RBUTTONDOWN)]
        bool OnRButtonDown(ref Message msg)
        {
            BackColor = Color.Blue;
            return true;
        }

        [WinMsg(WM_LBUTTONDOWN)]
        bool OnLButtonDown(ref Message msg)
        {
            BackColor = Color.Red;
            return true;
        }

    }
}
... << RSDN@Home 1.1.3 beta 2 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.