views:

284

answers:

2

Hello,

I hope that I can explain my problem well enough for someone to help.

Basically, I have a horizontal scrollbar (ranged 0 to 1000) and an edit control that represents the position of the scrollbar divided by 1000, so that the user can use either the scrollbar to select a range of numbers between 0 and 1 up to a 3 decimal precision (.001, .002, ..., .987, etc.), or enter their own number in the edit box. As they scroll the scrollbar, the number in the edit control changes to reflect the new scroll position. When a new number is entered, the scrollbar sets itself to a new position reflecting the number entered. Meanwhile I also perform some calculations with this number as it changes (through either the scrollbar or the edit control) and display the results in another dialog.

Here is my problem: I'm having trouble deciding which event handlers to use to produce the proper behavior when a user enters a number into the edit control.

I'm using a double value variable called fuelMargin to handle my edit control and a CScrollBar control variable called fuelScroll to handle the scrollbar.

In my HSCROLL event I set the edit control to the scroll position / 1000. No problems there; when the user scrolls the scrollbar the edit box is correctly updated.

As for the edit box, my first attempt was an ONCHANGE event:

void MarginDlg::OnEnChangeFueledit()
{
    CEdit* editBox;
    editBox = (CEdit*)GetDlgItem(IDC_FUELEDIT);

    CString editString;
    editBox->GetWindowText(editString);

    if (editString.Compare(".") != 0 && editString.Compare("0.") != 0
     && editString.Compare(".0") != 0 && editString.Compare("0.0") != 0
     && editString.Compare(".00") != 0 && editString.Compare("0.00") != 0)
    {
     UpdateData();
     UpdateData(FALSE);

     if (fuelMargin > 1)
     {
      UpdateData();
      fuelMargin = 1;
      UpdateData(FALSE);
     }
     if (fuelMargin < 0)
     {
      UpdateData();
      fuelMargin = 0;
      UpdateData(FALSE);
     }
     fuelScroll.SetScrollPos(int(fuelMargin*1000));
    }
}

I needed that first if statement in there to keep from doing an UpdateData() when the user is trying to type a number like .5 or .05 or .005. It does produce a few wonky behaviors, though; when the user tries to type something like .56, after the .5 an UpdateData() is performed, the number becomes 0.5, and the cursor is moved to the far left, so if they tried to type .56 they would accidentally end up typing 60.5 -- which goes to 1, since I won't let them enter numbers lower than 0 or higher than 1. If they enter 0.56, however, this behavior is avoided.

For my second attempt, I commented out my ONCHANGE event and put in an ONKILLFOCUS event instead:

void MarginDlg::OnEnKillfocusFueledit()
{
    UpdateData();
    UpdateData(FALSE);

    if (fuelMargin > 1)
    {
     UpdateData();
     fuelMargin = 1;
     UpdateData(FALSE);
    }
    if (fuelMargin < 0)
    {
     UpdateData();
     fuelMargin = 0;
     UpdateData(FALSE);
    }
    fuelScroll.SetScrollPos(int(fuelMargin*1000));
}

So now the user can finish typing their number and all is hunky dory--as long as they click out of the edit box. The scrollbar won't move and results won't be calculated until the box loses focus.

I want the results to be calculated as the numbers in the box are being typed; I want the scrollbar to move as the numbers are being typed. But I don't want typing to be disrupted, i.e. the actual numbers in the box changed or the cursor moved in any way.

Suggestions?

Thanks!

A: 

I'd suggest watching for the Enter key, and performing UpdateData() then as well as OnKillFocus and OnChange. The more user-friendly the better.

Next, make sure your UpdateData() routine only works in the direction you need:

If ".5" is entered in the edit control, run your UpdateData() routine when the OnChange event is raised, but make sure to update your scrollbar only. Don't worry about updating the edit control to "0.5" until OnKillFocus is raised. Any updates in the reverse direction (to the edit control) will mess with your cursor. If your edit control is somehow bound to this double variable and auto updates when the var changes, consider leaving the double and your edit control seperate from each other until the OnKillFocus event is raised.

The same concept applies in the other direction as well. When the user scrolls, don't mess with the scrollbar. Just update the edit control and leave it at that.

I should add that XAML's data-binding features really help in situations like this, if you know how to use them properly. It's unfortunate for us native-type developers that it's so difficult to implement similar functionality using only event handlers.

Giffyguy
+2  A: 

With the first approach, it looks like you're almost there: the only really significant problem is that the repeated calls to UpdateData() mess with the cursor position as the user is typing.

Given that you're trying to have a reasonably complex interaction between the controls, what I'd suggest is not to do validation in the OnChange() at all, so that as the user is typing he can type what he wants (which is how most numeric edit controls work anyway). When the user closes the dialog the controls are on (or clicks a button that uses the data in some way) then validation should be triggered, and a suitable error shown.

Once you're free from validating in OnChange(), you can fix the "cursor moves" problem by simply not calling UpdateData() in OnChange(). Instead, just parse the number from "editString" and, if it's in the valid range, update the scrollbar. That way, the scrollbar updates as the user types, and if they type in an invalid value the scrollbar stays put, and they'll get an error when they move to whatever the next stage is. Something like this (not tested):

void MarginDlg::OnEnChangeFueledit()
{
  CString editString;
  GetDlgItem(IDC_FUELEDIT)->GetWindowText(editString);

  double editValue;
  if ((sscanf(editString,"%lf",&editValue) == 1)
  {
    if (editValue >= 0.0) && (editValue <= 1.0))
      fuelScroll.SetScrollPos(int(editValue*1000));
  }
}

The only remaining important problem to note is that, if the user types some invalid value, or a number out of the valid range, then the edit control and the scrollbar will be out of sync. The simplest way to deal with that is to just decide that the edit control is the "master" value: that is, when we want to know what the user entered, we always look at the edit control, not the scrollbar, and validate data.

As for your second approach, one possible solution might be to implement a timer message handler: in the timer handler you could say the equivalent of "if the user hasn't typed anything for a second, I'll assume they're done, and parse the number and update the scrollbar". I'm not so keen on that as a solution, though.

DavidK
I used this method for my onchange and am very pleased with the results. Thanks!I do minimal error checking in killfocus, i.e. make sure the number is in range, and if it's not I set it to either 0 or 1 (and update the scrollbar accordingly). I would LIKE to update my results there as well but I'm having some issues... so I'm currently in the process of debugging that. But thank you very much for your solution.
Lauren