Terminal.Gui icon indicating copy to clipboard operation
Terminal.Gui copied to clipboard

I am using combobox control, when I delete characters It throws the same error **IndexOutOfRangeException**

Open tznind opened this issue 2 years ago • 7 comments

I am using combobox control, when I delete characters It throws the same error IndexOutOfRangeException

Originally posted by @ttool22 in https://github.com/gui-cs/Terminal.Gui/issues/1250#issuecomment-1211739635

tznind avatar Aug 11 '22 09:08 tznind

@ttool22 I have moved the discussion to a new issue because the one you posted in is about scroll view. So while the Exception is the same (IndexOutOfRangeException) the root cause is likely to be different.

tznind avatar Aug 11 '22 09:08 tznind

I've created a new console project with a combo box and it is working fine in 1.7.2

using Terminal.Gui;

Application.Init();

var w = new Window();

ComboBox combobox1 = new ComboBox();
combobox1.SetSource(new List<string> { "cow", "dog", "fish" });
combobox1.Width = 10;
combobox1.Height = 4;
w.Add(combobox1);

Application.Run(w);

Application.Shutdown();

Can you provide more information about how your use case varies from the above?

  • Do you have any event handlers?
  • Do you ever change the source of the combo box? (e.g. from another Thread or in a callback)

One thing that might help is if you can get a stack trace. The easiest way to do this is to add an Exception handler to the main loop. To do that you can swap Application.Run(myWindow) to use the overload:

Application.Run(myWindow, (e) =>
        { 
            MessageBox.ErrorQuery("Badness", e.ToString(), "Ok");
            return false;
        }
        );

If you implement the above block you should get an error box instead of a hard crash:

image Example error message shown by registering an Exception callback

tznind avatar Aug 11 '22 09:08 tznind

Yes, I have event handler for refreshing time every second. I call eventhandler for refreshing time in OpenSelectedItem

timer.Elapsed += RefreshMatchTime; timer.Start();

static void RefreshMatchTime(object sender, ElapsedEventArgs e) { MatchTime.Text = DateTime.Now.Second.ToString(); Application.Refresh(); }

ttool22 avatar Aug 11 '22 09:08 ttool22

Ok thanks for the info. I think I am able to reproduce your error. Here is my repro:

using System.Timers;
using Terminal.Gui;

Application.Init();

var w = new Window();

ComboBox combobox1 = new ComboBox();
combobox1.SetSource(new List<string> { "cow", "dog", "fish" });
combobox1.Width = 10;
combobox1.Height = 4;
w.Add(combobox1);

// I have changed this to every 100th of a second to make the Exception manifest faster
var timer = new System.Timers.Timer(10);
timer.Elapsed += (s, e) => {
    combobox1.Text = DateTime.Now.Second.ToString();
    Application.Refresh();
};
timer.Start();


Application.Run(w, (e) =>
        { 
            MessageBox.ErrorQuery("Badness", e.ToString(), "Ok");
            return false;
        }
        );

Application.Shutdown();

The solution is to always use Application.MainLoop.Invoke to update UI properties. Please can you try updating your RefreshMatchTime method to use invoke:

var timer = new System.Timers.Timer(100);
timer.Elapsed += (s, e) => {
    Application.MainLoop.Invoke(()=> { combobox1.Text = DateTime.Now.Second.ToString(); });
};

Make sure to get rid of your call to Application.Refresh(); too.

madclickgood After applying the fix (MainLoop.Invoke)

tznind avatar Aug 11 '22 09:08 tznind

@ttool22 let me know if my suggestion works. Alternatively if you haven't understood what I have said or how to implement it please say and I will try to rephrase.

If you do get it working make sure to close the issue as resolved

tznind avatar Aug 11 '22 13:08 tznind

@tznind

Unfortunately it doesn't work

ttool22 avatar Aug 12 '22 08:08 ttool22

Can you share more of your code please? including the bit you updated with Application.MainLoop.Invoke

tznind avatar Aug 12 '22 08:08 tznind

timer.Elapsed += RefreshMatchTime; timer.Start();

static void RefreshMatchTime(object sender, ElapsedEventArgs e)
{
    try
    {
        string matchTime = string.Empty;        
        Constants.Seconds = Constants.Seconds + 1;
        matchTime = Constants.Minutes + " : " + (Constants.Seconds).ToString();

        Application.MainLoop?.Invoke(() =>
        {         
            Constants.MatchTime.Text = matchTime;
            Application.Refresh();
        });
    }
    catch (Exception ex)
    {

    }
}


ttool22 avatar Aug 12 '22 12:08 ttool22

I call it inside

ComboboxControl.OpenSelectedItem += async (ListViewItemEventArgs control) =>
{
  //code above
}

}

ttool22 avatar Aug 12 '22 12:08 ttool22

You didn't call it inside ComboboxControl.OpenSelectedItem, When I click on item in Combobox I start refresh time every second then I remove characters from combo, then error is thrown "IndexOutOfRangeEdxception"

ttool22 avatar Aug 12 '22 12:08 ttool22

Remove Application.Refresh(); from the code because the Application.MainLoop?.Invoke already will update the UI Thread.

BDisp avatar Aug 12 '22 13:08 BDisp

I have updated and changed the assignment to be a Label rather than the combo box as I assume you are using. It is still working as intended with no Exception.

onopen

The differences I can see vs your code are:

  • I only create one timer (regardless of how many times user confirms selection)
  • I don't call Application.Refresh (as @BDisp said)
using System.Timers;
using Terminal.Gui;

Application.Init();

var w = new Window();

Label matchTime = new Label();
matchTime.Width = 20;
matchTime.Height = 1;
matchTime.Y = 6;

ComboBox combobox1 = new ComboBox();
combobox1.SetSource(new List<string> { "cow", "dog", "fish" });
combobox1.Width = 10;
combobox1.Height = 4;

w.Add(combobox1);
w.Add(matchTime);

System.Timers.Timer? timer = null;

/*I've added async keyword here because you have it in your code but it has no effect*/
combobox1.OpenSelectedItem += async (ListViewItemEventArgs control) =>
{
    // do not create multiple timers
    if(timer == null)
    {
        timer = new System.Timers.Timer(10);
        timer.Elapsed += (s, e) =>
        {
            Application.MainLoop.Invoke(() => { matchTime.Text = DateTime.Now.Second.ToString(); });
        };
    }
    timer.Start();
};

Application.Run(w, (e) =>
        { 
            MessageBox.ErrorQuery("Badness", e.ToString(), "Ok");
            return false;
        }
        );

Application.Shutdown();

tznind avatar Aug 12 '22 13:08 tznind