views:

219

answers:

2

I have created a C# Windows Forms application which I've attempted to make as simple as possible to demonstrate a problem I am running into. I'm trying to use a DataGridView to allow user input in one column while simultaneously getting updates in another column from a background thread.

The problem is that the Input column is effectively un-editable because -- I think -- the updates which are intended for the Output column cause the Input column to be updated with it's current value while the user is attempting to change it.

Is this a bug in DataGridView? Is there a better way to do this sort of thing? Can anyone recommend a good workaround?

The following code demonstrates the problem. The Output column will continuously update and the Input column is virtually uneditable. I've merged the designer code (Form1.designer.cs), and Main (from Program.cs) into the form code (Form1.cs) -- so the following code should work on its own.

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Timers;

public partial class Form1 : Form
{
    private System.ComponentModel.IContainer components = null;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.dataGridView = new System.Windows.Forms.DataGridView();
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit();
        this.SuspendLayout();
        // 
        // dataGridView
        // 
        this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
        this.dataGridView.Location = new System.Drawing.Point(3, 12);
        this.dataGridView.Name = "dataGridView";
        this.dataGridView.RowTemplate.Height = 24;
        this.dataGridView.Size = new System.Drawing.Size(322, 158);
        this.dataGridView.TabIndex = 0;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(328, 174);
        this.Controls.Add(this.dataGridView);
        this.Name = "Form1";
        this.Text = "Form1";
        this.Load += new System.EventHandler(this.Form1_Load);
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit();
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.DataGridView dataGridView;

    public Form1()
    {
        InitializeComponent();
    }

    BindingSource bindingSource = new BindingSource();
    BindingList<Item> items = new BindingList<Item>();
    private System.Timers.Timer timer;
    private void Form1_Load(object sender, EventArgs e)
    {
        dataGridView.DataSource = bindingSource;
        bindingSource.DataSource = items;
        items.Add(new Item(dataGridView));

        timer = new System.Timers.Timer {Interval = 50};
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        timer.Start();
    }

    private Random random = new Random();
    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        items[0].SetOutput(random.Next(100));
    }
}


class Item : INotifyPropertyChanged
{
    public int Input { get; set; }

    private int output;
    public int Output
    {
        get { return output; }
        private set
        {
            output = value;
            OnPropertyChanged("Output");
        }
    }

    public Control control;

    public Item(Control control)
    {
        this.control = control;
    }

    public void SetOutput(int outputValue)
    {
        Output = outputValue;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            if(!control.IsDisposed)
                control.BeginInvoke(handler, this, new PropertyChangedEventArgs(name));
        }
    }
}

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
A: 

Maybe the cell being edited is losing focus? Perhaps each time you update the other column, store where you were editing, and reset the focus after the update?

John at CashCommons
The cell being edited doesn't appear to lose focus, however it is reset to it's previous value whenever the other column is updated (10 times per second in my example). It can be updated if you type a number and return VERY quickly.
Joe H
+1  A: 

I suspect that when a PropertyChanged event occurs, the DataGridView refreshes all cells, or perhaps only cells in the row that changed (does it happen when you're editing another row ?), losing all uncommited changes.

If you can intercept the event before the DataGridView refreshes the cells, you could save the uncommited changes away to restore them after the refresh. But that would be an ugly workaround...

Did you ask on the MSDN forums ? Maybe someone from MS could give you a more useful answer

Thomas Levesque
I posted the same question on MSDN following your comment here. So far -- in over a week -- I've had no response.
Joe H