2012-10-29 4 views
7

Sto creando un modulo dove ci sono icone sul desktop e possono essere spostati liberamente. Voglio mostrare a volte anche 500 o più icone in modo che abbiano bisogno di lavorare velocemente. La mia icona è:Perché un modulo con 500 componenti è lento?

TMyIcon = class (TGraphicControl)

in modo da non avere una maniglia di Windows. Il disegno è:

  • 1 x Canvas.Rectangle (che è di circa 64x32)
  • 1 x Canvas.TextOut (un po 'più piccolo del rettangolo)
  • 1 x Canvas.Draw (immagine è 32x32)

Il codice per spostare roba è come questo: MyIconMouseMove:

Ico.Left := Ico.Left + X-ClickedPos.X; 
Ico.Top := Ico.Top + Y-ClickedPos.Y; 

Sul modulo ci sono solitamente circa 50 icone, il resto è al di fuori dell'area visibile. Quando ho 100 icone, posso spostarle liberamente e funziona velocemente. Ma quando creo 500 icone, diventa laggoso, ma il numero di icone visibili è sempre lo stesso. Come posso dire a Windows di ignorare completamente le icone invisibili in modo che tutto funzioni senza intoppi?

O forse c'è un componente che può mostrare icone simili a desktop con la possibilità di spostarle? Qualcosa come TShellListView con AutoArrange = False?

+7

Perché questo ottenendo downvotes? –

+2

Piuttosto che descrivere il codice, potresti mostrare il codice. –

+4

@Mason Wheeler: stavo pensando lo stesso. Il downvoting va bene se una domanda fa davvero schifo (non è il caso qui), ma i downvoters dovrebbero almeno lasciare un commento costruttivo per descrivere come la domanda potrebbe essere migliorata. –

risposta

6

TGraphicControl è un controllo che non ha una maniglia propria. Usa i suoi genitori per mostrare il suo contenuto. Ciò significa che cambiare l'aspetto del tuo controllo costringerà anche il padre a essere ridisegnato. Ciò potrebbe anche innescare la riverniciatura di tutti gli altri controlli.

In teoria, solo la parte del genitore su cui è posizionato il controllo X deve essere invalidata, quindi è necessario ridipingere solo i controlli che si sovrappongono a tale parte. Tuttavia, ciò potrebbe causare una reazione a catena, provocando la chiamata di molti metodi di pittura ogni volta che si modifica un singolo pixel in uno di questi controlli.

Apparentemente, anche le icone all'esterno dell'area visibile vengono ridipinte. Penso che tu possa ottimizzare questo impostando la proprietà Visible delle icone su False se sono al di fuori dell'area visibile.

Se questo non funziona, potrebbe essere necessario un approccio completamente diverso: c'è la possibilità di dipingere tutte le icone su un singolo controllo, consentendo di bufferizzare le immagini. Se stai trascinando un'icona, puoi dipingere tutte le altre icone su una bitmap una volta. In ogni mossa del mouse, devi solo dipingere quella bitmap con buffer e l'icona singola che viene trascinata, invece di 100 (o 500) icone separate. Ciò dovrebbe accelerare un po 'le cose, anche se ci vorrà un po' più di sforzo per svilupparsi.

si potrebbe implementare in questo modo:

type 
    // A class to hold icon information. That is: Position and picture 
    TMyIcon = class 
    Pos: TPoint; 
    Picture: TPicture; 
    constructor Create(Src: TBitmap); 
    destructor Destroy; override; 
    end; 

    // A list of such icons 
    //TIconList = TList<TMyIcon>; 
    TIconList = TList; 

    // A single graphic controls that can display many icons and 
    // allows dragging them 
    TIconControl = class(TGraphicControl) 
    Icons: TIconList; 
    Buffer: TBitmap; 
    DragIcon: TMyIcon; 

    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; 

    procedure Initialize; 
    // Painting 
    procedure ValidateBuffer; 
    procedure Paint; override; 
    // Dragging 
    function IconAtPos(X, Y: Integer): TMyIcon; 
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; 
     X, Y: Integer); override; 
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; 
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; 
     X, Y: Integer); override; 
    end; 


{ TMyIcon } 

// Some random initialization 
constructor TMyIcon.Create(Src: TBitmap); 
begin 
    Picture := TPicture.Create; 
    Picture.Assign(Src); 
    Pos := Point(Random(500), Random(400)); 
end; 

destructor TMyIcon.Destroy; 
begin 
    Picture.Free; 
    inherited; 
end; 

Poi, il graphiccontrol stesso:

{ TIconControl } 

constructor TIconControl.Create(AOwner: TComponent); 
begin 
    inherited; 
    Icons := TIconList.Create; 
end; 

destructor TIconControl.Destroy; 
begin 
    // Todo: Free the individual icons in the list. 
    Icons.Free; 
    inherited; 
end; 

function TIconControl.IconAtPos(X, Y: Integer): TMyIcon; 
var 
    r: TRect; 
    i: Integer; 
begin 
    // Just return the first icon that contains the clicked pixel. 
    for i := 0 to Icons.Count - 1 do 
    begin 
    Result := TMyIcon(Icons[i]); 
    r := Rect(0, 0, Result.Picture.Graphic.Width, Result.Picture.Graphic.Height); 
    OffsetRect(r, Result.Pos.X, Result.Pos.Y); 
    if PtInRect(r, Point(X, Y)) then 
     Exit; 
    end; 
    Result := nil; 
end; 


procedure TIconControl.Initialize; 
var 
    Src: TBitmap; 
    i: Integer; 
begin 
    Src := TBitmap.Create; 
    try 
    // Load a random file. 
    Src.LoadFromFile('C:\ff\ff.bmp'); 

    // Test it with 10000 icons. 
    for i := 1 to 10000 do 
     Icons.Add(TMyIcon.Create(Src)); 

    finally 
    Src.Free; 
    end; 
end; 

procedure TIconControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X, 
    Y: Integer); 
begin 
    if Button = mbLeft then 
    begin 
    // Left button is clicked. Try to find the icon at the clicked position 
    DragIcon := IconAtPos(X, Y); 
    if Assigned(DragIcon) then 
    begin 
     // An icon is found. Clear the buffer (which contains all icons) so it 
     // will be regenerated with the 9999 not-dragged icons on next repaint. 
     FreeAndNil(Buffer); 

     Invalidate; 
    end; 
    end; 
end; 

procedure TIconControl.MouseMove(Shift: TShiftState; X, Y: Integer); 
begin 
    if Assigned(DragIcon) then 
    begin 
    // An icon is being dragged. Update its position and redraw the control. 
    DragIcon.Pos := Point(X, Y); 

    Invalidate; 
    end; 
end; 

procedure TIconControl.MouseUp(Button: TMouseButton; Shift: TShiftState; X, 
    Y: Integer); 
begin 
    if (Button = mbLeft) and Assigned(DragIcon) then 
    begin 
    // The button is released. Free the buffer, which contains the 9999 
    // other icons, so it will be regenerated with all 10000 icons on 
    // next repaint. 
    FreeAndNil(Buffer); 
    // Set DragIcon to nil. No icon is dragged at the moment. 
    DragIcon := nil; 

    Invalidate; 
    end; 
end; 

procedure TIconControl.Paint; 
begin 
    // Check if the buffer is up to date. 
    ValidateBuffer; 

    // Draw the buffer (either 9999 or 10000 icons in one go) 
    Canvas.Draw(0, 0, Buffer); 

    // If one ican was dragged, draw it separately. 
    if Assigned(DragIcon) then 
    Canvas.Draw(DragIcon.Pos.X, DragIcon.Pos.Y, DragIcon.Picture.Graphic); 
end; 

procedure TIconControl.ValidateBuffer; 
var 
    i: Integer; 
    Icon: TMyIcon; 
begin 
    // If the buffer is assigned, there's nothing to do. It is nilled if 
    // it needs to be regenerated. 
    if not Assigned(Buffer) then 
    begin 
    Buffer := TBitmap.Create; 
    Buffer.Width := Width; 
    Buffer.Height := Height; 
    for i := 0 to Icons.Count - 1 do 
    begin 
     Icon := TMyIcon(Icons[i]); 
     if Icon <> DragIcon then 
     Buffer.Canvas.Draw(Icon.Pos.X, Icon.Pos.Y, Icon.Picture.Graphic); 
    end; 
    end; 
end; 

Creare uno di quei controlli, rendono compilare il modulo e inizializzare con 10000 icone.

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    DoubleBuffered := True; 

    with TIconControl.Create(Self) do 
    begin 
    Parent := Self; 
    Align := alClient; 
    Initialize; 
    end; 
end; 

E 'un po' rapida & sporca, ma dimostra questa soluzione può funzionare molto bene. Se inizi a trascinare (il mouse verso il basso), noterai un piccolo ritardo mentre le icone 10000 vengono disegnate sulla bitmap che passa per un buffer. Dopo di ciò, non vi è alcun ritardo notevole durante il trascinamento, poiché su ogni ridisegno vengono disegnate solo due immagini (anziché 500 nel tuo caso).

+1

Questo pomeriggio non ho avuto tempo, ma ora ho aggiunto un piccolo esempio che mostra come gestire 10000 icone in un unico controllo, con un discreto trascinamento e rilascio rapidi. – GolezTrol

+0

Grazie per quello, sembra promettente. Non posso compilarlo in Lazarus né in Delphi 2005 a causa di cose come: TList ma forse posso renderlo utile dopo alcune modifiche. – Tom

+0

Ah, stavo cercando di utilizzare le funzionalità più recenti, non conoscendo la tua versione Delphi. 'TList ' significa 'una lista di TMyIcons'. In Delphi 2005, puoi usare solo TList, solo dovrai modificare un po 'il ciclo 'for' e digitare gli elementi su un TMyIcon al momento del recupero. – GolezTrol

1

Si potrebbe voler controllare questo controllo che è esattamente quello che hai chiesto.

rkView from RMKlever

Si tratta essenzialmente di un visualizzatore di miniature icona o la foto con lo scorrimento ecc

+1

Sembra promettente ma non c'è demo, documentazione e non è solo una sostituzione di ListView, quindi non so ancora come usarlo. – Tom

+1

Ha davvero bisogno di una demo SEMPLICE. Forse ne farò uno, dal momento che il signor Klever è stato così gentile da prendere il controllo. Ha una demo, ma è una demo terribilmente dura per essere costruita e funzionante: http://rmklever.com/?p=318 –

+0

Ho trovato la sua altra demo: http://rmklever.com/?p=266 ma è anche abbastanza complicato. Per essere onesto, ho visitato questo sito alcune volte in passato, ho scaricato i componenti ma a causa di nessun pacchetto, demo, molte dipendenze non ho mai trovato il tempo di compilare una cosa. – Tom