Good day to all!

I write my own GUI-interface for the toy.

Class Button .

There is the following set of delegate events:

 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 something like this:

 private void OnMouseDown() { EventHandler tempHandler = MouseDownHandler; // ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Π΄Π΅Π»Π΅Π³Π°Ρ‚ события if (tempHandler != null) // провСряСм, Π½Π΅ пустой Π»ΠΈ Π΄Π΅Π»Π΅Π³Π°Ρ‚ { tempHandler(this, EventArgs.Empty); // Π²Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌ событиС } _state = ButtonState.Click; // ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для опрСдСлСния ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½Ρ‹Ρ… ΠΊΠΎΠΎΡ€Π΄ΠΈΠ½Π°Ρ‚ Π½Π° спрайтС тСкстуры ΠΊΠ½ΠΎΠΏΠΊΠΈ Π²ΠΎ врСмя Π΅Π΅ рисования } 

Appropriate methods are present for other delegate events. In the same class, there is 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) } 

In another place an instance of the button is created:

 Button button = new Button(...); 

Respectively properties, textures, etc. are set. It all works. Further, in the same "other place", the delegate is assigned a method:

 button.MouseInHandler += НазваниС_ΠΌΠ΅Ρ‚ΠΎΠ΄Π°; 

With this, I think everything should be clear. The logic is briefly - an object is created, a method is assigned to it in a special delegate, after which the Update() method checks if the button and cursor intersection, clicked, etc. and based on this, the event we need is already triggered.

The problem that exists now: 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 using additional fields:

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

With a change in the logic structure 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 it all works, as long as the user behaves normally. However, if you try different situations, for example, hold the paint button, then move the button and release, or hold the paint button over the button and release it in another place - all this leads to the fact that at some point an event that should it was to be called - not called, or vice versa, that event is raised, which should not be called.

I tried to describe my problem in as much detail as possible, but if there are any questions, ask, clarify if necessary.

  • Have you tried to track the sequence of events and their processing using Debug.WriteLine("Π‘Ρ‹Π»ΠΎ событиС наТатия Π½Π° Π»Π΅Π².ΠΊΠ»Π°Π²ΠΈΡˆΡƒ") , etc.? It seems that somewhere along with a subscription to an event there is a need to unsubscribe from it, although I am not sure. - Bulson
  • Yes. I defined methods with the following content: pastebin.com/CUvpEdNZ Actually, this allowed me to understand that events are triggered multiple times. About the unsubscribe and subscription - it is logical, I tried to do it with add. fields, but I feel that the approach is a little wrong, because the interweaving of = true, = false begins and at some point the code becomes impossible to read. - Aquinary
  • 2
    Once again, Razik reread the penultimate paragraph and there was this thought: here in WPF there are tunneling & bubbling events, only there are RoutedEvent events and there you can stop further downward or upstream calls of events in the event handler. It looks like you need to implement something like this. - Bulson
  • one
    @Bulson +1, or as in forms, raise an event only for the top visible item, and ignore for the bottom. And it would be nice to get rid of the need to check the event for null, and to protect yourself from pushing this very null there. Announcement of events slightly gets fatter, but handlers will lose weight for the better, because they are executed, unlike announcements. - rdorn

1 answer 1

Usually for UI-elements introduce the concept of Capture.

When the MouseDown event MouseDown , the current item under the mouse is fixed. This fixation is called capture. After this, all mouse movement events are delivered only to this item . At the same time, the release event of the mouse pulls the auto-generation of a click only if the mouse was inside the element with capture. After releasing the mouse, the capture is reset.

In this way, unexpected combinations of events disappear. You can check the capture operation on any application window: click on the close button of the window in the upper right corner, and, without releasing the mouse button, remove it 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 only happens if the previous state of the mouse was not pressed.

  • I flourish. Using the edits you made to the logical structure of the code and adding Capture tracking to the element, I achieved the desired and received quite acceptable code. - Aquinary