1

I've been puzzling over this one for a few days now and it's got me pretty beaten, but to be honest I'm not all that experienced yet and I'm having trouble with DataGridView - which seems a common topic.

public partial class frmMain : Form
{
    ServerConnection sabCom;
    private BindingSource jobSource = new BindingSource();
    private void timer1_Tick(object sender, EventArgs e)
    {
            if (bgUpdateThread.IsBusy == false)
            {
                bgUpdateThread.RunWorkerAsync(sabCom);
            }
        }
    }

    private void frmMain_Load(object sender, EventArgs e)
    {
        timer1.Interval = 3000;
        timer1.Start();
    }

    private void bgUpdateThread_DoWork(object sender, DoWorkEventArgs e)
    {
        ServerConnection s = e.Argument as ServerConnection;
        s.update();
        e.Result = s;
    }

    private void bgUpdateThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.sabCom = e.Result as ServerConnection;

        if (dgvQueueFormatted == false)
        {
            dgvQueue_Init();  //Applies formatting and loads column width. Inits data sources.
        }
        else
        {
            dgvQueue_Update();

        }
    }

    private void dgvQueue_Update()
    {
        dgvQueue.Refresh();
    }

    private void dgvQueue_Init()
    {
        try
        {
            jobSource.DataSource = sabCom.queue.jobs;
            dgvQueue.DataSource = jobSource;
            try
            {
                //Apply saved column spacing to the dgvQueue
                //Uses reflection to set dgvQueue to DoubleBuffer
            }
            catch
            { }
        }
        catch
        { }
    }

    private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
    {
        //Saves information about the dgvQueue on shutdown.
    }

Queue Class:

public class Queue  
{
    private string _status;
    public string status { get { return _status; } set { _status = value; } }
    private string _eta;
    public string eta { get { return _eta; } set { _eta = value; } }
    private List<Job> _jobs;
    public List<Job> jobs
    {
        get
        {
            return _jobs;
        }
        set
        {
            _jobs = value;
        }
    }
    private List<string> _categories;
    public List<string> categories { get { return _categories; } set { _categories = value; } }

    private XmlDocument xmld;
    private ServerConnection m_parent;
    private XmlNodeList _xmljobs;

    public Queue(ServerConnection srvConn)
    {
        //fetch the Queue xml
        m_parent = srvConn;
        xmld = new XmlDocument();
        _jobs = new List<Job>();
    }

    public void update()
    {
        updateXml();
        updateQueue();
        updateJobs();
    }

    private void updateXml()
    {
        //Loads xml file into xmld
    }

    private void updateQueue()
    {
        XmlNodeList elements = xmld.SelectNodes("queue");

        foreach (XmlNode element in elements)
        {
            _status = element.SelectSingleNode("status").InnerText;
            _eta = element.SelectSingleNode("eta").InnerText;
        }
    }

    private void updateJobs()
    {
        _xmljobs = xmld.SelectNodes("queue/job");
        jobs.Clear();

        foreach (XmlNode xmljob in _xmljobs)
        {
            Job t_job;

            _status = xmljob.SelectSingleNode("status").InnerText;
            _eta = xmljob.SelectSingleNode("eta").InnerText;

            //Create temp job to match against list.
            t_job = new Job(_status, _eta);
            jobs.Add(t_job);
        }
    }

Job class: In reality it holds around 30 values of varying types, but they're all in the same format:

public class Job
{
    private int _status;
    public int status { get { return _status; } set { _status = value; } }
    private string _eta;
    public string eta { get { return _eta; } set { _eta = value; } }


    public Job(string status, string eta)
    {
        _status = status;
        _eta = eta;
    }
}

When interacting with the DataGridView I get the error:

The following exception occured in the DataGridView:

System.IndexOutOfRangeException: Index does not have a value. at System.Windows.Forms.CurrencyManager.get_Item(Int32 index) at System.Windows.Forms.DataGridViewDataConnection.GetError(Int32 boundColumnIndex, Int32 columnIndex, Int32 rowIndex)

And when entering the debugger it's triggered on the initial Application.Run(new frmMain(). What on earth am I doing wrong? The program still functions and updates normally but I can't even handle the event to suppress the default error message!

Edit - Answer! Instead of clearing the list and re-creating it, just updating the values within it works better. For the moment I have this code:

            t_job = _jobs.FirstOrDefault(c => c.nzo_id == t_nzo_id);
            if (t_job == null) //Job not in list, insert
            {
                t_job = new Job(t_status, i_index, t_eta, i_timeLeft, t_age, i_mbleft, i_mb, t_filename, i_priority, t_category, i_percentage, t_nzo_id, this);
                jobs.Add(t_job);
            }
            else //update object in current list
            {
                jobs[t_job.Index].status = t_status;
                jobs[t_job.Index].priority = i_priority;
                jobs[t_job.Index].category = t_category;
                jobs[t_job.Index].percentage = i_percentage;
                jobs[t_job.Index].timeleft = i_timeLeft;
                jobs[t_job.Index].mbleft = i_mbleft;
            } 

Which prevents it!

HeWhoWas
  • 601
  • 1
  • 10
  • 22
  • It looks to be a binding issue in one of the properties of the Job class. How is your datagridview columns defined, is it set to autogenerate or are you explicitly setting the columns and their bindings. also, please could you add the Job class as well as this is where the binding goes wrong – TBohnen.jnr Jun 02 '11 at 10:09
  • Added the class to the OP, I think it's a binding issue as well. I'm autogenerating the columns because I thought while I was going through the initial testing it would be easier. Is there any particular way I should be going about it? Eventually I would like to implement a way to have the user specify visible colums and their order....but for now, showing data would be good :-P – HeWhoWas Jun 02 '11 at 10:21
  • There is no easy way for me to tell you what is wrong, put a breakpoint on the following line jobSource.DataSource = sabCom.queue.jobs; Then go through all the objects in sabCom.queue.jobs and it's children to see if any of them are null, if any are null that should be your problem, but report back and we can have a look – TBohnen.jnr Jun 02 '11 at 10:42
  • Ok, sorry for the delay in my reply but I ended up sleeping. Anyhow, it's somewhat difficult to get the error to trigger and a breakpoint - the error seems much more likely to happen if I'm interacting with the GridView. For what it's worth though, I added some checking to the code that updates Job/Queue to filter out any results that contain null/"". So it shouldn't be coming through to the dgv.... Edit - This is on a google code SVN if you'd like to have a look at it.....http://code.google.com/p/sabnzbdclient/ If not, thanks for taking the time to answer as far as you did :-) – HeWhoWas Jun 02 '11 at 21:44
  • Unfortunately I'm at work and our proxy server is closed down like you wouldn't believe, but if you don't come right I will have a look tonight at home, let me know here if you do though – TBohnen.jnr Jun 03 '11 at 05:23
  • Ok, so I got your code and loaded a couple of nzbs, everything is working fine, is there any nzb files that it happens with in particular or any way I would be able to reproduce it? – TBohnen.jnr Jun 03 '11 at 19:35
  • Again, researching and sleeping. It could have something to do with the fact that I'm polling over a wireless networks so the updates are taking longer? And that I currently have about 150 nzb's in the queue. But these are circumstances that it's supposed to work under! If you're willing to get into nitty gritty assistance with me, I could provide you with a url/port to connect to my sab interface (or a clone thereof) and see it in action :-) If not, I'll just continue on and make a test queue that's smaller and deal with it when I need to :-P – HeWhoWas Jun 03 '11 at 23:47
  • yeh, I can give it a bash, you can send me the details by email (on my profile page) – TBohnen.jnr Jun 04 '11 at 06:46
  • I can see your facebook details but no email, mine should be visible on my page, if you can send me something and I'll reply. Thanks for taking the time out to help me, can't tell you how much it's appreciated. – HeWhoWas Jun 04 '11 at 13:47
  • So we can't see each other's email accounts :-), Send me an email at theo123@mailinator.com – TBohnen.jnr Jun 04 '11 at 14:12
  • Have a look at my answer, try it out and if it works mark it as correct please. If it doesn't let me know. – TBohnen.jnr Jun 04 '11 at 15:16

1 Answers1

1

The problem seems to be that with a refresh interval of say 1 second, while the user is scrolling or trying to access the fields of the data the data is constantly being removed and readded. This causes the binding to to call try call a value that it thinks might still be there, but isn't and that is why you are getting the index out of range exception

The first thing I would suggest doing is in your updateJobs method as well as all the other lists that are refreshed like the queue list, instead of clearing the list everytime is first checking to see if the job from the xml exists in the current job list, if it does then you can change the current values if the value has changed otherwise do nothing.

To check if the job exists it is as easy as:

t_job = _jobs.FirstOrDefault(c => c.Filename == t_filename);

This will return a null if the job does not exist, I would assume filename might not be unique, so might want to change it so that it really is unique i.e.

t_job = _jobs.FirstOrDefault(c => c.Filename == t_filename && c.nzo_id == t_nzo_id);

One thing you will now have to cater for is that old jobs won't be automatically removed, so whenever a new history job is added, first check to see if it exists in the queue and then remove it there before adding it to the history list.

so change your properties to be:

public int Index 
        { 
            get { return _index; } 
            set 
            { 
                if (_index != value)
                    _index = value; 
            } 
        }

instead of:

public int Index { get { return _index; } set { _index = value; } }

The other thing is that try catch exceptions are expensive, so instead of having:

try { i_percentage = double.Parse(t_percentage); } catch { }

because you know that if there is a value it is going to be a double, you can change it to:

if (!string.IsNullOrEmpty(t_percentage))
    i_percentage = double.Parse(t_percentage);

now if you don't know if the value in t_percentage is going to be a double you can use a try-parse:

if (!string.IsNullOrEmpty(t_percentage))
    double.TryParse(t_percentage,out i_percentage);

This way you avoid the overhead caused by an exception. This might be micro-optimizing and is not always neccessary if it doesn't actually cause a problem, but given that you can have hundreds of jobs, each with 10 or so properties refreshing everysecond, things can actually get noticeably slower if even 2 of the 10 properties throws an exception.

One more thing, after your backgroundworker is completed, in the method: dgvQueue_Update() you are calling the ResetBindings(true); this causes your whole datagrid to refresh instead of just the individual items.

try changing it to:

for (int i = 0; i < jobSource.List.Count; i++)
    jobSource.ResetItem(i);

The difference is this:

this.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, itemIndex));

compared to when you say ResetBindings:

this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
TBohnen.jnr
  • 5,117
  • 1
  • 19
  • 26
  • I've been testing it thoroughly and it seems to have resolved the problem. I changed all the try/catch blocks to what you recommended as well, and it's been running overnight without throwing any errors :-) – HeWhoWas Jun 05 '11 at 01:22