c# - net - ¿Cómo puedo poblar con seguridad los datos y Actualizar() un DataGridView en una aplicación multiproceso?
propiedades de datagridview (3)
Debe actualizar la cuadrícula en la cadena de interfaz de usuario principal, como todos los demás controles. Ver control.Invocar o Control.BeginInvoke.
Mi aplicación tiene un objeto DataGridView y una lista de tipo MousePos. MousePos es una clase personalizada que contiene las coordenadas X, Y del mouse (de tipo "Point") y un recuento activo de esta posición. Tengo un hilo (System.Timers.Timer) que plantea un evento una vez por segundo, verifica la posición del mouse, agrega y / o actualiza el recuento de la posición del mouse en esta lista.
Me gustaría tener un hilo conductor similar (una vez más, creo que System.Timers.Timer es una buena opción) que volvería a plantear un evento una vez por segundo para actualizar automáticamente () el DataGridView para que el usuario pueda ver los datos en el actualización de la pantalla. (como TaskManager lo hace)
Desafortunadamente, llamar al método DataGridView.Refresh () da como resultado que el VS2005 detenga la ejecución y advirtió que me encontré con una situación de cruce.
Si estoy entendiendo correctamente, tengo 3 hilos ahora:
- Hilo de interfaz de usuario principal
- Hilo de lista de MousePos (temporizador)
- DataGridView Refresh thread (Timer)
Para ver si podía Actualizar () el DataGridView en el hilo principal, agregué un botón al formulario que llamaba a DataGridView.Refresh (), pero esto (curiosamente) no hizo nada. Encontré un tema que parecía indicar que si configuraba DataGridView.DataSource = null y volvía a mi lista, refrescaría la cuadrícula de datos. Y de hecho esto funcionó, pero solo a través del botón (que se maneja en el hilo principal).
Entonces esta pregunta se ha convertido en una de dos partes:
- ¿Está poniendo nulo a DataGridView.DataSource y de regreso a mi Lista una forma aceptable de actualizar la cuadrícula de datos? (Me parece ineficiente ...)
- ¿Cómo puedo hacer esto de forma segura en un entorno de subprocesos múltiples?
Aquí está el código que he escrito hasta ahora (C # /. Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
¡ACTUALIZAR! - Descubrí parcialmente la respuesta a la parte # 1 en el libro "Pro .NET 2.0 Windows Forms and Customer Controls in C #"
Originalmente pensé que Refresh () no estaba haciendo nada y que necesitaba llamar al método Invalidate () , para decirle a Windows que vuelva a pintar mi control en su tiempo libre. (que generalmente es inmediato, pero si necesita una garantía para volver a pintarlo ahora , realice un seguimiento con una llamada inmediata al método Update ()).
dataGridView1.Invalidate();
Pero resulta que el método Refresh () es simplemente un alias para:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
El único problema que encontré con esto fue que si no había datos en dataGridView, ninguna cantidad de invalidación actualizaría el control. Tuve que reasignar el origen de datos. Luego funcionó bien después de eso. Pero solo por la cantidad de filas (o elementos en mi lista): si se agregaron nuevos elementos, dataGridView no se daría cuenta de que había más filas para mostrar.
Por lo tanto, parece que al vincular una fuente de datos (Lista o Tabla) a la Fuente de datos, dataGridView cuenta los elementos (filas) y luego los establece internamente y nunca verifica si hay nuevas filas / elementos o filas / elementos eliminados. Esta es la razón por la que volver a vincular el origen de datos repetidamente estaba funcionando antes.
Ahora, descubra cómo actualizar el número de filas para visualizar en dataGridView sin tener que volver a enlazar la fuente de datos ... ¡diversión, diversión, diversión! :-)
Después de hacer algunas excavaciones, creo que tengo mi respuesta a la parte 2 de mi pregunta (también conocido como seguro multihilo):
En lugar de usar System.Timers.Timer , descubrí que debería usar System.Windows.Forms.Timer en su lugar.
El evento ocurre de tal manera que el método que se usa en la devolución de llamada ocurre automáticamente en el hilo principal. ¡Sin problemas de cross-threading!
La declaración se ve así:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
Y el método es así:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
¡Parece que tienes tu respuesta allí mismo! Solo en cawse tienes curiosidad acerca de cómo hacer las llamadas de hilo cruzado a la interfaz de usuario: Todos los controles tienen un método Invoke () (o BEginInvoke () - en caso de que quieras hacer cosas de manera asincrónica), esto se usa para llamar a cualquier método el control dentro del contexto del hilo principal de UI. Por lo tanto, si llamara a su vista de cuadrícula de datos desde otro subproceso, tendría que hacer lo siguiente:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of ''help'' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}