2010-04-23 18 views
10

Sto usando Windows Forms. Per molto tempo, pictureBox.Invalidate(); ha funzionato per ridisegnare lo schermo. Tuttavia, ora non funziona e non sono sicuro del perché.C#: Windows Form: Che cosa potrebbe causare che Invalidate() non si ridisegna?

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); 

chiamato nel mio codice per disegnare il mondo in modo appropriato:

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(); 
} 

cosa potevo fare male qui?

+0

Hai qualche eccezione? – SLaks

+0

No, non ci sono eccezioni. –

risposta

16

Per capire questo bisogna avere una certa comprensione del modo in cui funziona a livello di sistema operativo.

controlli di Windows sono disegnati in risposta ad un messaggio WM_PAINT. Quando ricevono questo messaggio, disegnano qualunque parte di loro sia stata invalidata. I controlli specifici possono essere invalidati e specifiche regioni di controlli possono essere invalidate, questo è fatto per minimizzare la quantità di ridipinture che è stata fatta.

Alla fine, Windows vedrà che alcuni controlli devono riverniciatura ed emettere WM_PAINT messaggi a loro. Ma lo fa solo dopo che tutti gli altri messaggi sono stati elaborati, il che significa che Invalidate non impone un ridisegno immediato. Refresh tecnicamente dovrebbe, ma non è sempre affidabile. (AGGIORNAMENTO: Questo perché Refresh è virtual e ci sono alcuni controlli in natura che sovrascrivere questo metodo con un'implementazione corretta.)

C'è un metodo che fa forza una pittura immediata mediante l'emissione di un messaggio WM_PAINT e questo è Control.Update. Quindi, se si vuole forzare un ridisegno immediato, si utilizza:

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

Questo sarà sempre ridisegnare il controllo, non importa che cosa sta accadendo, anche se l'interfaccia utente sta ancora elaborando i messaggi. Letteralmente, credo che utilizza l'API SendMessage invece di PostMessage che costringe la pittura ad essere fatto in modo sincrono, invece di lanciare alla fine di una coda di messaggi a lungo.

+0

Hai notato una differenza tra Aggiorna e Invalidare/Aggiorna? Non ho mai visto molta differenza tra Refresh e plain ol 'Invalidate. Secondo MSDN, l'aggiornamento invia WM_PAINT direttamente alla finestra, mentre Invalidate inserisce il messaggio nella coda dell'applicazione. In pratica non l'ho mai visto fare alcuna differenza osservabile, nemmeno nell'animazione ad alta velocità. – MusiGenesis

+4

@MusiGenesis: ho eseguito un test rapido e ho visto lo stesso comportamento con 'Refresh' vs.' Invalidate/Update'. Questo mi ha infastidito perché ero * positivo * che avevo notato una differenza prima. Così l'ho aperto in Reflector e ho visto che 'Control.Refresh' è, letteralmente, un' Invalidate' seguito da un 'Update'. Poi mi sono reso conto che il metodo "Refresh" è "virtuale". Quindi a volte non funziona perché gli autori del controllo lo sovrascrivono e lo rovinano. 'Refresh' dovrebbe funzionare * la maggior parte * del tempo, ma in caso contrario, di solito è possibile correggerlo con un esplicito' Invalidate/Update'. – Aaronaught

+0

@MusiGenesis: A proposito, se si desidera osservare la differenza tra 'Invalidate' e' Refresh' (o 'Invalidate/Update'), basta lanciare una casella di testo sul form e, in un gestore di eventi, scrivere' textBox1 .BackColor = Color.Red' seguito da 'Invalidate' poi' Thread.Sleep (1000) '- vedrai che l'aggiornamento non avviene finché non viene completato l'esecuzione del gestore eventi. Se sei nel mezzo di un'operazione di lunga durata e non hai usato un 'BackgroundWorker' o un thread di lavoro, devi" forzare "gli aggiornamenti in questo modo. – Aaronaught

1

Invalidate() solo "invalida" il controllo o la forma (segna per riverniciatura), ma non forza un ridisegno. Sarà ridisegnato non appena l'applicazione torna a ridisegnare di nuovo quando non ci sono più messaggi da elaborare nella coda dei messaggi. Se si desidera forzare un ridisegno, è possibile utilizzare Refresh().

+0

Giusto, ma non viene mai ridisegnato. –

0

Invalidate o Refresh farà la stessa cosa in questo caso e imporrà un ridisegno (eventualmente). Se non riesci a vedere nulla ridisegnata (mai), allora significa che sia nulla è stato disegnato a tutti in DrawWorldBox o ciò che è stato elaborato è stato redatto al largo della parte visibile dell'immagine del PictureBox.

Assicurarsi (utilizzando i punti di interruzione o la registrazione o fare un passo attraverso il codice, come preferite) che qualcosa è di essere è stato aggiunto nel selectionRectangles, e che almeno uno di questi rettangoli copre la parte visibile del controllo PictureBox. Assicurati anche che la penna con cui stai disegnando non sia dello stesso colore dello sfondo.

Problemi correlati