Подписать вызов события на событие

171
01 сентября 2018, 07:10

Делаю GUI для MonoGame. Обработка происходит через метод Update(), что не всегда удобно, поэтому я решил вынести некоторый функционал в события. Есть классы MouseListener и KeyboardListener, которые имеют свои события, все вызывается и работает, на этом этапе все хорошо.

public class KeyboardListener : Listener
{
    public event EventHandler<KeyboardEventArgs> KeyUp;
    public event EventHandler<KeyboardEventArgs> KeyDown;
    public event EventHandler<KeyboardEventArgs> KeyPressed;
    public event EventHandler<KeyboardEventArgs> KeyReleased;
    public event EventHandler<KeyboardEventArgs> KeyTyped;
}

Дальше есть обработчик GUI, который следит за нажатиями мыши и, если нажатие произошло поверх элемента интерфейса, ставит на него фокус. Элементы интерфейса также имеют свои события (общие: нажатие мыши, нажатие кнопки клавиатуры, индивидуальные: изменение текста, картинки в PictureBox и т.д.). После того, как элемент становится фокусным, необходимо подписать вызов некоторых его событий на события из MouseListener и KeyboardListener. Я попробовал сделать через анонимные методы, но это работает только в одну сторону - отписаться таким же способом не получается.

public class Control
{
    public event EventHandler<KeyboardEventArgs> KeyUp;
    ...
    protected void OnKeyUp(KeyboardEventArgs args)
    {
        if (KeyUp != null)
            KeyUp.Invoke(this, args);
    }
    ...
    public void SetFocused()
    {
        KeyboardListener.Instance.KeyUp += delegate(object sender, KeyboardEventArgs args)
        {
            OnKeyUp(args);
        };
    }
    public void SetUnfocused()
    {
        KeyboardListener.Instance.KeyUp -= delegate(object sender, KeyboardEventArgs args)
        {
            OnKeyUp(args);
        };
    }
}

Пробовал подписывать метод Control.KeyUp.Invoke на KeyboardListener.Instance.KeyUp, но Control.KeyUp иногда равен null, и не получается подписать метод нулевой ссылки.

Скорее всего, можно решить проблему через создание специального метода для вызова события с сигнатурой, совпадающей с вызывающим событием, но тогда получится +18 служебных методов, а их и так уже порядком хватает.

Вопрос: как сделать то, что я пытался сделать выше, с наименьшими потерями?
И подвопрос: по-вашему, стоит ли так потеть ради событийного GUI, или есть какой-нибудь другой вариант, чтобы не приходилось раздувать метод Update на 500 строк, вмещая в него всю логику, какую только можно было придумать?

P.S. не бейте за стену текста, не придумал, как объяснить короче

UPD: Смотрел различные open-source GUI, нашел весьма неплохой вариант: не объявлять в Control общие события вроде KeyDown или MouseMoved, а заменить их виртуальными методами вроде OnKeyDown, OnMouseMoved. В производных классах переопределять эти методы, заключая в них логику, свойственную данному элементу управления, например, проверку на нажатость кнопки для Button. И в производных классах реализовывать только специализированные события вроде Button.Pressed или TextBox.TextChanged.

Answer 1

Отвечаю сам, т.к. нашел решение для себя.

Для моего случая не обязательно подписывать событие на событие (вообще события объявлять не нужно, за исключением некоторых функциональных), можно обойтись иначе, т.к. поведение у разных экземпляров класса Label, например, меняться не будет.

public class Control
{
    //public event EventHandler<MouseEventArgs> MouseDown;
    protected virtual void OnMouseDown(object sender, MouseEventArgs args)
    {
    }
}
public class Button : Control
{
    // Обеспечение функционала конкретного элемента
    public event EventHandler<EventArgs> Pressed;
    private void OnPressed(EventArgs args)
    {
        if (Pressed != null)
            Pressed.Invoke(this, args);
    }
    // Замена подписыванию на Control.MouseDown
    protected override void OnMouseDown(object sender, MouseEventArgs args)
    {
        if (/* кнопка нажата */)
            OnPressed(EventArgs.Empty);
    }
}

Ну и для тех, кто будет по названию вопроса искать решение схожей проблемы:
Объявить метод сигнатуры вызывающего события, который будет инвокать вызываемое событие:

// событие, на которое надо подписаться
public event EventHandler<EventArgs> Event1
// событие, вызов которого нужно подписать
public event EventHandler<EventArgs> Event2;
// метод для "ручного" вызова
private void OnEvent2(EventArgs args)
{
    if (Event2 != null)
        Event2.Invoke(this, args);
}
// метод для подписывания на Event1
public void InvokeEvent2(object sender, EventArgs args)
{
    if (Event2 != null)
        Event2.Invoke(this, args);
}
// подписывание
Event1 += InvokeEvent2;
READ ALSO
Повторение записи в ячейке (подсказка) DataGrid wpf

Повторение записи в ячейке (подсказка) DataGrid wpf

Подскажите, как сделать, чтобы текст, раннее введенный в ячейку DataGrid, повторялся после следующего ввода в другую ячейку (примерно как в Excel,...

202
Перевод SQL запроса в LINQ

Перевод SQL запроса в LINQ

ЗдраствуйтеЕсть такой SQL запрос

180
Как организовать очередь к файлу между сессиями

Как организовать очередь к файлу между сессиями

Допустим есть файлВ один момент времени им может пользоваться один инстанс программы

185
Как перехватить ошибку в методе PHP?

Как перехватить ошибку в методе PHP?

В каком-то месте "падает" метод:

187