Zapis do pliku lokalizacji dynamicznie utworzonych elementów

0

Witam
Jakiego najprostszego mechanizmu mogę użyć aby zapisać do pliku lokalizację dynamicznie utworzonych elementów a w momencie startu programu mieć możliwość wczytania tych elementów wraz z ich lokalizacją. Za pomocą programu tworzę dowolną ilość tpaneli mogę je sobie przesuwać w dowolne miejsce formatki ale jak już sobie je rozmieszczę to chciałbym ich lokalizacje mieć gdzieś zapisane aby w momencie startu programu nie musieć od nowa ich generować i zmieniać lokalizacji?

0

Serializacja do json/xml i potem deserializacja: https://code.google.com/archive/p/delphi-oop/wikis/SvSerializer.wiki

0

To dla mnie chyba za twardy orzech. Muszę znaleźć dobrze opisane przykłady bo nie pojmuję zagadnienia.

0

@Deniss77: zainteresuj się metodami WriteComponent i ReadComponent z klasy strumienia.

2

No to skoro to dla Ciebie za trudne to najprościej będzie posłużyć się klasą TIniFile i trzymać całość w pliku ini.

Niech przykładowo zawartość tego pliku będzie następująca:

[General]
PanelCount=4

[Panel1]
Top=123
Left=100

[Panel2]
Top=223
Left=45

[Panel3]
Top=698
Left=0

[Panel4]
Top=200
Left=870

Jak to obsłużyć poczytasz np. tutaj http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/IniFiles_TIniFile.html

Ja robiłem coś takiego z przyciskami i działa idealnie. Dodatkowo każdy przycisk miał odpowiednią akcję którą wykonywał. Program na starcie będzie czytał PanelCount i potem w pętli czytał parametry Top oraz Left z kolejnych sekcji o nazwie Panelnn gdzie za nn podstawisz sobie kolejne cyfry od 1 do PanelCount

0

TiniFile jest raczej do ogarnięcia dla mnie, współrzędne już odczytuję sobie, problem teraz upatruję w tym jak zrobić żeby wykreować na podstawie tych danych te wszystkie panele podczas startu programu, ale to już temat na jutro.

3

Jeśli skorzystasz ze schematu podanego przez @Mr.YaHooo, to wystarczy wczytać liczbę elementów z klucza PanelCount. Ta liczba posłuży do określenia liczby iteracji pętli, w której trzeba utworzyć obiekt komponentu, przypisać go do rodzica, nadać mu nazwę (jeśli do zapisu używasz FindComponent), a na koniec pobrać dane o jego rozmiarze z odpowiedniej sekcji pliku.

var
  Input: TIniFile;
  Panel: TPanel;
var
  Count, Index: Integer;
begin
  Input := TIniFile.Create('config.ini');
  try
    Count := Input.ReadInteger('General', 'PanelsCount', 0);
    
    for Index := 1 to Count do
    begin
      Panel := TPanel.Create(MyForm);
      Panel.Name := 'Panel%d'.Format([Index]);
      
      Panel.Left := Input.ReadInteger(Panel.Name, 'Left', 0);
      Panel.Top := Input.ReadInteger(Panel.Name, 'Top', 0);
    end;
  finally
    Input.Free();
  end;
end;

Jeśli każdy z tych paneli może posiadać inne wymiary, to w każdej sekcji oprócz pozycji, zapisz jeszcze szerokość i wysokość kontrolek, a w kodzie odczytującym dodaj poniższe dwie linijki:

Panel.Width := Input.ReadInteger(Panel.Name, 'Width', 0);
Panel.Height := Input.ReadInteger(Panel.Name, 'Height', 0);

Powyższy kod obsługuje indeksowanie od 1, w zgodzie z przykładowym plikiem konfiguracyjnym, jednak sam polecam indeksować wszystko od 0, żeby nie było później niespodzianek.

Kod pisany z palca.

0

Wygląda na do opanowania, teraz będę ćwiczył zapisywanie do pliku i jak to opanuję to dopiero się wezmę za odczytywanie.

Zapis do pliku już działa. A teraz mam znowu pytanie, zapis realizuję w taki sposób:

var
  INI : TINIFile;
begin
  INI := TINIFile.Create(ExtractFilePath(Application.ExeName) + 'setup.ini');
  try
    { jakieś instrukcje }
  finally
    INI.Free;
  end;
end;

Czy dopuszczalne jest powielanie tej procedurki za każdym razem jak chcę zrobić zapis do pliku, oczywiście za każdym razem następuje zapis innych wartości pomiędzy try ... finally?

Jak mogę pozyskać współrzędne panela po kliknięciu buttona. Chcę współrzędną wyświetlić na polu edit. Myślałem o takim kierunku składni:

edit6.Text := Tpanel(FindComponent('obiekt'+ inttostr(zmienna2))).top
1
Deniss77 napisał(a):

Czy dopuszczalne jest powielanie tej procedurki za każdym razem jak chcę zrobić zapis do pliku, oczywiście za każdym razem następuje zapis innych wartości pomiędzy try ... finally?

Powielanie kodu zawsze jest złe – to łamanie reguły DRY. Procedury i funkcje tworzy się w taki sposób, aby zawierały pewną, uniwersalną logikę, realizującą konkretne zadanie, do wielokrotnego wykorzystania (czyli wywołania) lub w celu wydzielenia logiki do zwiększenia czytelności kodu.

Jeśli potrzebujesz w swoim programie wykonać jakieś zadanie w kilku jego miejscach, to napisz sobie procedurkę przyjmującą konkretne parametry, na których będzie ona operować. Jedna procedura, a w kilku miejscach ją wywołuj, podając odpowiednie dane w parametrach.

Jak mogę pozyskać współrzędne panela po kliknięciu buttona.

Zdarzenie OnClick przycisku dostarcza w parametrze referencję klikniętego przycisku i nic więcej. Tak więc do dyspozycji masz jedynie dane zawarte w obiekcie tego przycisku.

Nic nie wiemy na temat przycisków – nie wiemy gdzie się znajdują (w której kontrolce są osadzone), nie wiemy jakie mają nazwy i nie wiemy w jaki sposób i czy w ogóle są powiązane z tymi panelami, więc niczego nie da się w tej chwili doradzić. Opisz problem sensownie i podaj wszystkie ważne szczegóły.

0

Po prostu chciałem jakoś pozyskać współrzędne paneli o określonej nazwie abym mógł je zapisać do pliku ini.
Problem polega na tym że mogę każdy panel sobie przesunąć gdzie chcę, i jak już panele ustawię w miejscach docelowych to klikam sobie buttona zapisz lokalizację i zapisuję współrzędne wszystkich paneli mających określoną nazwę. Później przy starcie programu pobiorę z pliku ini te parametry i ustawię panele tak jak zostawiłem.

0

W dalszym ciągu piszesz o tym co zamierzasz zrobić, zamiast o tym, o co pytam.

Deniss77 napisał(a):

Po prostu chciałem jakoś pozyskać współrzędne paneli o określonej nazwie abym mógł je zapisać do pliku ini. […] to klikam sobie buttona zapisz lokalizację i zapisuję współrzędne wszystkich paneli mających określoną nazwę.

Jaką określoną nazwę? Jakie w ogóle nazwy nadajesz tym panelom? W jakim komponencie są te panele osadzone? Ile ich jest? Odpowiedz dokładnie na te pytania, a postaram się podać rozwiązanie.

0

Program generuje tpanele dynamicznie po kliknięciu określonego buttona i nadaje im nazwy obiekt(id) za każdym razem zwiększając id, panele się generują i mają założoną nazwę. Rozmieszczam je sobie na formatce przeciągając myszką w jakieś miejsce. Teraz chciałbym dorobić taką funkcjonalność aby zapisać ilość wygenerowanych paneli i ich współrzędne do pliku ini aby później mógł przywrócić ich pozycję. Tylko nie wiem jak pobrać ich wartości left i top.

Paneli jest tyle ile razy kliknę buttona generującego panela.

Albo inaczej kładę na formatce obiekt TPanel nadaję mu nazwę panel1 i teraz chcę odczytać współrzędne panel1 klikając w buttona odczytaj współrzędne i wyświetlić je w polu Tedit np edt1.text.

1
Deniss77 napisał(a):

Albo inaczej kładę na formatce obiekt TPanel nadaję mu nazwę panel1 i teraz chcę odczytać współrzędne panel1 klikając w buttona odczytaj współrzędne i wyświetlić je w polu Tedit np edt1.text.

Możesz skorzystać z metody FindChildControl lub FindComponent.

Pierwsza przegląda tylko kontrolki znajdujące się bezpośrednio wewnątrz danego komponent-rodzica lub bezpośrednio wewnątrz formularza (zależy z którego obiektu zostanie wywołana – komponentu czy formularza). To oznacza, że jeśli metodę wywołasz z obiektu formularza – jako Form1.FindChildControl – to metoda przeszuka bezpośrednią zawartość formularza. Druga natomiast przeszukuje całą zawartość danego komponentu-rodzica lub formularza, bez względu na poziom osadzenia komponentów.

Jeśli o internalsy chodzi, to FindChildControl przegląda listę subkontrolek z pola FControls, a FindComponent przeszukuje listę z pola FComponents. Jeśli komponenty są wielopoziomowo zagnieżdżone w sobie, to listy z wymienionych pól posiadają różną zawartość, dlatego dobranie odpowiedniej metody przeszukującej jest kluczowe.


Skoro już znasz różnicę w działaniu tych metod, możesz wybrać odpowiednią do realizacji swojego zadania. Przykład dla pierwszej metody poniżej:

procedure TForm1.Button1Click(Sender: TObject);
var
  Panel: TPanel;
begin
  Panel := Self.FindChildControl('Panel1') as TPanel;
  Edit1.Text := 'Left: %d; Top: %d'.Format([Panel.Left, Panel.Top]);
end;

W powyższym przykładzie Self zawiera referencję okna, więc przeszuka tylko komponenty leżące bezpośrednio na formularzu.

0

A takie rozwiązanie można uznać za poprawne:

edit6.Text := inttostr(Tpanel(FindComponent('obiekt'+ inttostr(zmienna2))).Top)

Działa mi to. Dopisałbym tylko drugą linijkę z left

2

Jeśli działa prawidłowo to można uznać za poprawne. Nie wiem czym jest zmienna2, bo nic o niej wcześniej nie pisałeś, a jej nazwa kompletnie niczego nie mówi o swoim przeznaczeniu i zawartości. Ale skoro działa, to niech będzie.

Przy czym ten kod nie jest zabezpieczony – FindComponent może zwrócić nil, co znaczy, że dalsze instrukcje spowodują rzucenie wyjątku (a dokładniej, przy próbie odczytu wartości właściwości Top). Rozbij tę linijkę na kilka osobnych, tak aby było wiadomo co się dzieje i dodaj zabezpieczenia, jeśli to konieczne.

Poza tym jeśli z wyszukanego komponentu potrzebujesz odczytać więcej niż jedną wartość (a potrzebujesz), to powinieneś raz pobrać referencję kontrolki, a następnie użyć jej do odczytu Left i Top i ew. innych danych.

0

Ok dziękuję. Postaram się zrobić zgodnie ze wskazówkami.

0

Mam pytanie - tak z ciekawości. Co za program piszesz? Co on robi? Pytam, bo się zastanawiam, do czego potrzebne są Ci dynamicznie generowane i przesuwalne panele i jakoś nic ewidentnego nie przychodzi mi do głowy ;)

I jeszcze taka sugestia/porada. Żeby była jasność - nie twierdzę, że plik .INI jest złym pomysłem, ale możesz się też zainteresować sqlite. Jest do tego bardzo fajny wrapper, który sprowadza obsługę całego SQL do dosłownie kilku linii. A moim zdaniem jednak wygoda korzystania z SQL jest większa niż obsługa pliku INI.

Ten wrapper jest dostępny pod adresem http://www.ararat.cz/doku.php/en:sqlitewrap . Osoby trochę obeznane w świecie Delphi mogą zauważyć, że jest to ten sam twórca, który odpowiada za Synapse, czyli bibliotekę do obsługi komunikacji TCP/IP :)

0

Kontrolki leżą na mapie z timage i swoim kolorem odzwierciedlają stany różnych urządzeń, przesuwam je żeby znalazły się w odpowiednim miejscu rysunku.

0

Czyli jakaś automatyka - dobrze kombinuję?

0

Dokładnie tak.

1

@Deniss77: a nie wolałbyś mieć jednego komponentu zawierającego w sobie wirtualne prostokąty? Byłoby co prawda więcej roboty z programowaniem takiej kontrolki, ale przynajmniej nie miałbyś problemu z serializacją jego zawartości do pliku.

Dwa lata temu inny użytkownik tego forum potrzebował podobnej funkcjonalności – też chodziło o przesuwanie/układanie prostokątów. W razie czego jest to wątek Przesuwanie obrazka po canvas, a w tym poście pokazałem w jaki sposób taką kontrolkę oprogramować. Znajdziesz tam również gotowy projekt dla Lazarusa, którym możesz się pobawić.


Edit: hmm… przykład z tamtego wątku wykorzystuje subclassing kontrolki typu TPaintBox, więc to nieco inne rozwiązanie. No i nie podobają mi się te klasy do przechowywania danych – nie używają generyków, a mogłyby. W każdym razie opakowanie takiej funkcjonalności w normalny komponent jest jak najbardziej możliwe, a nawet zalecane.

0

Dzięki przeanalizuję to jeszcze.

Jak sformatować wyrażenie

Panel.Caption := inttostr(Input.ReadInteger(Panel.Name, 'Caption', 0));

aby poprawnie wyświetlać caption paneli niezależnie od znaków znajdujących się w odczytywanym pliku ini w pozycji caption. Teraz caption paneli wyświetla się poprawnie tylko jeżeli w pozycji caption pliku ini znajdują się cyfry jeżeli znajdą się tam litery to caption wyświetla się 0. Męczy mnie to już spory czas.

0

Jeśli w Caption są litery, to nie możesz traktować go jako liczby – przecież to oczywiste.

Panel.Caption := Input.ReadString(Panel.Name, 'Caption', '');

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