2010-04-23 8 views
10

Estoy usando Windows Forms. Durante mucho tiempo, pictureBox.Invalidate(); trabajó para volver a dibujar la pantalla. Sin embargo, ahora no funciona y no estoy seguro de por qué.C#: Windows Forms: ¿Qué podría causar Invalidate() para no volver a dibujar?

this.worldBox = new System.Windows.Forms.PictureBox(); 
this.worldBox.BackColor = System.Drawing.SystemColors.Control; 
this.worldBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 
this.worldBox.Location = new System.Drawing.Point(170, 82); 
this.worldBox.Name = "worldBox"; 
this.worldBox.Size = new System.Drawing.Size(261, 250); 
this.worldBox.TabIndex = 0; 
this.worldBox.TabStop = false; 
this.worldBox.MouseMove += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseMove); 
this.worldBox.MouseDown += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseDown); 
this.worldBox.MouseUp += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseUp); 

llamados en mi código para dibujar el mundo apropiadamente:

view.DrawWorldBox(worldBox, canvas, gameEngine.GameObjectManager.Controllers, 
    selectedGameObjects, LevelEditorUtils.PREVIEWS); 

View.DrawWorldBox:

public void DrawWorldBox(PictureBox worldBox, 
    Panel canvas, 
    ICollection<IGameObjectController> controllers, 
    ICollection<IGameObjectController> selectedGameObjects, 
    IDictionary<string, Image> previews) 
{ 
    int left = Math.Abs(worldBox.Location.X); 
    int top = Math.Abs(worldBox.Location.Y); 
    Rectangle screenRect = new Rectangle(left, top, canvas.Width, 
     canvas.Height); 

    IDictionary<float, ICollection<IGameObjectController>> layers = 
     LevelEditorUtils.LayersOfControllers(controllers); 
    IOrderedEnumerable<KeyValuePair<float, 
     ICollection<IGameObjectController>>> sortedLayers 
      = from item in layers 
       orderby item.Key descending 
       select item; 

    using (Graphics g = Graphics.FromImage(worldBox.Image)) 
    { 
     foreach (KeyValuePair<float, ICollection<IGameObjectController>> 
     kv in sortedLayers) 
     { 
      foreach (IGameObjectController controller in kv.Value) 
      { 
       // ... 

       float scale = controller.View.Scale; 
       float width = controller.View.Width; 
       float height = controller.View.Height; 
       Rectangle controllerRect = new 
        Rectangle((int)controller.Model.Position.X, 
        (int)controller.Model.Position.Y, 
        (int)(width * scale), 
        (int)(height * scale)); 

       // cull objects that aren't intersecting with the canvas 
       if (controllerRect.IntersectsWith(screenRect)) 
       { 
        Image img = previews[controller.Model.HumanReadableName]; 
        g.DrawImage(img, controllerRect); 
       } 

       if (selectedGameObjects.Contains(controller)) 
       { 
        selectionRectangles.Add(controllerRect); 
       } 
      } 
     } 
     foreach (Rectangle rect in selectionRectangles) 
     { 
      g.DrawRectangle(drawingPen, rect); 
     } 
     selectionRectangles.Clear(); 
    } 
    worldBox.Invalidate(); 
} 

Qué podría estar haciendo mal aquí?

+0

¿Tienes alguna excepción? – SLaks

+0

No, no hay excepciones. –

Respuesta

16

Para entender esto, debe tener cierta comprensión de la forma en que esto funciona en el nivel del sistema operativo.

Los controles de Windows se dibujan en respuesta a un mensaje WM_PAINT. Cuando reciben este mensaje, dibujan cualquier parte de ellos que haya sido invalidada. Los controles específicos se pueden invalidar, y las regiones específicas de los controles se pueden invalidar, esto se hace para minimizar la cantidad de repintado que se realiza.

Eventualmente, Windows verá que algunos controles necesitan volver a pintar y emitirá mensajes WM_PAINT. Pero solo lo hace después de que se hayan procesado todos los demás mensajes, lo que significa que Invalidate no fuerza un redibujado inmediato. Refresh técnicamente debería, pero no siempre es confiable. (ACTUALIZACIÓN: Esto se debe a Refresh es virtual y hay ciertos controles en la naturaleza que reemplazar este método con una aplicación incorrecta.)

Hay un método que hace fuerza de una pintura inmediata mediante la emisión de un mensaje WM_PAINT , y eso es Control.Update. Así que si desea forzar un redibujo inmediata, que utilice:

control.Invalidate(); 
control.Update(); 

Esto siempre será volver a dibujar el control, no importa lo que está sucediendo, incluso si la interfaz de usuario todavía está procesando los mensajes. Literalmente, creo que usa la API SendMessage en lugar de PostMessage, lo que obliga a que la pintura se realice de forma síncrona en lugar de lanzarla al final de una larga cola de mensajes.

+0

¿Ha observado una diferencia entre Actualizar e Invalidar/Actualizar? Nunca he visto mucha diferencia entre Refresh y plain ol 'Invalidate. Según MSDN, Update envía WM_PAINT directamente a la ventana, mientras que Invalidate coloca el mensaje en la cola de la aplicación. En la práctica, nunca he visto esto hacer una diferencia observable, incluso en la animación de alta velocidad. – MusiGenesis

+4

@MusiGenesis: ejecuté una prueba rápida y vi el mismo comportamiento con 'Refresh' vs.' Invalidate/Update'. Esto me molestó porque estaba * positivo * de que había observado una diferencia antes. Así que lo abrí en Reflector y vi que 'Control.Refresh' es, literalmente, un' Invalidate' seguido de 'Update'. Entonces me di cuenta: el método 'Refresh' es' virtual'. Por lo tanto, a veces no funciona porque los autores del control lo anulan y lo arruinan. 'Refresh' debería funcionar * la mayoría * del tiempo, pero si no lo hace, generalmente puede solucionarlo con un' Invalidate/Update' explícito. – Aaronaught

+0

@MusiGenesis: por cierto, si quiere observar la diferencia entre 'Invalidar' y 'Actualizar' (o 'Invalidar/Actualizar'), simplemente coloque un cuadro de texto en el formulario, y en un controlador de eventos, escriba 'cuadro de texto1 .BackColor = Color.Red' seguido de 'Invalidate' y luego' Thread.Sleep (1000) '- verá que la actualización no ocurre hasta que el controlador de eventos haya terminado de ejecutarse. Si está en medio de una operación de larga ejecución y no ha utilizado un 'BackgroundWorker' o un subproceso de trabajo, debe" forzar "las actualizaciones de esta manera. – Aaronaught

1

Invalidate() solo "invalida" el control o formulario (lo marca para volver a pintar), pero no forza un redibujado. Se volverá a dibujar tan pronto como la aplicación vuelva a pintar nuevamente cuando no haya más mensajes para procesar en la cola de mensajes. Si desea forzar el repintado, puede usar Refresh().

+0

Correcto, pero no se vuelve a dibujar nunca. –

0

Invalidate o Refresh harán lo mismo en este caso, y forzarán un redibujado (eventualmente). Si no está viendo nada redibujado (nunca), significa que no se ha dibujado nada en DrawWorldBox o que lo que se ha dibujado se ha extraído de la parte visible de la imagen del PictureBox.

Asegúrese (usando puntos de corte o tala o recorrer el código, como se prefiere) que algo está siendo está siendo añadido a selectionRectangles, y que al menos uno de esos rectángulos cubre la parte visible del cuadro de imagen. También asegúrese de que el bolígrafo con el que está dibujando no sea del mismo color que el fondo.

Cuestiones relacionadas