Страницы

Поиск по вопросам

вторник, 31 декабря 2019 г.

Запрет на многократный вызов события при действиях с мышью

#c_sharp #события #логика #xna


Всем доброго времени суток!

Пишу собственный GUI-интерфейс для игрушки. 

Класс Button.

Имеется следующий набор событий-делегатов:

public event EventHandler MouseUpHandler;
public event EventHandler MouseDownHandler;
public event EventHandler MouseOutHandler;
public event EventHandler MouseInHandler;


Каждому из событию соответствует свой метод:

private void OnMouseIn() {...}
private void OnMouseOut() {...}
private void OnMouseUp() {...}
private void OnMouseDown() {...}


В них выполняется примерно такой код:

private void OnMouseDown()
{
    EventHandler tempHandler = MouseDownHandler; // получаем делегат события
    if (tempHandler != null) // проверяем, не пустой ли делегат
    {
        tempHandler(this, EventArgs.Empty); // вызываем событие
    }
    _state = ButtonState.Click; // используется для определения правильных координат
на спрайте текстуры кнопки во время ее рисования
}


Соответствующие методы присутствуют для других событий-делегатов.
В этом же классе присутствует метод 
Update(), который просчитывает логику кода:

public void Update()
{
     /* Формируем данные о положении мыши и о зоне пересечения (на основе позиции
и размеров кнопки) */
     MouseState mouseState = Mouse.GetState();
     Point mousePosition = new Point(mouseState.X, mouseState.Y); 

     Rectangle buttonRectangle = new Rectangle
     (
        (int) this.Position.X, (int) this.Position.Y,
        (int) this.Size.X, (int) this.Size.Y
     );

     if (buttonRectangle.Contains(mousePosition)) // проверяем на наличие пересечения
курсора мыши и кнопки
     {
         if (mouseState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
//ЛКМ - нажатие кнопки мыши
         {
             OnMouseDown();
         }
         if (_mousePrevState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed 
         && mouseState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Released)
// ЛКМ - отпускание кнопки мыши
         {
             OnMouseUp();
         }
     } else // выход курсора за границы кнопки
     {
         OnMouseOut();
     }
    _mousePrevState = mouseState; // сохраняем предыдущее состояние (MouseUp может
быть только после MouseDown)
}


В другом месте создается экземпляр кнопки:

Button button = new Button(...);


Соответственно задаются свойства, текстуры и т.д. Все это функционирует. 
Далее, в том же "другом месте", делегату присваивается метод:

button.MouseInHandler += Название_метода;


С этим, думаю, все должно быть понятно. 
Логика вкратце - создается объект, ему в специальный делегат присваивается метод,
после чего в методе 
Update() проверяется, было ли пересечение кнопки и курсора, был ли клик и т.д. и
на основе этого уже вызывается нужное нам событие. 

Проблема, которая имеется сейчас: метод вызывается не единожды, а множество раз подряд.

Все это приводит к тому, что, например, вывод в консоль любого текста происходит
множество раз, но требуется не более одного. 

Я попробовал решить проблему с помощью дополнительных полей:

private bool _isMouseUp;
private bool _isMouseDown;
private bool _isMouseIn;
private bool _isMouseOut;


С изменением структуры логики методов-событий:

private void OnMouseIn()
{
    if (!_isMouseIn) // если событие не вызывалось 
    {
         EventHandler tempHandler = MouseInHandler;
         if (tempHandler != null)
         {
              tempHandler(this, EventArgs.Empty);
         }
         _isMouseIn = true; // определяем событие, как вызванное и не даем ему совершиться
повторно
         _isMouseOut = false; // после In события можно допустить выполнение Out события
    }
    _state = ButtonState.Hover;
}
// примерно такой же код ниже, разве что теперь Out и In поменялись местами
private void OnMouseOut()
{
    if (!_isMouseOut)
    {
         EventHandler tempHandler = MouseOutHandler;
         if (tempHandler != null)
         {
              tempHandler(this, EventArgs.Empty);
         }
         _isMouseOut = true;
         _isMouseIn = false;
    }
    _state = ButtonState.Normal;
}


Для MouseDown и MouseUp соответствующие изменения. 

И все это работает, до тех пор, пока пользователь ведет себя нормально. Однако если
пробовать различные ситуации, например, зажать ЛКМ, после чего навести на кнопку и
отпустить, или зажать ЛКМ над кнопкой и отпустить ее в другом месте - все это приводит
к тому, что в какой-либо момент то или иное событие, которое должно было быть вызвано
- не вызывается, либо наоборот, вызывается то событие, которые вызываться не должно. 

Я попробовал как можно более подробно описать свою проблему, но если будут какие-нибудь
вопросы - задавайте, уточню, если нужно.
    


Ответы

Ответ 1



Обычно для UI-элементов вводят понятие Capture. Когда происходит событие MouseDown, текущий элемент под мышью фиксируется. Эта фиксация и называется capture. После этого все события о перемещении мыши доставляются только этому элементу. При этом событие отпускания мыши тянет за собой автогенерацию клика только если при этом мышь была внутри элемента с capture. После отпускания мыши capture обнуляется. Таким образом исчезают неожиданные комбинации событий. Вы можете проверить работу capture на окне какого-нибудь приложения: нажмите на крестик закрытия окна в правом верхнем углу, и, не отпуская кнопку мыши, уведите её из кнопки. Теперь отпустите мышь, крестик не сработает. Кстати, у вас в коде логическая ошибка: для MouseDown нужно проверять предыдущее состояние точно так же, как вы делаете для MouseUp: MouseDown происходит только если предыдущее состоянии мыши было ненажатым.

Комментариев нет:

Отправить комментарий