Всем доброго времени суток!
Пишу собственный 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 соответствующие изменения.
И все это работает, до тех пор, пока пользователь ведет себя нормально. Однако если пробовать различные ситуации, например, зажать ЛКМ, после чего навести на кнопку и отпустить, или зажать ЛКМ над кнопкой и отпустить ее в другом месте - все это приводит к тому, что в какой-либо момент то или иное событие, которое должно было быть вызвано - не вызывается, либо наоборот, вызывается то событие, которые вызываться не должно.
Я попробовал как можно более подробно описать свою проблему, но если будут какие-нибудь вопросы - задавайте, уточню, если нужно.
Ответ
Обычно для UI-элементов вводят понятие Capture.
Когда происходит событие MouseDown, текущий элемент под мышью фиксируется. Эта фиксация и называется capture. После этого все события о перемещении мыши доставляются только этому элементу. При этом событие отпускания мыши тянет за собой автогенерацию клика только если при этом мышь была внутри элемента с capture. После отпускания мыши capture обнуляется.
Таким образом исчезают неожиданные комбинации событий. Вы можете проверить работу capture на окне какого-нибудь приложения: нажмите на крестик закрытия окна в правом верхнем углу, и, не отпуская кнопку мыши, уведите её из кнопки. Теперь отпустите мышь, крестик не сработает.
Кстати, у вас в коде логическая ошибка: для MouseDown нужно проверять предыдущее состояние точно так же, как вы делаете для MouseUp: MouseDown происходит только если предыдущее состоянии мыши было ненажатым.
Комментариев нет:
Отправить комментарий