1

I have several buttons, and I want them to do something when the cursor has been positioned over them for an already specified time. In this case they should just write their content in a textbox.

This is the Timer:

private static System.Timers.Timer myTimer = 
        new System.Timers.Timer(1500);

This is the method the buttons execute with the MouseEnter event:

private void keysHover(object sender, EventArgs e)
    {
        myTimer.Elapsed += delegate { keysHoverOK(sender); };
        myTimer.Enabled = true;
    }

And this is what gets executed if the Timer finishes:

private void keysHoverOK(object sender)
    {
        this.Dispatcher.Invoke((Action)(() =>
        {
            txtTest.Text += (sender as System.Windows.Controls.Button).Content.ToString();
        }));
        myTimer.Enabled = false;

    }

I don't quite understand why this is happening, but everytime one of the buttons completes the Timer the keysHoverOK method will write as many characters as there have been hovered. For example, if I hover over the button A, it will write A, if I then hover over the button B, it will write AB, thus getting AAB written on the textbox and so on and so forth, the sentence executes as many times as the rest of the buttons have executed the keysHover method, even if they didn't complete the Timer themselves, it's like their content got saved somewhere. Now of course all I want the buttons to do is to write their content and their content only. So do you have an idea of what I'm doing wrong?

user1676874
  • 875
  • 2
  • 24
  • 45

2 Answers2

0

Do you mean the MouseEnter event? I'm not aware of any MouseOver event in WPF.

Without a good, minimal, complete code example, it's impossible to know for sure what the problem is. However, based on the small amount of code you've shared and your problem description, it appears that your main issue is that you're sharing a single Timer object with multiple controls. This is exacerbated by the fact that when one control subscribes to the Timer.Elapsed event, it never unsubscribes. So if another control enables the timer (subscribing to the event as well), both controls are notified when the timer interval elapses.

Even a single control is problematic, as it subscribes itself to the event each time the MouseEnter event is raised.

The fix is to disable the timer and unsubscribe from the event when the mouse leaves the bounds of the control, or when the timer interval has elapsed. That might look something like this:

private EventHandler _timerElapsedHandler;

// Subscribed to the MouseEnter event
private void keysHover(object sender, EventArgs e)
{
    _timerElapsedHandler = delegate { keysHoverOK(sender); };
    myTimer.Elapsed += _timerElapsedHandler;
    myTimer.Enabled = true;
}

// Subscribed to the MouseLeave event
private void keysLeave(object sender, EventArgs e)
{
    DisableTimer();
}

private void keysHoverOK(object sender)
{
    this.Dispatcher.Invoke((Action)(() =>
    {
        txtTest.Text += (sender as System.Windows.Controls.Button).Content.ToString();
    }));
    DisableTimer();
}

private void DisableTimer()
{
    myTimer.Elapsed -= _timerElapsedHandler;
    myTimer.Enabled = false;
    _timerElapsedHandler = null;
}

Other comments:

  • You should cast instead of using as. Only use as when a reference can legitimately be of a different type than you are checking for. Use a cast when it is always supposed to be the type you are checking for. That way, if you have a bug, you will get a meaningful exception, instead of just some NullReferenceException
  • The above example fixes the problem with the least disruption to your code. But really, I would make other changes too. For example, rather than storing the delegate in a field, I would just get the Content.ToString() value and store that. Then instead of using an anonymous method for the delegate instance, I would use a named method that simply uses the stored string value to append to the Text property. You can subscribe and unsubscribe the named method by name; the delegate type does the right thing even though it's using a different delegate instance for the subscribe and the unsubscribe.
  • Another change you might consider making is to use a different Timer instance for each control. Then you don't have to subscribe or unsubscribe as the mouse events occur; just subscribe during initialization.
  • Finally, especially as this is WPF code, you really should consider storing the appended text in an observable property (e.g. DependencyProperty, or implement INotifyPropertyChanged), and bind it to the txtTest.Text property rather than manipulating that property directly.
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
0

I'm assuming when you say:

This is the method the buttons execute with the MouseOver event:

You might mean the MouseEnter event?

From what I see:

  • You have one central timer
  • It will start the elapsed count down on the first button you enter
  • You have not stopped the timer if you leave that button before it elapses
  • You seem only to add delegates to the event without removing any

The code segment myTimer.Elapsed += delegate { keysHoverOK(sender); }; adds another delegate to the list of already added delegates. It does not replace the list with just one delegate.

If you leave the button before the timer elapses you need to remove the delegate from the timer elapsed event using the minus-equal operator (myTimer.Elapsed -= ....), and then stop the timer. Here you have a problem that you've created an anonymous method so you'd need:-

  1. Research into removing anonymous methods

or

  1. Research into removing all event handlers

or possibly the simplest menthod

  1. Stop and destroy any running timer and create a new timer instance each time you enter the button.
Community
  • 1
  • 1
Rhys
  • 4,511
  • 2
  • 23
  • 32