Reordenação de Items em ListBox
Eu tenho uma tabela que contém itens ordenados. A coluna “posicao” indica a posição relativa de cada item (de 1 a n) e seus valores não podem ser repetidos. Eu precisava de uma forma prática de redefinir a ordenação dos itens. A dificuldade é que a cada vez que um item muda de posição todos os itens posteriores também devem mudar.
A solução foi levar todos os nomes de itens para um TListBox respeitando a ordenação atual e deixar o usuário livremente posicionar os nomes sem se preocupar com o dataset. Quando o usuário estiver satisfeito com a ordem dos itens, clica em OK e o programa atribui de uma só vez todos os valores de “posicao” ao dataset conforme os índices dos nomes no ListBox.
A idéia foi rapidamente implementada pelo Fábio Costa, que trabalha comigo. A solução inicial usava apenas teclas de atalho e botões para mover o item selecionado para cima e para baixo, para a primeira ou última posição. O problema é que ele começou a tomar gosto pela coisa e danou a ter idéias. Por que não arrastar e soltar um item com o mouse para mudar sua posição? Por que não fazer o mouse dar scroll sozinho no ListBox quando o usuário estiver arrastando para além da primeira ou da última posição do controle?
Assim, a coisa foi evoluindo para uma solução mais e mais sofisticada. Não é que ficou bom?
Várias técnicas foram utilizadas para chegar ao resultado final: TActionList com teclas de atalhos (Ctrl+Home, Ctrl+Up, Ctrl+Down, Ctrl+End) e habilitação condicional, manipulação dos eventos OnStartDrag, OnDragOver, OnDragDrop e OnDragEnd, execução de mensagens do Windows pelo método Perform, scroll de TListBox por código, identificação do item do ListBox dada uma posição (X, Y) de tela, timers com intervalos dinâmicos (1s para começar, 0.2s para continuar a rolagem).
Veja o código de exemplo comentado abaixo, sem a parte de banco de dados. Você também pode fazer o download do código fonte do projeto de demonstração.
unit ListBoxReorder_Form; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ActnList, StdCtrls, ExtCtrls; type TScrollZone = (szNone, szTop, szBottom); TMainForm = class(TForm) ActionList: TActionList; ListBox: TListBox; ScrollTimer: TTimer; DownAction: TAction; DownButton: TButton; FirstAction: TAction; FirstButton: TButton; LastAction: TAction; LastButton: TButton; UpAction: TAction; UpButton: TButton; procedure FormCreate(Sender: TObject); procedure FirstActionExecute(Sender: TObject); procedure FirstActionUpdate(Sender: TObject); procedure UpActionExecute(Sender: TObject); procedure UpActionUpdate(Sender: TObject); procedure DownActionExecute(Sender: TObject); procedure DownActionUpdate(Sender: TObject); procedure LastActionExecute(Sender: TObject); procedure LastActionUpdate(Sender: TObject); procedure ListBoxStartDrag(Sender: TObject; var DragObject: TDragObject); procedure ListBoxDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure ListBoxDragDrop(Sender, Source: TObject; X, Y: Integer); procedure ListBoxEndDrag(Sender, Target: TObject; X, Y: Integer); procedure ScrollTimerExecute(Sender: TObject); private FScrollZone: TScrollZone; procedure InitListBox; function IsSelected: Boolean; procedure MoveItem(NewIndex: Integer); function GetScrollZone(X, Y: Integer): TScrollZone; end; var MainForm: TMainForm; implementation {$R *.dfm} { Inicializa ListBox.Items para demonstração } procedure TMainForm.FormCreate(Sender: TObject); begin InitListBox; ListBox.DragMode := dmAutomatic; end; { Inicializa ListBox.Items com nomes de arquivos da pasta System32 } procedure TMainForm.InitListBox; var F: TSearchRec; begin ListBox.Items.BeginUpdate; if FindFirst('C:\Windows\System32\*.*', faArchive, F) = 0 then repeat ListBox.Items.Add(F.Name); until FindNext(F) <> 0; FindClose(F); ListBox.Items.EndUpdate; ListBox.ItemIndex := 0; end; { Indica se algum item do ListBox está selecionado } function TMainForm.IsSelected: Boolean; begin Result := ListBox.ItemIndex <> -1; end; { Move um item do ListBox para outra posição, seleciona a nova posição no ListBox e atribui o foco ao ListBox. } procedure TMainForm.MoveItem(NewIndex: Integer); begin ListBox.Items.Move(ListBox.ItemIndex, NewIndex); ListBox.ItemIndex := NewIndex; ListBox.SetFocus; end; procedure TMainForm.DownActionExecute(Sender: TObject); begin MoveItem(ListBox.ItemIndex + 1); end; procedure TMainForm.DownActionUpdate(Sender: TObject); begin DownAction.Enabled := IsSelected and (ListBox.ItemIndex < ListBox.Items.Count - 1); end; procedure TMainForm.FirstActionExecute(Sender: TObject); begin MoveItem(0); end; procedure TMainForm.FirstActionUpdate(Sender: TObject); begin FirstAction.Enabled := IsSelected and (ListBox.ItemIndex > 0); end; procedure TMainForm.LastActionExecute(Sender: TObject); begin MoveItem(ListBox.Items.Count - 1); end; procedure TMainForm.LastActionUpdate(Sender: TObject); begin LastAction.Enabled := IsSelected and (ListBox.ItemIndex < ListBox.Items.Count - 1); end; procedure TMainForm.UpActionExecute(Sender: TObject); begin MoveItem(ListBox.ItemIndex - 1); end; procedure TMainForm.UpActionUpdate(Sender: TObject); begin UpAction.Enabled := IsSelected and (ListBox.ItemIndex > 0); end; { Inicializa FScrollZone com szNone } procedure TMainForm.ListBoxStartDrag(Sender: TObject; var DragObject: TDragObject); begin FScrollZone := szNone; end; { Define se a posição do mouse aponta para um item válido do ListBox que permita soltar o item arrastado (Accept). Inicia ou cancela o timer de scroll automático. } procedure TMainForm.ListBoxDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var NewZone: TScrollZone; begin Accept := (Source = ListBox) and (ListBox.ItemAtPos(Point(X, Y), True) <> -1); NewZone := GetScrollZone(X, Y); if NewZone <> FScrollZone then begin FScrollZone := NewZone; if (FScrollZone <> szNone) then begin ScrollTimer.Interval := 1000; ScrollTimer.Enabled := True; end else ScrollTimer.Enabled := False; end; end; { Move item selecionado para nova posição } procedure TMainForm.ListBoxDragDrop(Sender, Source: TObject; X, Y: Integer); var NewIndex: Integer; begin NewIndex := ListBox.ItemAtPos(Point(X, Y), True); if NewIndex <> -1 then MoveItem(NewIndex); end; { Desliga timer de scroll no fim do drag and drop } procedure TMainForm.ListBoxEndDrag(Sender, Target: TObject; X, Y: Integer); begin ScrollTimer.Enabled := False; end; { Retorna a szTop se mouse está na área da primeira linha visível do ListBox, szBottom se mouse está na área da última linha visível do ListBox ou szNone caso contrátrio. } function TMainForm.GetScrollZone(X, Y: Integer): TScrollZone; begin if (Y < ListBox.ItemHeight) then Result := szTop else if (Y >= ListBox.ClientHeight - ListBox.ItemHeight) then Result := szBottom else Result := szNone; end; { Faz scroll de uma linha para cima ou para baixo conforme a zona de scroll definida pela posição do mouse. Redefine intervalo do timer para 0.2s após o primeiro scroll. } procedure TMainForm.ScrollTimerExecute(Sender: TObject); begin ScrollTimer.Interval := 200; if (FScrollZone = szTop) then begin ListBox.Perform(WM_VSCROLL, SB_LINEUP, 0); ListBox.Perform(WM_VSCROLL, SB_ENDSCROLL, 0); end else if (FScrollZone = szBottom) then begin ListBox.Perform(WM_VSCROLL, SB_LINEDOWN, 0); ListBox.Perform(WM_VSCROLL, SB_ENDSCROLL, 0); end; end; end.
Comments
One Response to “Reordenação de Items em ListBox”
Deixe uma Resposta

Maravilha de artigo, sensacional. Gostaria de aproveitar este espaço e pedir sua ajuda mais uma vez. Estou migrando meu projeto do Delphi 7 para o Delphi 2009, tenho uma rotina no delphi 7 que lista o conteudo de um Memo usando o “assignPrn” no delphi 7 funciona perfeitamente porem quando implemento do Delphi 2009 no relatório é imprimido sugeira “(@##– ªªº]…)” será q/ tem algum detalhe q/ eu deva mudar no delphi2009. Se Vc puder me ajudar. Grande abraço - Mauro