Lista jednokierunkowa obsługiwana z poziomu aplikacji okienkowej

0

Witam.

Mam do zrobienia na studia liste jednokierunkową w Delphi.
Jest to mój początek w tym języku programowania. Czytałem kilka poradników i analizowałem różne kody.
Dla lepszego zrozumienia tego zadania postanowiłem sobie napisać to w języku C++.
Jednak nadal nie mam pomysłu jak to przetworzyć na Delphi.

Ogólne założenia programu:

1.Lista z pliku txt pobierana do programu i wczytana
2.Sprawdzanie czy został wczytany poprawny plik
3.Przy zmianie danych pyta użytkownika czy zapisać plik i zmiany nadane przez użytkownika
4.Przyciski dodaj i usuń ostatni element listy

Co do 1 podpunktu to znalazłem coś takiego na internecie jednak chciałbym aby ta lista wyświetlała się w okienku - memo przykładowo.

Memo.Lines.LoadFromFile('C:\plik.txt');

Na podpunkt 2 i 3 nie mam ogólnie pomysłu.

Co do 4 podpunktu to znalazłem kilka procedur:

Dodawanie elementu:

procedure TSinglyLinkedList.Add(AElement : TData);
var
  NewOne : PElement;
begin
  NewOne := CreateElement(AElement);

  if FFirst = nil then
  begin
    FFirst := NewOne;
    FLast := FFirst;
  end
  else
  begin
    FLast^.Next := NewOne;
    FLast := NewOne;
  end;

  Inc(FCount);
end;

Usuwanie elementu:

procedure TSinglyLinkedList.Delete(AIndex : Word);
var
  ToDelete, Prev: PElement;
begin
  if AIndex = 0 then
  begin
    ToDelete := FFirst;
    FFirst := FFirst^.Next;

    if FFirst = nil then
      FLast := nil;
  end
  else
  begin
    GetElementByIndex(AIndex, Prev, ToDelete);
    Prev^.Next := ToDelete^.Next;

    if ToDelete^.Next = nil then
      FLast := Prev;
  end;

  Dispose(ToDelete);
  Dec(FCount);
end;

Wyszukuje element:

function TSinglyLinkedList.Find(AData : TData) : Integer;
var
  ToFind : PElement;
begin
  Result := 0;
  ToFind := FFirst;

  while ToFind <> nil do
  if ToFind^.Data = AData then
    Exit
  else
  begin
    ToFind := ToFind^.Next;
    Inc(Result);
  end;

  Result := -1;
end;

Tak jak wspominałem pierwszy raz mam do czynienia z Delphi i nie ogarniam tego środowiska, więc piszę tu do społeczności z prośbą o pomoc w realizacji zadania.
Za poświęcony czas każdego z Was z góry serdecznie dziękuje.

4

Znam ten kod — razem z @babubabu napisaliśmy artykuł, z którego on pochodzi. ;)

arokos123 napisał(a):

1.Lista z pliku txt pobierana do programu i wczytana
2.Sprawdzanie czy został wczytany poprawny plik
3.Przy zmianie danych pyta użytkownika czy zapisać plik i zmiany nadane przez użytkownika
4.Przyciski dodaj i usuń ostatni element listy

  1. Dodaj sobie metodę LoadFromFile do klasy listy i w niej zaimplementuj ładowanie listy z pliku. Jesli chcesz użyć pliku tekstowego, to zadeklaruj sobie zmienną plikową typu Text, otwórz go za pomocą AssignFile i Reset, a następnie w pęli while sprawdzaj EoF i linijka po linijce wczytuj dane (procedurką Read lub ReadLn). Każdą wczytanę linijkę dodawaj do listy za pomocą metody Add.

  2. Co to znaczy? To czy poprawny plik został wczytany, zależy od nazwy/ścieżki pliku przekazanej do metody LoadFromFile. Jeśli chodzi Ci o to, czy wczytywanie danych z pliku powiodło się (czyli czy nie wystąpił błąd), to tym zajmują się wyjątki. Możesz tę metodę przerobić na funkcyjną, która będzie zwracać wartość logiczną, która będzie określać czy odczyt się powiódł czy nie. Jeśli coś pójdzie nie tak to poleci wyjątek, który należy przechwycić w tej metodzie, ustawić rezultat na False i przerwać dalsze wczytywanie.

  3. Wyjaśnij dokładniej jak ma to działać.

  4. To akurat nie jest trudne — do usuwania węzła służy metoda Delete, której podaje się indeks węzła do usunięcia (licząc od 0). Jeśli chcesz usunąć pierwszy węzeł, to wywołaj metodę List.Delete(0), a jeśli ostatni, to List.Delete(List.Count - 1).

0

@furious programming:

  1. Z tego artykułu co przeanalizowałem znalazłem procedure która wczytuje dane z pliku. Podpiąłem sobie to pod przycisk onclick. Mam dużo błedów np. z niezdefiniowanym atrybutem Add nie mam pojecia mam sobie to zdefiniowac wyżej w type jako zmienna string czy co?
procedure TForm1.Button1Click(Sender: TObject);
begin
procedure TSinglyLinkedList.LoadFromFile(const AFileName: String);
var
  LoadFile: TFileStream;
  ToLoad: TData;
begin
  LoadFile := TFileStream.Create(AFileName, fmOpenRead);
  try
    while LoadFile.Position < LoadFile.Size do
    begin
      LoadFile.ReadBuffer(ToLoad, ELEMENT_DATA_SIZE);
      Add(ToLoad);
    end;
  finally
    FreeAndNil(LoadFile);
  end;
end;
end;

end.

2.Chyba chodzi tu o to, aby użytkownik graficznie wczytywał plik. Pewnie trzeba zrobić jakiegoś ifa który sprawdz czy podany plik jest prawidłowy. Jezeli tak to go wczytuje, jeśli nie to wyświetla komunikat o błędnym pliku.

3.Jeżeli użytkownik dodaje jakiś element do listy lub coś w niej modyfikuje i chce wyłączyć program to ma mu się wyświetlać okno czy zapisać zmiany czy nie.

  1. Tu mam taki sam problem jak w podpunkcie 1. Dodałem procedury pod przyciski które stworzyłem i mam dużo błędów dotyczących niezdefiniowanych zmiennych.
3
  1. Nie możesz wsadzić kodu z definicją metody do zdarzenia, bo to nie ma prawda działać — taki kod nie jest dozwolony i kompilator wyrzuci błąd podczas kompilacji. Definicja metody to przepis na wykonanie jakiejś operacji, jego się używa (wywołuje), a nie przekleja. To tak jakbyś chciał ugotować zupę mając przepis na kartce i zamiast wrzucać do garnka składniki opisane w przepisie, wrzucił do gara kartkę. ;)

    Jeśli chcesz się nauczyć programowania, to musisz zacząć od kursu (np. od tego kursu) — po kolei uczyć się języka od rzeczy najprostszych po programowanie obiektowe. Bez tego nie dasz rady napisać żadnego programu i ciągle będziesz się wkurzał, że coś nie działa.

  2. Nie wiem jak Tobie, ale mi nic to nie mówi. Co to znaczy „wczytać plik graficznie”? No i jak chcesz zapisać kod, który sprawdza czy podany plik jest prawidłowy? Jak określić jego prawidłowość? Brać pod uwagę nazwę pliku? Jego rozszerzenie? Żeby napisać takiego ifa, trzeba mieć konkretne wytyczne.

  3. Tutaj wystarczy mieć zmienną logiczną (np. o nazwie Modified), która trzyma informację o tym, czy zawartość listy zmodyfikowano czy nie. Po załadowaniu pliku ustawiasz wartość tej zmiennej na False. Po jakiejkolwiek modyfikacji listy, czyli dodaniu, usunięciu węzła czy edycji jego danych, ustawiasz zmienną na True. Aby wyświetlić takiego okienko, wystarczy w zdarzeniu OnCloseQuery formularza sprawdzić stan tej zmiennej i jeśli jest ustawiona na True, wyświetlić MessageBox z zapytaniem. Następnie przechwycić wartość zwracaną (typ wciśniętego w tym okienku przycisku) i jeśli jest to IDYES to wywołać metodę SaveToFile.

0

@furious programming:
1.Okej czyli jak dobrze zrozumiałem to procedure mam w innym miejscu a w zdarzeniu tym onclick wywołuję tą procedure.
Coś takiego znalazłem w jednym z kursów:

procedure pokaz(ile:integer);
var
x: integer;
begin
for x:=1 to ile do showmessage('Okienko wyświetlone '+inttostr(x)+' raz');
end;

wywołanie procedury:

pokaz(3);

2.Jak napisałem o wczytywaniu pliku graficznie chodziło mi o to, że po uruchomieniu programu odpala mi się okienko z możliwością wyboru pliku. Nie wiem czy jest to możliwe w Delphi.
A co do prawidłowości tego pliku to się zastanawiam bo po nazwie to w sumie słabe zabezpieczenie, bo można utworzyć kilka plików, więc chyba z tą ścieżką do pliku coś spróbuje.

1

@arokos123:

procedure pokaz(ile:integer);
var
x: integer;
begin
for x:=1 to ile do showmessage('Okienko wyświetlone '+inttostr(x)+' raz');
end;

nie wiem gdzie to znalazłeś ..ale ten kod to koszmarek

0

@grzegorz_so:
Nie chodzi mi tu o poprawność tego kodu tylko o tą strukturę procedury i odwoływania się do niej.
Bo z tego co zrozumiałem to procedura to taka funkcja w której przekazujemy jakiś parametr.

pokaz(3);

Jeżeli stworzę jakąś procedure przykładowo odczytu z pliku i wywołam ją w przycisku onclick to w zmiennej dam przykładowo ścieżkę do pliku:

odczyt('C:\plik.txt');

Czy to będzie działało poprawnie i czy dobrze myśle?

1

Dobrze myślisz — tak to właśnie działa. Ciało metody czy po prostu procedury zwiera instrukcje do wykonania (czyli taki przepis), natomiast aby z procedury skorzystać, należy ją wywołać, przekazując jej jakieś dane. Wiele procedur posiada jakieś parametry, aby przekazywać im dowolne dane — dzięki temu procedura staje się uniwersalna i można ją wykorzystać wielokrotnie, bez pisania w kółko tego samego (reguła DRY).

W przypadku Twojej listy, kod ładujący listę z pliku powinien być zapisany w metodzie klasy TSinglyLinkedList, tak jak to jest zrobione w tym artykule, z którego korzystasz. Równie dobrze możesz obecny kod metody LoadFromFile usunąć i napisać tam własne instrukcje, które będą wykonywały ładowanie danych z pliku tekstowego, zamiast ze strumienia.

1

@arokos123:

Bo z tego co zrozumiałem to procedura to taka funkcja w której przekazujemy jakiś parametr.

Jesteś w będzie. Jedyne co odróżnia procedurę od funkcji jest fakt że funkcja robi swoje i zwraca rezultat a procedura robi swoje i nie zwraca żadnego rezultatu.
Formalnie obecność albo brak parametrów w wywołaniu funkcji/procedury w tym kontekście nie ma żadnego znaczenia.

0

Dobra już ogarnąłem większość jednak mam jeszcze kilka pytań.

  1. Stworzyłem sobie procedure do wczytywania pliku z lista. Zrobiłem sobie okienko, aby użytkownik wpisał hasło do listy. Jednak z tego co czytałem nie ma w delphi zmiennej password aby hasło wpisywane nie było widoczne. Nie wiem za bardzo jakby to zmodyfikować.Następna sprawa jeśli użytkownik zamknie okno to wykonuje się ten druga infstukcja warunkowa:
if Haslo<>'test'

procedure TForm1.Button1Click(Sender: TObject);
begin

Haslo:=InputBox('Dostęp do listy','Wpisz haslo:','');
if Haslo='test' then
    ListBox1.Items.LoadFromFile('C:\lista.txt');
if Haslo<>'test' then
     MessageDlgPos('Błędne hasło!',mtInformation,mbOKCancel,0,200,200);

end;

2.Co do usuwania elementu to mam coś takiego jednak jest to procedura wykorzystująca numer id. A skąd użytkownik ma wiedzieć który wiersz wyświetlany w okienku ma numer. Moze da się to zrobić aby użytkownik wpisywał dany element wiersza i on na tej podstawie został usunięty.

procedure TForm1.UsuńClick(Sender: TObject);
begin
b:=InputBox('Usuń element','Wprowadź id:','');

c:=StrToInt(b);
ListBox1.Items.Delete(c);
end;

3.Ostatnia sprawa to z tym okienkiem które ma się wyświetlać jak użytkownik coś zmodyfikował w liście, ma się pytać czy zapisać zmiany. Wymyśliłem coś takiego:

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if CanClose := false begin
         DialogResult result = MessageBox('Zapisać plik?', MessageBoxButtons.YesNo);
         switch(result)
        {
           case DialogResult.Yes:
              Memo1.Lines.SaveToFile('lista.txt');
           case DialogResult.No:
              break;
        }
  end;


Mam taką procedure FormCloseQuery i ona jest z automatu podpięta pod zamykanie programu? Czy muszę ją gdzieś wywołać. Bo mi nie działa

2
arokos123 napisał(a):

Stworzyłem sobie procedure do wczytywania pliku z lista. Zrobiłem sobie okienko, aby użytkownik wpisał hasło do listy. Jednak z tego co czytałem nie ma w delphi zmiennej password aby hasło wpisywane nie było widoczne. Nie wiem za bardzo jakby to zmodyfikować.

Z tego co widzę w dokumentacji, InputBox nie daje możliwości zasłonięcia zawartości pola edycyjnego, ale skoro robisz program na studia, to raczej nie powinieneś się tym przejmować. Można by stworzyć własne okienko dialogowe, położyć na nim TEdit i ustawić mu PasswordChar np. na * i wtedy tekst będzie zasłonięty, ale na razie to za dużo jak na Twoje zdolności. Raczej skup się na tym, aby ten program w ogóle działał, niż na wodotryskach.

Jeśli chodzi o kod logowania, to trudno zrozumieć co masz na myśli. Jeśli logowanie ma się odbywać po wciśnięciu danego przycisku i ma być jednorazowe, to powinieneś mieć jakąś zmienną, która przechowa stan logowania (zmienną logiczną). W skrócie, wyglądałoby to w ten sposób:

var
  // zmienna globalna przechowująca stan logowania
  LoggedIn: Boolean;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // jesli użytkownik wpisał hasło i zaakceptował okienko, do zmiennej
  // zostanie wpisane "True", jeśli podał hasło "test", a jesli
  // podał inne hasło, to do zmiennej trafi wartość "False"
  LoggedIn := InputBox('Dostęp do listy', 'Wpisz haslo:', '') = 'test';
  
  // sprawdzamy czy podano nieprawidłowe hasło
  if not LoggedIn then
    MessageDlgPos('Błędne hasło!', mtError, mbOKCancel, 0, 200, 200);
end;

Ten kod jest skrócony, bo bardziej przyjazna wersja wyglądałaby tak (nie licząc deklaracji zmiennej):

procedure TForm1.Button1Click(Sender: TObject);
var
  Password: String;
begin
  // pobieramy tekst wpisany przez użytkownika (lub domyślny, jeśli anulował okienko)
  Password := InputBox('Dostęp do listy', 'Wpisz haslo:', '');
  
  // sprawdzamy czy hasło jest poprawne
  if Password = 'test' then
    // jest poprawne, więc wpisujemy "True" do zmiennej
    LoggedIn := True
  else
  begin
    // hasło jest błędne, wpisujemy "False", bo nie zalogowano
    LoggedIn := False;
    
    // wyświetlamy okienko z informacjami o błędzie logowania
    MessageDlgPos('Błędne hasło!', mtError, mbOKCancel, 0, 200, 200);
  end;
end;

W każdym razie to jest prostu przykład tego jak przeprowadzić logowanie i ustawić wartość logiczną, określającą stan logowania. Mała uwaga — do wyświetlenia błędu wystarczy funkcja MessageBox — użycie MessageDlgPos nie jest konieczne.

Co do usuwania elementu to mam coś takiego jednak jest to procedura wykorzystująca numer id. A skąd użytkownik ma wiedzieć który wiersz wyświetlany w okienku ma numer.

Niech sobie policzy. ;)

Wszystko zależy od tego, w jaki sposób przedstawiasz dane w okienku. Jeśli używasz do tego ListBox, to użytkownik musiałby samodzielnie obliczyć numer wiersza, co jest raczej słabe. Są kontrolki, które pokazują numerowanie (np. TStringGrid), więc można by z nich skorzystać, ale na razie wystarczy Ci to co masz, czyli zwykły ListBox.

Moze da się to zrobić aby użytkownik wpisywał dany element wiersza i on na tej podstawie został usunięty.

Można i tak, a nawet w tym przypadku trzeba. I nie będzie to trudne, bo klasa TSinglyLinkedList ma już zdefiniowaną metodę Find, która służy właśnie do wyszukania elementu w liście i zwrócenia jego indeksu — a ten indeks można użyć do wywołania metody Delete. Pamiętać należy, że elementy listy oraz linie w ListBox są indeksowane od 0, a także o tym, że element z danymi wskazanymi przez użytkownika może w liście nie istnieć i w takim przypadku metoda Find zwróci wartość -1, co trzeba sprawdzić.

Jeśli Twoja lista również przechowuje liczby (tak jak w moim artykule) i zakładając, że lista jedokierunkowa ma nazwę List:

var
  // zmienna z Twoją listą jednokierunkową
  List: TSinglyLinkedList;

{..}

procedure TForm1.UsuńClick(Sender: TObject);
var
  DataValue, DataIndex: Integer;
begin
  // pobieramy wartość do usunięcia z listy
  // wartość "-1" to wartość domyślna, która oznacza anulowanie chęci usuwania
  DataValue := InputBox('Usuń element','Podaj liczbę do usunięcia z listy:', -1);
  
  // sprawdzamy czy użytkownik wpisał dane i zaakceptował okienko
  if DataValue <> -1 then
  begin
    // użytkownik podał dane, więc szukamy ich w liście
    DataIndex := List.Find(DataValue);
    
    // jeśli dane istnieją w liście
    if DataIndex <> -1 then
    begin
      // znaleziono wartość w liście, więc ją usuwamy z listy oraz z kontrolki
      List.Delete(DataIndex);
      ListBox1.Items.Delete(DataIndex);
    end
    else
      // dane nie istnieją w liście, wyświetlamy informację
      MessageDlgPos('Nie znaleziono danej liczby w liście.', mtInformation, mbOKCancel, 0, 200, 200);
  end;
end;

Przy czym pamiętaj o nie używaniu polskich słów do nazywania danych i komponentów — to zła praktyka.

Ostatnia sprawa to z tym okienkiem które ma się wyświetlać jak użytkownik coś zmodyfikował w liście, ma się pytać czy zapisać zmiany.

Żeby wiedzieć czy lista została zmodyfikowana czy nie, pasuje mieć jakąś zmienną, która będzie taką informację przechowywała — wcześniej sugerowałem zadeklarowanie zmiennej Modified. Czyli za każdym razem gdy coś się w liście zmienia, trzeba przypisać do tej zmiennej wartość True. Wyżej podałem kod usuwania danych, do którego należy dodać ustawianie tej zmiennej, ale tylko wtedy, gdy usuwanie faktycznie będzie miało miejsce (czyli gdy użytkownik poda dane istniejące w liście).

var
  Modified: Boolean;

{..}

procedure TForm1.UsuńClick(Sender: TObject);
var
  DataValue, DataIndex: Integer;
begin
  DataValue := InputBox('Usuń element','Podaj liczbę do usunięcia z listy:', -1);

  if DataValue <> -1 then
  begin
    DataIndex := List.Find(DataValue);
    
    if DataIndex <> -1 then
    begin
      List.Delete(DataIndex);
      ListBox1.Items.Delete(DataIndex);
      
      // lista została zmodyfikowana, więc ustawiamy zmienną na "True"
      Modified := True;
    end
    else
      MessageDlgPos('Nie znaleziono danej liczby w liście.', mtInformation, mbOKCancel, 0, 200, 200);
  end;
end;

Teraz tę zmienną będzie można użyć podczas zamykania programu i opcjonalnego wyświetlania pytania o zapis listy. Twój kod OnCloseQuery niestety jest zupełnie błędny, bo nadal nie zaznajomiłeś się z kursem programowania — np. używasz operatora przypisania zamiast porównania oraz instrukcji switch, której nie ma w Delphi.

Zdarzenie OnCloseQuery posiada parametr CanClose, który domyślnie zawiera wartość True i aby przerwać zamykanie okna, należy przypisać do tej zmiennej wartość False. To ma się dziać wtedy, gdy użytkownik w okienku z pytaniem wciśnie przycisk Anuluj lub zamknie okienko krzyżykiem (lub Alt+F4). Jeśli wciśnie przycisk Tak, lista ma być zapisana, a jeśli Nie, to nie. W obu przypadkach okno musi zostać zamknięte, więc CanClose nie powinien być modyfikowany.

W krócie można to napisać tak (pisane z głowy, nietestowane):

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // jeśli lista nie została zmodyfikowana, wychodzimy z tego zdarzenia
  // okno zotanie zamknięte, a lista nie będzie zapisana do pliku
  if not Modified then Exit;

  // tutaj wiemy, że lista została zmodyfikowana, czyli zmienna "Modified" zawiera "True"
  // musimy zapytać użytkownika czy chce ją zapisać czy nie

  // wyświetlamy okienko z pytaniem i rozróżniamy w instrukcji wyboru to który przycisk został wciśnięty
  case MessageBox('Zapisać listę do pliku?', 'Wyjście z programu', MB_YESNOCANCEL) of
    // użytkownik wyraził zgodę na zapis, więc zapisujemy listę do pliku
    IDYES:    List.SaveToFile('lista.txt');
    // użytkownik anulował wybór, więc anulujemy zamykanie okna
    IDCANCEL: CanClose := False;
  end;
end;

To jest skrócona wersja — dłuższa i bardziej przyjazna początkującemu wygląda tak:

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  DialogResult: Integer;
begin
  // jeśli lista nie została zmodyfikowana, wychodzimy z tej metody
  // okno zotanie zamknięte, a lista nie będzie zapisana do pliku
  if not Modified then Exit;

  // tutaj wiemy, że lista została zmodyfikowana, czyli zmienna "Modified" zawiera "True"
  // musimy zapytać użytkownika czy chce ją zapisać czy nie

  // wyświetlamy okienko z pytaniem i pobieramy ID wciśniętego przycisku
  DialogResult := MessageBox('Zapisać listę do pliku?', 'Wyjście z programu', MB_YESNOCANCEL);
  
  // jeśli użytkownik wcisnął przycisk "Tak"
  if DialogResult = IDYES
  begin
    List.SaveToFile('lista.txt');  // zapisujemy listę do pliku
    CanClose := True;              // wpisyjemy "True", czyli wyrażamy zgodę na zamknięcie okna
  end;
  
  // jeśli użytkownik wcisnął przycisk "Nie"
  if DialogResult = IDNO then
    CanClose := True;  // wpisyjemy "True", czyli wyrażamy zgodę na zamknięcie okna

  if DialogResult = IDCANCEL then
    CanClose := False;  // wpisujemy "False", czyli przerywamy zamykanie okna
end;

Powyższe pisane z głowy, mogą być błędy.

0

Nie do końca działa mi to zdarzenie OnCloseQuery. Zamykam program krzyżykiem lub alt+f4 i nie mam wyświetla mi się okienko czy zamknąć.
Ustawiłem sobie tą zmienną Modified:=True i nic mi się nie dzieje.

Druga sprawa to przekopiowałem sobie tą funkcję i nie mam pojęcia jak ją wywołać:

function TSinglyLinkedList.Find(AData: TData): Integer;
var
  ToFind : PElement;
begin
  Result := 0;
  ToFind := FFirst;

  while ToFind <> nil do
  if ToFind^.Data = AData then
    Exit
  else
  begin
    ToFind := ToFind^.Next;
    Inc(Result);
  end;

  Result := -1;
end;

Próbowałem coś takiego:

ListBox1.Items.Find(a);

Może za dużo chcę osiągnąć w tym programie ale czy istnieje takie zabezpieczenie w MessageBox zmienić długość wprowadzanych znaków, aby użytkownik mógł przykładowo maksymalnie 10 znaków wpisać w pole.

1

Jeśli chcesz używać listy omawianej w artykule, to cały jej kod musisz przekopiować do swojego projektu, a następnie zadeklarować zmienną, która będzie trzymała instancję tej listy. Nie da się listy jednokierunkowej (i żadnej innej) przyczepić do ListBox, bo ten komponent posiada własną i tylko na niej operuje.

Lista jednokierunkowa musi być osobno, a jej zawartość musi być dodatkowo skopiowana do ListBox. Czyli listy muszą być dwie i aby użytkownik widział aktualną zawartość tej jednokierunkowej, to po załadowaniu listy z pliku musisz ją skopiować do ListBox, a każda modyfikacja oznacza zmodyfikowanie dwóch list. Dlatego w kawałkach kodu podanych w poprzednim swoim poście, operacje przeprowadzane są na dwóch listach — List to lista jednokierunkowa, a ListBox to kontrolka z kopią zawartości.

0

Po dłuższej analizie zdecydowałem się, że listę będę wyświetlał w Memo1 z możliwością edycji.

Co do logowania to stworzyłem zmienną logiczną która przekazuje informacje o logowaniu. Tylko teraz jak użytkownik jest niezalogowany to może korzystać z przycisków dodaj element i usuń element.
Próbowałem to zabezpieczyć jedną instrukcją:

if Login=True then
begin
//tylko że tutaj jak wklejam wywołanie przycisku onclick to mam mase błędów
end;

Ostatnia sprawa to ta procedura która ma się odpalić jak zamykam program.

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  DialogResult: Integer;
begin
  
   if Modified:=False then Exit;
  DialogResult := MessageBox('Zapisać listę do pliku?', 'Wyjście z programu', MB_YESNOCANCEL);

 
  if DialogResult = IDYES
  begin
    List.SaveToFile('lista.txt'); 
    CanClose := True;             
  end;


  if DialogResult = IDNO then
    CanClose := True;  

  if DialogResult = IDCANCEL then
    CanClose := False;  
end;

Zdefiniowałem sobie zmienną Modified=True przy uruchamianiu programu.
Nawet stworzyłem sobie przycisk Zamknij który nie działa:

procedure TForm1.ZamknijClick(Sender: TObject);
begin
   Close()
end;
3

Nic z tego nie będzie — brak Ci podstawowej wiedzy, przez co robisz rzeczy zupełnie losowo, które nie mają sensu w kontekście Pascala. Jeśli chcesz cokolwiek wiedzieć i być w stanie robić podstawowe programy, musisz przysiąść do nauki i przyswoić wiedzę z zakresu podstaw programowania w Pascalu. Do tego czasu absolutnie nie będziesz wiedział o czym piszę, a więc nie będziesz miał szans zrobić czegokolwiek.

0

Dobra to ogólnie trochę się wziąłem do nauki, aby ogarnąć to Delphi.
Udało mi się zrobić wszystkie te podpunkty z wymienionych.

1.Chciałbym jeszcze zrobić sobie przycisk następny element. Ponieważ w liście jednokierunkowej jest to podstawa.
Chodzi o to, żeby jak odpalam liste to uruchamiała mi się pierwsza linijka z pliku, a jak klikne następny to + 1 i tak dalej. Aby mógł przechodzić przez elementy.

2.Dziwna sytuacja mi się przytrafiła i za bardzo nie wiem jak mam to naprawić. Mam taki kodzik:

procedure TForm1.ZalogujClick(Sender: TObject);
begin
Fille:=Edit1.Text;
if Fille=' ' then
  begin
       Application.MessageBox('Wpisz nazwe pliku!', 'Informacja', MB_OK +MB_ICONINFORMATION + MB_DEFBUTTON2);
       Exit;
  end
else
  begin

  if Login=True then
    begin
      Application.MessageBox('Lista została już wczytana!', 'Informacja', MB_OK +MB_ICONINFORMATION + MB_DEFBUTTON2);
      Exit

    end
  else
    Password:=InputBox('Dostęp do listy','Wpisz haslo:','');
    if Password=' ' then
    begin
       Application.MessageBox('Wpisz hasło!', 'Informacja', MB_OK +MB_ICONINFORMATION + MB_DEFBUTTON2);
       Exit;
    end;
    if Password='test' then

    begin
        Fille:= Fille;
        if FileCreate(Fille)=-1 then
          begin
              ListBox1.Items.LoadFromFile(Fille);
          end;
        if FileCreate(Fille)<>-1 then
          begin
             FileCreate(Fille);
             ListBox1.Items.LoadFromFile(Fille);
          end;
        Login:=True;
        Edit1.Visible:=false;
        Usun.Visible:=true;
        Wyczysc.Visible:=true;
        Zapisz.Visible:=true;

        Dodaj.Visible:=true;
        ListBox1.Visible:=true;
    end;
    if Password<>'test' then
         Application.MessageBox('Błędne hasło', 'Informacja', MB_OK +MB_ICONINFORMATION + MB_DEFBUTTON2);
  end;
end;

Przy wpisaniu w pole edycyjne nazwy pliku istniejącego wyświetla mi się komunikat: Proces nie moze uzyskac dostepu do pliku, ponieważ jest on używany przez inny proces.
A przy wpisaniu w pole edycyjne pliku który nie istnieje tworzy mi się on i wyświetla mi się ten sam komunikat.
Jak odpale plik tekstowy ręcznie to mam taki sam komunikat, nie wiem co się stało ale kilka dni temu mi wszystko działało.

2

@arokos123:

if Fille=' ' then

jeśli już to tak

if trim(Fille)='' then 

Do sprawdzenia czy plik istnieje jest funkcja fileexists(nazwaPliku)
W sumie to kod nadający się do poprawki, np. taki kwiatek ...

 Fille:= Fille;

Do obsługi plików Delphi (Free Pascal) ma kilka bardzo wygodnych klas.
Dla plików binarnych to klasy dziedziczące z Tstream, np. Tmemorystream albo Tfilestream a dla plików tekstowych klasa Tstrings i jej klasy potomne np. Tstringlist

cały kod

 if FileCreate(Fille)=-1 then
          begin
              ListBox1.Items.LoadFromFile(Fille);
          end;
        if FileCreate(Fille)<>-1 then
          begin
             FileCreate(Fille);
             ListBox1.Items.LoadFromFile(Fille);
          end;

można zastąpić czterema wierszami kodu

if fileexists(Fille) then
    ListBox1.Items.LoadFromFile(Fille)
else
    ListBox1.Items.Text := '';

ponadto

if FileCreate(Fille)=-1 then
          begin
              ListBox1.Items.LoadFromFile(Fille);
          end;

w tym kodzie FileCreate(Fille)=-1 oznacza tylko że nie udało się utworzyć pliku Fille co nie musi oznaczać że taki plik już istnieje a tym samym ListBox1.Items.LoadFromFile(Fille); może rzucić wyjątkiem

0

Ostatni element który mi został i chciałbym poprawić. To wczytywanie z pliku linijki danych.

Czy istnieje jakaś procedura, aby zwracała mi linijke z pliku tekstowego.
Przykładowo użytkownik podaje numer 5 i procedura zwraca 5 linijke z pliku.
Szukałem w internecie i nic nie znalazłem.

0
fuction textLine(aFilename:string;aRowNum:integer):string;
begin 
 result:='';
 ts := TStrings.create;
 try
   ts.LoadFromFile(aFilename);
   if ts.count>=(ARowNum) then 
      result:=ts[aRowNum-1];
 finally
   ts.Free;
 end;
end

tstrings indeksuje wiersze od zera. Pisane z głowy

0
arokos123 napisał(a):

Czy istnieje jakaś procedura, aby zwracała mi linijke z pliku tekstowego.

Istnieje — stary poczciwy ReadLn.

To co masz zrobić, jeśli chodzi o ładowanie danych z pliku do listy, to w pętli odczytywać linijka po linijce i wrzucać je do listy kierunkowej. Nie potrzebujesz pobierać konkretnej linijki z pliku, bo wszystkie operacje na liście powinny być wykonywane na danych załadowanych do pamięci, a plik powinien być używany wyłącznie podczas wstępnego załadowania danych do listy oraz końcowego zapisu listy do pliku.

Tak więc zrezygnuj z tego o czym myślisz, bo to rozwiązanie nieefektywne. Takie rzeczy robi się tylko w przypadku, gdy pliki są ogromne (np. gigabajtowe), więc ładuje się tylko ich fragmenty, aby nie zapchać RAM-u. jednak tego typu rozwiązania są skomplikowane w implementacji i jak na Twój program to o wiele za wysoka półka. Skup się na tym, aby Twój program działał prawidłowo i logicznie, bez utrudniania sobie roboty.

0

Mam tą liste w listboxie i chciałbym stworzyć przycisk następny i pierwszy element.
Stworzyłem stałą Linia=0 którą bym zwiększał jeśli przycisk się wykona. Tak myślałem sobie aby odszukać jakiejś procedury która zwraca mi daną linie w pliku.
Co do ReadLn to coś takiego znalazłem ale nie wiem czy to mi pomoże i zbytnio nie rozumiem tej procedury:

procedure ReadLn([ var F: Text; ] V1 [, V2, ...,Vn ]);
1
arokos123 napisał(a):

Mam tą liste w listboxie i chciałbym stworzyć przycisk następny i pierwszy element.

Nic mi to nie mówi. Co mają te przyciski robić?

Stworzyłem stałą Linia=0 którą bym zwiększał jeśli przycisk się wykona. Tak myślałem sobie aby odszukać jakiejś procedury która zwraca mi daną linie w pliku.

Bez sensu — musisz sobie przemyśleć ten kod i dojść do jak najprostszego rozwiązania. No i tym najprostszym rozwiązaniem jest właśnie jednokrotne ładowanie i jednokrotny zapis listy z i do pliku tekstowego. Każde odstępstwo od tego sposobu skutkować będzie coraz bardziej skomplikowanym kodem, a więc nakład pracy będzie większy, a sam kod mniej zrozumiały.

Zrób tak:

  • po zalogowaniu załaduj całą zawartość pliku tekstowego do pamięci (czyli zbuduj listę kierunkową z tej zawartości):
    • podczas wczytywania, ładuj linijka po linijce za pomocą procedury ReadLn,
    • po załadowaniu listy, przepisz jej zawartość do ListBox, tak aby mieć jej kopię i wyświetlać ją dla użytkownika,
  • podczas modyfikacji listy, każdą zmianę przeprowadzaj na liście kierunkowej oraz w ListBox, tak aby obie listy zawierały to samo,
  • podczas zamykania programu:
    • jeśli użytkownik zaakceptował chęć zapisu zmian, otwórz plik do zapisu, korzystając z procedury Rewrite,
    • teraz iteruj w pętli po węzłach listy kierunkowej i zapisuj dane za pomocą WriteLn,
    • na koniec zamknij plik i zwolnij listę kierunkową z pamięci.

To tyle, najprostsze rozwiązanie, w sam raz dla prostego projektu na studia.

Co do ReadLn to coś takiego znalazłem ale nie wiem czy to mi pomoże i zbytnio nie rozumiem tej procedury:

procedure ReadLn([ var F: Text; ] V1 [, V2, ...,Vn ]);

ReadLn ma wiele zastosowań — jest to de facto pseudoprocedura, bo jej kod jest budowany dynamicznie w trakcie kompilacji (nigdzie nie znajdziesz implementacji tej procedury). Pozwala ona wczytywać dane dowolnego typu, zarówno ze standardowego wejścia (np. z konsoli, w której dane użytkownik wpisuje z klawiatury), a także bezpośrednio z pliku.

Aby wczytać dane z pliku, pierwszym parametrem w wywołaniu musi być zmienna plikowa — np. typu Text lub czytelniej TextFile. I to samo tyczy się WriteLn — to też pseudoprocedura, która pozwala zapisywać dane zarówno do standardowego wyjścia (konsoli) jak i do plików. I składnia jest podobna — jeśli chcesz zapisywać dane do pliku, pierwszym parametrem musi być zmienna plikowa.

Przykład odczytu danych z pliku list.txt i wyświetlenie zawartości w konsoli:

var
  Input: TextFile; // zmienna plikowa
  Line: String;    // zmienna do przechowywania linii
begin
  // skojarzenie zmiennej z plikiem "list.txt"
  AssignFile(Input, 'list.txt');

  // otwarcie pliku do odczytu
  Reset(Input);

  // dopóki nie napotkano końca pliku
  while not EoF(Input) do
  begin
    // wczytanie linii z pliku do zmiennej
    ReadLn(Input, Line);

    // wyświetlenie w konsoli wartości zmiennej
    WriteLn(Input);
  end;

  // zamknięcie pliku
  CloseFile(Input);

  // oczekiwanie na wciśnięcie "Enter" i zamknięcie konsoli
  ReadLn();
end.

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