Tablica indeksów - zamiana pozycji

0

Witam wszystkich, piszę, ponieważ mam ogrmny problem z bardzo prostą rzeczą, jaką jest dynamiczna tablica.

Stworzyłem tą tablicę, by przechowywać indeksy pozycji komponentu TListBox. Gdy przesuwam jakąś pozycję w TListBox, równolegle przesywać należy w tej tablicy. Jednak steasznie dziwne rzeczy pojawiają się w talicy, gdy wykonuję tą czynność.

Tablica jest zawarta w obiekcie, który jest tworzony w konstruktorze formy głównej, oraz usuwana w destruktorze tej formy.

W ww klasie są dwie metody do przesuwania o jedną pozycję w górę lub w dół. Oto kod klasy:

unit IndexTabClass;

interface

type
  TIndexTab = class(TObject)
  private
    FIndex: array of Integer;
  public
    constructor Create;
    Destructor Destroy; override;

    procedure Add(Value: Integer);
    procedure Delete(Index: Integer);

    procedure MoveUp(Index: Integer);
    procedure MoveDown(Index: Integer);
  end;

implementation

uses
  MainFrm;

constructor TIndexTab.Create;
begin
  inherited Create;
  SetLength(FIndex, 0);
end;

destructor TIndexTab.Destroy;
begin
  SetLength(FIndex, 0);
  inherited Destroy;
end;

procedure TIndexTab.Add(Value: Integer);
begin
  SetLength(FIndex, Length(FIndex) + 1);
  FIndex[High(FIndex)] := Value;
end;

procedure TIndexTab.Delete(Index: Integer);
var
  I: Integer;
begin
  if Length(FIndex) > 0 then
    if Length(FIndex) = 1 then
      SetLength(FIndex, 0)
    else
      if (Index > -1) and (Index < Length(FIndex)) then
        if Index = Low(FIndex) then
          begin
            for I := 1 to High(FIndex) do
              FIndex[I - 1] := FIndex[I];

            SetLength(FIndex, High(FIndex));
          end
        else
          if Index = High(FIndex) then
            SetLength(FIndex, High(FIndex))
          else
            begin
              for I := Index + 1 to High(FIndex) do
                FIndex[I - 1] := FIndex[I];

              SetLength(FIndex, High(FIndex));
            end;
end;

procedure TIndexTab.MoveUp(Index: Integer);
var
  iTemp: Integer;
begin
  iTemp := Index - 1;
  FIndex[Index - 1] := FIndex[Index];
  FIndex[Index] := iTemp;
end;

procedure TIndexTab.MoveDown(Index: Integer);
var
  iTemp: Integer;
begin
  iTemp := Index;
  FIndex[Index] := FIndex[Index + 1];
  FIndex[Index + 1] := iTemp;
end;

end.

Zwróćcie uwagę na metody MoveUp i MoveDown, bo tylko z nimi mam kłopot:

procedure TIndexTab.MoveUp(Index: Integer);
var
  iTemp: Integer;
begin
  iTemp := Index - 1;
  FIndex[Index - 1] := FIndex[Index];
  FIndex[Index] := iTemp;
end;

procedure TIndexTab.MoveDown(Index: Integer);
var
  iTemp: Integer;
begin
  iTemp := Index;
  FIndex[Index] := FIndex[Index + 1];
  FIndex[Index + 1] := iTemp;
end;

Dodawanie i usuwanie poszczególnych pozycji działa bez zarzutu.

Przypuśćmy, że wypełniam tablicę elementami od 0 do 9. Jeżeli przesuwam w dół lub w górę kilka razy, to gdy wyświetlam zawartość tablicy istnieje kilka pozycji w tablicy o takich samych wartościach, np. kilka razy występuje 1.

Wie ktoś co jest w tym kodzie źle? A może ma ktoś pomysł, jak zrobić to na swój sposób?

Zależy mi na pomocy przy oprogramowaniu tylko tych dwóch metod, resztę wiem jak mam zrobić. Testowałem ten kod wielokrotnie i naprawdę nie wiem, w jaki sposób napisać te dwie metody tak, by wszystko bezbłędnie działało.

Tablica ma być dynamiczna, posiadać indeksy typu Integer, zawierać dwie metody do przesuwania pozycji o jeden w górę lub w dół. Nie musi być zabezpieczeń takich jak np. czy można przesunąć czy nie, bo metody te będą podpięte do PopupMenu i jeżeli nie będzie można przesunąć, to odpowiednia pozycja będzie zablokowana. Chodzi mi tylko o kod zamieniający dwie pozycje. Jeżeli ktoś nie zrozumiał tematu problemu, prościej mówiąc potrzebuję odpowiednika metody Exchange z TListBox.

Dziękuję serdecznie za zainteresowanie.
Pozdrawiam.

0

nie chcę Cię martwić ale już ktoś coś takiego napisał. I tak byli to programiści borlanda. Zwie się to TList, TObjectList, TComponentList.
BTW lista w listboxie jest typu TStrings i ma ona coś takiego jak Object - możesz tam wcisnąć cokolwiek zechcesz. Napisz może do czego Ci ta dodatkowa tablica

0

Misiekd mnie wyprzedzil, bo klepałem przykład. Otóż poza skorzystaniem z TList, mozna jeszcze inaczej, ale na pewno nie trzeba się męczyć z tablicą dynamiczną. Bo trzeba wiedzieć, że obiekty takie jak: TStringList, TListBox (i jego odmiany), TComboBox lub TListView może przechowywać poza tekstem również obiekty. Dla przechowywania dodatkowych informacji na przykład stringów trzeba tworzyć obiekt klasy TObject, ale dla Integerów wystarczy rzutowac jak poniżej i wtedy nie musimy się o nic martwić jeżeli chodzi o usuwanie dodawanie czy elementów albo zmianę ich kolejności. Zresztą sprawdź sam, a więcej przykładów jest w google.

//...
procedure TForm1.FormCreate(Sender: TObject);
var
  I : integer;
begin
  for I := 1 to 10 do
  begin
    ListBox1.Items.AddObject(IntToStr(I), TObject(Integer(I)));
  end;
end;

procedure TForm1.ListBox1Click(Sender: TObject);
var
  Idx : integer;
begin
  Idx := ListBox1.ItemIndex;
  if Idx > -1 then
  begin
    Caption := IntToStr(Integer(TObject(ListBox1.Items.Objects[Idx])));
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Idx : integer;
begin
  Idx := ListBox1.ItemIndex;
  if Idx > -1 then
  begin
    ListBox1.Items.Delete(Idx);
  end;
end;
0

W tych problematycznych funkcjach masz brak sprawdzenia czy nie wyszedłeś poza zakres tablicy.
Delete można znacznie uprościć:

L:=Length(FIndex);
if (0<=Index)and(Index<L) then
begin
  for I:=Index+1 to L-1 do FIndex[I-1]:=FIndex[I]; // to można właściwie zastąpić na Move();
  SetLength(FIndex,L-1);
end;

i to wszystko.

0

Wymyśliłem sposób porównywania jednego ListBox'a z drugim. Zamiast porównywać łańcuchy, przechowuję w dodatkowej tablicy indeksy pozycji pierwszego w drugim. Zawartość pierwszego jest stała, zawartość drugiego można uzupełniać kopiując pozycje z pierwszego. Ale sprawdzanie, czy w drugim istnieje już wybrana pozycja jako porównywanie łańcuchów jest bardzo czasochłonna - u mnie 2000 plików sprawdzało kilkanaście sekund. Jeżeli przechowuję w dynamicznej tablicy tylko indeksy, porównywanie ich jest o niebo szybsze - sprawdza 2000 plików w mniej niż pół sekundy.

Dlatego tak bardzo zależy mi na tym, by napisać własną klasę i ją obsługiwać. Kiedyś już pisałem taką klasę, która jest swoistą tablicą tablic typu string, inaczej mówiąc dynamiczna tablica dynamicznych tablic. Działała super, były w niej metody pozwalające m.in. na przesuwanie, dodawanie, wstawianie i wyszukiwanie wybranych ciągów. Właśnie tam przesuwanie działało idealnie, nie było najmniejszych kłopotów. Potrzebuję napisać taką klasę dlatego, że przyda mi się jeszcze w następnych projektach. Chcę napisać jedną uniwersalną klasę, tak, żebym mógł korzystać z niej bez przymusu modyfikacji ani pisania kodu od nowa.

Z tego co porysowałem na tablicy jest tylko jedna możliwość zamiany dwóch sąsiednich pozycji (przykładowo):

iTemp := FIndex[I + 1];
FIndex[I + 1] := FIndex[I];
FIndex[I] := iTemp;

Można jeszcze zmienić kolejność:

iTemp := FIndex[I];
FIndex[I] := FIndex[I + 1];
FIndex[I + 1] := iTemp;

ale to i tak na to samo wychodzi. W każdym razie tak czy tak jedno trzeba przechować, do pierwszego wpisz drugie, do drugiego wpisz z tempa. Niby nic trudnego, ale za cholere nie chce działać.

Sprawdzałem to, w jaki sposób robiłem to we wcześniej wspomnianej klasie z łańcuchami. Tu procedura przesuwająca pozycję o jeden w górę:

procedure TStringTable.MoveUpRow(const RowIndex: TRCount);
var
  aBuffer: array of String;
  I: TCCount;
begin
  if (RowIndex in [FTableIndex.Row.Low .. FTableIndex.Row.High]) and
     (RowIndex > FTableIndex.Row.Low) then
    begin
      SetLength(aBuffer, FTableCount.Columns);

      for I := FTableIndex.Column.Low to FTableIndex.Column.High do
        begin
          aBuffer[I] := FColumn[I].Row[RowIndex - 1];
          FColumn[I].Row[RowIndex - 1] := FColumn[I].Row[RowIndex];
          FColumn[I].Row[RowIndex]     := aBuffer[I];
        end;
    end;
end;

Metoda ta sama. Ta działa, na liczbach już nie...

Jeżeli miałby ktoś pomysł, jak napisać swoją klasę z metodą MoveUp i MoveDown, bardzo by mnie to uszczęśliwiło. Jeśli nie ma już siły na to, niestety będzie trzeba kombinować z objektami. Ale najpierw chcę spróbować stworzyć swoją klasę, ponieważ będzie wtedy tak jak wspomniałem wcześniej uniwersalna.

Chyba, że zmodyfikuję komponent TListBox tak, żeby przechowywał jeszcze liczby. Też dobra sprawa, ale jeżeli w programie nie będę korzystał z tego komponentu - nie przyda się (np. w konsoli).

Tak że na razie zastanówmy się nad swoją klasą, jak nikt nic nie wymyśli, trzeba będzie korzystać z objektów ListBox.

0

ale na pewno nie trzeba się męczyć z tablicą dynamiczną

Nie przejmuj się olesio, bardzo lubię tablice dynamiczne. Lubię je tak bardzo, jak bardzo czasami mnie denerwują :)

0

Przecież ci napisałem z czym masz problem jasno i wyraźnie kilka postów wyżej.

W tych problematycznych funkcjach masz brak sprawdzenia czy nie wyszedłeś poza zakres tablicy.

0

Nie musi być zabezpieczeń takich jak np. czy można przesunąć czy nie, bo metody te będą podpięte do PopupMenu i jeżeli nie będzie można przesunąć, to odpowiednia pozycja będzie zablokowana.

_13th_Dragon, gdybyś czytał uważnie to byś wiedział, że zabezpieczenia te są podłączone do zdarzenia OnPopup i tam są sprawdzane takie rzeczy, jak np. czy można przesunąć czy nie, co znaczy, czy wyjdzie się poza zakres czy nie.

Dlaczego? Bo po co mam dwa razy sprawdzać tą samą rzecz, skoro mogę zablokować możliwość przesunięcia w komponencie TPopupMenu.

Problem dalej nie jest rozwiązany. Proszę o kolejne posty. Dziękuję.

0

A poza tym, gdyby błąd leżał w wyjściu poza zakres tablicy to dostałbym Access Violation a nie pomieszane wartości w tablicy.

0
procedure TIndexTab.MoveDown(Index: Integer);
var
  iTemp: Integer;
begin
  iTemp := FIndex[Index]; // ty masz to: // iTemp := Index; // to samo w tej drugiej.
  FIndex[Index] := FIndex[Index + 1];
  FIndex[Index + 1] := iTemp;
end;
0

O kurcze, szukałem błędu zupełnie gdzie indziej a był tak blisko... :)

No w sumie racja, terazto nawet nie jest dziwne, że takie rzeczy wychodziły w tablicy.

Dziękuję wszystkim za pomoc _13th_Dragon, olesio i misiekd. Teraz już wszystko działa w jak najlepszym porządku.

Pozdrawiam i jeszcze raz dziękuję.

1 użytkowników online, w tym zalogowanych: 0, gości: 1