CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 7 of 7
  1. #1
    Join Date
    Jun 2012
    Posts
    37

    Question SLOWING DOWN NumericUpDown control increments

    Hi There, how are you?

    I am working on a software (VC++ 2008) that communicates with a piece of hardware that is pretty "slow". I am having a small problem that, if the user holds the NumericUpDown control button, the software "issues too many times" the same command and then it locks up until the hardware finishes processing - very annoying.

    I would like to SLOW DOWN the NumericUpDown control, but so far I haven't had success with it.

    I tried to use the Acceleration propriety, but couldn't, and actually msdn does not have an example for VC++.

    How could I possibly slow it, or at least, prevent it from "automatically accelerate" on hold?

    Please advise.

    Thank you very much!

  2. #2
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: SLOWING DOWN NumericUpDown control increments

    I was surprised myself when I realized that in deed the Accellerations property doesn't provide a straightforward way to limit the NUD's repeat rate. And I didn't find any other straigtforward way to do this either. You may achieve the primary goal you describe by deriving your own control class from NumericUpDown and overriding the DownButton() and UpButton() methods, simply intercepting calls (i.e. not call the base class implementation) if you determine the hardware isn't yet ready for another update. (Timing control may be implemented similar to what I show below.)

    However, I'm no friend of deriving from Windows Forms control classes unless there doesn't seem to be any other reasonable approach. The approach I'd like to suggest also doesn't actually fiddle around with the actual control's repeat rate at all, preventing to potentially make the control behave "unnatural" from the POV of user expectations. Instead I defer submitting changes to the hardware until I determine I can reasonably expect it to be ready to accept another update.

    I preferred the System::Timers::Timer over the System::Windows::Forms::Timer here, because it can natively be set up as a one-shot timer, removing the need to stop the timer in the tick handler and thereby making the code clearer.

    The non-local variables used are all private form class members:

    Code:
      private:
        Timers::Timer ^m_tmrHardwareGuard;
        Decimal m_decPendingValue;
    These are the relevant form class member functions:

    Code:
    // Form1.cpp
    
    #include "stdafx.h"
    
    #include "Form1.h"
    
    using namespace Test13;
    
    System::Void Form1::numericUpDown1_ValueChanged(System::Object^  sender, System::EventArgs^  e)
    {
      if (!m_tmrHardwareGuard->Enabled) {
        SendToHardware(numericUpDown1->Value);
        m_tmrHardwareGuard->Start();
      } else
        m_decPendingValue = numericUpDown1->Value;
    }
    
    void Form1::GuardTimerElapsedHandler(Object ^sender, Timers::ElapsedEventArgs ^e)
    {
      if (m_decPendingValue != -1) {
        SendToHardware(m_decPendingValue);
        m_decPendingValue = -1;
      }
    }
    And this is the necessary setup done in the form class' c'tor:

    Code:
      public:
        Form1(void) : m_tmrHardwareGuard(gcnew Timers::Timer(250)),  // Easily eye-spottable interval for testing
          m_decPendingValue(-1)  // Means no value pending
        {
          InitializeComponent();
          //
          //TODO: Add the constructor code here
          //
    
          m_tmrHardwareGuard->SynchronizingObject = this;
          m_tmrHardwareGuard->AutoReset = false;
          m_tmrHardwareGuard->Elapsed += gcnew Timers::ElapsedEventHandler(this, &Form1::GuardTimerElapsedHandler);
        }
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  3. #3
    Join Date
    Jun 2012
    Posts
    37

    Re: SLOWING DOWN NumericUpDown control increments

    Wow, I really thought that I was missing something obvious, but apparently I wasn't. How could this control not have such a simple and necessary adjustment?

    Thank you Eri, I'll try to implement the timer control.

    Best wishes and happy new year!

  4. #4
    Join Date
    Jan 2010
    Posts
    1,133

    Re: SLOWING DOWN NumericUpDown control increments

    It seems that the NumericUpDownAcceleration objects within the collection returned by the Accelerations property do not change the number of ValueChanged events per second, but rather determine by what amount the numerical value changes after one of the up/down buttons is held pressed for a given amount of time (so that the value appears to change at a different rate, but "under the hood" only the delta value is different).

    What you could do is to handle the ValueChanged event so that your handler simply does nothing unless a certain amount of time has passed since the last time you actually did handle it (in which case, you let the handler do it's work, and reset the timer). Almost like a frame rate limiter. I'm assuming you don't require a super reliable high precision timer, so you can just use the DateTime value class to get the current total time in miliseconds.

    You can obtain a DateTime representation of current time using the Now static property. Then you can use the Ticks property and convert that to miliseconds, or you can simply store the obtained DateTime as a member variable, and then the next time the event handler is called, you call DateTime::Now again, and subract the old value from it - this will produce a TimeSpan instance, on which you can simply use the TotalMilliseconds property and test it against some threshold value. If it's lower, you do nothing, and repreat the process; if it's higher (or equal), you handle the event and update the stored DateTime to the last obtained instance of DateTime. The first cycle might require some sort of special handling, but otherwise it is relatively straightforward.

  5. #5
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: SLOWING DOWN NumericUpDown control increments

    Quote Originally Posted by TheGreatCthulhu View Post
    It seems that the NumericUpDownAcceleration objects within the collection returned by the Accelerations property do not change the number of ValueChanged events per second, but rather determine by what amount the numerical value changes after one of the up/down buttons is held pressed for a given amount of time (so that the value appears to change at a different rate, but "under the hood" only the delta value is different).
    Yeah, that's the conclusion I ended up with, as well as the OP did, most probably.

    What you could do is to handle the ValueChanged event so that your handler simply does nothing unless a certain amount of time has passed since the last time you actually did handle it (in which case, you let the handler do it's work, and reset the timer). Almost like a frame rate limiter. I'm assuming you don't require a super reliable high precision timer, so you can just use the DateTime value class to get the current total time in miliseconds.
    In fact, the timer-based approach was the first one that came to my mind, and I'd still prefer it, not for the sake of timing precision, but code clarity; and I also find it rather straightforward. Admittedly, however, how straightforwatd the timer-based approach looks to a developer largely depends on how used they are to thinking in terms of event-driven control flow. In addition, I cache premature value updates and submit them as soon as the hardware is considered ready again rather than simply dropping them. This ensures that eventually the value the hardware is set to matches the value set up by the NUD, at the latest at the point of the last user value change plus the guard interval. Implementing submission of the latest cached change at a point when no more ValueChanged events are fired anymore would mean a significant amount of extra complexity in the measurement-based approach, I'm afraid, while that comes for free with the timer approach.

    I wouldn't even bet on that an approach based on measuring the time difference would be simpler in terms of code complexity. Finally, if I'd opt for a time measurement-based aproach, I'd probably use a Stopwatch for that, since that has specifically been designed for measuring time intervals (differences).
    Last edited by Eri523; January 4th, 2014 at 10:28 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  6. #6
    Join Date
    Jan 2010
    Posts
    1,133

    Re: SLOWING DOWN NumericUpDown control increments

    Oh, I was switching among several tabs, didn't see you've answered already - and you gave it quite a bit more thought than I did.

    Quote Originally Posted by Eri523 View Post
    In addition, I cache premature value updates and submit them as soon as the hardware is considered ready again rather than simply dropping them. This ensures that eventually the value the hardware is set to matches the value set up by the NUD, at the latest at the point of the last user value change plus the guard interval.
    Right - I forgot that the user input will still update the control, and that they need to be kept in sync. Actually, the timing (message loop) based approach and the event-firing timer approach are not that different conceptually, because you can think of the event-firing timer as an object that does the time-management related work for you - so using one here is a good choice. Didn't initially cross my mind because for some reason I don't like to use timers.

    A point of interest regarding the System::Timers::Timer class - note that the Elapsed event is raised on a different thread, so one needs to be careful about potential multithreading issues.


    Quote Originally Posted by Eri523 View Post
    Implementing submission of the latest cached change at a point when no more ValueChanged events are fired anymore would mean a significant amount of extra complexity in the measurement-based approach, I'm afraid, while that comes for free with the timer approach.
    Hm, you are right about that too. Alternatively, one could handle the Application::Idle event from the main form to basically create a user-made timer running on the UI thread, based on time measurements, and then do the same thing you did (assuming the updates themselves are relatively fast, there should be no visible impact on the UI responsiveness), but it's better to utilize a ready-made solution.

    Quote Originally Posted by Eri523 View Post
    Finally, if I'd opt for a time measurement-based approach, I'd probably use a Stopwatch for that, since that has specifically been designed for measuring time intervals (differences).
    Yeah, it's probably a better choice; when I just need to "get it done" I use DateTime::Now because it's static, so I don't need to maintain an extra instance variable; also, one can just do (DateTime::Now - lastDateTime)->TotalMiliseconds. Not pretty I know, but what the heck.
    Last edited by TheGreatCthulhu; January 5th, 2014 at 01:22 AM.

  7. #7
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    Re: SLOWING DOWN NumericUpDown control increments

    Quote Originally Posted by TheGreatCthulhu View Post
    [...] Didn't initially cross my mind because for some reason I don't like to use timers.
    One reason I can imagine for an aversion against timers is a historical one: Under Windows 3.1x, and I think 95 as well, timers were a pretty rare and precious resource. IIRC there was a total of four of them available - system-wide!

    A point of interest regarding the System::Timers::Timer class - note that the Elapsed event is raised on a different thread, so one needs to be careful about potential multithreading issues.
    Right, however, in simple scenarios like this one, the burden of synchronization can simply be delegated to the Timer object and then be orgotten about by assigning an object implementing ISynchronizeInvoke to the timer's SynchronizingObject property. In the case here this is the form (which inherits the interface implementation from System::Windows::Forms::Control). In fact, I only did not use this simple solution and implemented synchronization myself in relatively rare and rather complex cases when I wanted to specifically take advantage of the tick handler runnining in parallel on a separate thread.

    The issue of potentially late-fired tick events mentioned by MSDN is relatively small and only of real practical relevance on a heavily loaded system in the scenario here, assuming that practically the only way to close the form is user interaction, so the time span between the last ValueChanged event fired and the form being closed can be assumed to be large compared to the hardware guard interval. It still needs to be kept in mind of course.

    [...] Alternatively, one could handle the Application::Idle event from the main form to basically create a user-made timer running on the UI thread, based on time measurements, and then do the same thing you did (assuming the updates themselves are relatively fast, there should be no visible impact on the UI responsiveness), but it's better to utilize a ready-made solution.
    Now this is an approach I didn't even remotely think of. I'm afraid, however, that there's a realistic chance that Application::Idle gets fired after ValueChanged earlier than expiration of the hardware guard interval. To take care of that case, we'd need something that half-way reliably would wake up the GUI thread later so it can check whether there's still a deferred value update to be submitted to the hardware. ... something like, err, a timer?

    Yeah, [a Stopwatch is] probably a better choice; when I just need to "get it done" I use DateTime::Now because it's static, so I don't need to maintain an extra instance variable; also, one can just do (DateTime::Now - lastDateTime)->TotalMiliseconds. Not pretty I know, but what the heck.
    Well, the Stopwatch has a static member for that purpose as well: GetTimestamp(). But of course using this we'd still need to memorize the starting time, calculate the difference and then convert that to milliseconds (for easier handling): (Stopwatch::GetTimestamp() - llLastTimestamp) * 1000 / Stopwatch::Frequency, so nothing would actually be gained that way.

    After all, with all approaches we discussed, we'd need exactly one variable as our "timing anchor", be that of type Timer, Stopwatch, DateTime or long long.

    BTW, nice to see you 'round here again!
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured