Question:
Good day to all!
I am writing my own GUI interface for a toy.
Button
class.
We have the following set of event delegates:
public event EventHandler MouseUpHandler;
public event EventHandler MouseDownHandler;
public event EventHandler MouseOutHandler;
public event EventHandler MouseInHandler;
Each event has its own method:
private void OnMouseIn() {...}
private void OnMouseOut() {...}
private void OnMouseUp() {...}
private void OnMouseDown() {...}
They run code like this:
private void OnMouseDown()
{
EventHandler tempHandler = MouseDownHandler; // получаем делегат события
if (tempHandler != null) // проверяем, не пустой ли делегат
{
tempHandler(this, EventArgs.Empty); // вызываем событие
}
_state = ButtonState.Click; // используется для определения правильных координат на спрайте текстуры кнопки во время ее рисования
}
Corresponding methods are present for other event delegates. The same class contains the Update()
method, which calculates the logic of the code:
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)
}
Elsewhere, the button is instantiated:
Button button = new Button(...);
Accordingly, properties, textures, etc. are set. All of this is functioning. Further, in the same "other place", the delegate is assigned a method:
button.MouseInHandler += Название_метода;
With this, I think everything should be clear. The logic in brief – an object is created, a method is assigned to it in a special delegate, after which it is checked in the Update()
method whether there was an intersection of the button and the cursor, whether there was a click, etc. and based on this, the event we need is already called.
The problem that exists now is that the method is called not once, but many times in a row.
All this leads to the fact that, for example, the output to the console of any text occurs many times, but no more than one is required.
I tried to solve the problem with additional fields:
private bool _isMouseUp;
private bool _isMouseDown;
private bool _isMouseIn;
private bool _isMouseOut;
With a change in the structure of the logic of event methods:
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;
}
For MouseDown
and MouseUp
corresponding changes.
And all this works, as long as the user behaves normally. However, if you try different situations, for example, hold down the LMB, then point at the button and release it, or hold the LMB over the button and release it in another place – all this leads to the fact that at some point this or that event, which should was to be called – not called, or vice versa, the event that should not be called is called.
I tried to describe my problem in as much detail as possible, but if there are any questions, ask, I will clarify if necessary.
Answer:
Usually, for UI elements, the concept of Capture is introduced.
When the MouseDown
event occurs, the current element under the mouse is fixed. This commit is called capture. After that , all mouse – move events are delivered only to this element . In this case, the mouse release event pulls the auto-generation of a click only if the mouse was inside the element with capture. After releasing the mouse, capture is reset to zero.
Thus, unexpected combinations of events disappear. You can check how capture works on a window of some application: click on the window close cross in the upper right corner, and, without releasing the mouse button, move it away from the button. Now release the mouse, the cross will not work.
By the way, you have a logical error in your code: for MouseDown you need to check the previous state in the same way as you do for MouseUp: MouseDown occurs only if the previous mouse state was unpressed.