Właściwość rekordowa

0

Witam wszystkich, potrzebuję pomocy przy projektowaniu klasy. Mam np. taki rekord:

TFooRec = record
  Counter: Integer;
  Index: Integer;
end;

Chcę go wykorzystać w klasie:

TFooClass = class
private
  FFooRec: TFooRec;
published
  property FooRec: TFooRec read FFooRec;
end;

Ale jest pewna przeszkoda, mianowicie w jaki sposób właściwość FooRec zadeklarować w klasie tak, bym mógł odczytywać Counter jedną procedurą, a Index inną?

Dla przykładu podam kod, który w żadnym wypadku się nie skompiluje, ale może naprowadzi was na sens pytania:

TFooClass = class
private
  FFooRec: TFooRec;

  function GetCounter: Integer;
  function GetIndex: Integer;
published
  FooRec: record
    property Counter: Integer read GetCounter;
    property Index: Integer read GetIndex;
  end;
end;

Ten kod się nie skompiluje, ale chodzi mi o to, żeby poszczególne pola rekordu odczytywać innymi funkcjami, ale żebym mógł się do tych właśnie pól rekordu odnosić się w kodzie w taki sposób:

procedure FooProc;
var
  FooClass: TFooClass;
  Counter: Integer;
begin
  { konstruktor }

  Counter := FooClass.FooRec.Counter;

  { destruktor }
end;

Może to i zakręcone ale potrzebuję zgrupować kilka właściwości w jedną, ale żeby można było odczytywać je innymi funkcjami.

Bardzo proszę o pomoc. Pozdrawiam.

PS: Będę jutro rano on-line to kuknę tutaj.

0

zmień record na class i będzie git (no i oczywiście odpowiednio oprogramuj)

0

Znaczy który rekord? Ten w private czy w published?

Ten w private nie musi być rekordem, te pola mogą być luźno:

TFooClass = class
private
  FCounter: Integer;
  FIndex: Integer;
 
  function GetCounter: Integer;
  function GetIndex: Integer;
published
  FooRec: record
    property Counter: Integer read GetCounter;
    property Index: Integer read GetIndex;
  end;
end;
0

Dzięki misiekd, nie wiem czy dobrze zrozumiałem, w każdym razie przestudiuje podany przez Ciebie kod i odpiszę. Jak będę miał jakieś problemy - dam znać.

Dziękuję z góry. Pozdrawiam.

0

W dalszym ciągu nie wiem, w jaki sposób mógłbym wykorzystać podany przez Ciebie kod... Jakoś nie mogę zrozumieć o co w nim chodzi...

Jak dla mnie kod na tamtej stronie nie dotyczy tematu, fakt faktem utworzone są właściwości opisane klasą, ale w dalszym ciągu sekcjia published wygląda tak:

 property Property1 : TExpandingRecord read FProperty1 write SetProperty1;
property Property2 : TExpandingRecord read FProperty2 write SetProperty2;
property Property3 : TExpandingRecord read FProperty3 write SetProperty3;

Czyli w dalszym ciągu widoczne w CodeCompletion będą trzy właściwości: Property1, property2 i Property3 z tylko taką różnicą, że każda z nich posiada jeszcze subitemy (IntegerProp, StringProp, CollectionProp) a nie o to mi chodziło...

Spróbuję jeszcze raz wyjaśnić:

mam dla przykładu taką klasę:

TFooClass = class
private
  FCount: Integer;
  FIndex: Integer;

  function GetCount: Integer;
  function GetIndex: Integer;
public
  constructor Create;
  destructor Destroy; override;
published
  property Count: Integer read GetCount;
  property Index: Integer read GetIndex;
end;

Chcę, aby w CodeCompletion właściwości Count i Index były umieszczone np. wewnątrz właściwości Data:

procedure Foo;
var
  FooObj: TFooClass;
  Count: Integer;
  Index: Integer;
begin
  { konstructor }

  Count := FooObj.Data.Count;
  Index := FooObj.Data.Index;

  { destruktor }
end;

Czyli właściwości Count i Index umieszczone są wewnątrz właściwości Data, lecz odczytywane są przez inne funkcje (GetCount i GetIndex).

Moje pytanie brzmi: w jaki sposób i gdzie mam zamieścić informacje o tym, że ww właściwości są wewnątrz Data?

0

Data musiało by być inna klasą. Tak jak w TStringList albo obiektach typu Memo czy RichEdit masz własnośc Strings (domyślna dla TStringList) albo Lines, która mają własności Count i inne albo metody. Wtedy dajesz property Data typu TMojTypData. O ile dobrze zrozumiałem co chcesz osiągnąć.

0

No tak, musiało by być inną klasą, ale jeżeli tak bym zrobił to w jaki sposób każdą właściwość tej innej klasy odczytywać inną funkcją?

Olesio, chcę pogrupować właściwości na kategorie tak, by na liście w CodeCompletion nie było wszystko pomieszane. Dam jeszcze inny przykład: mam w klasie przechowywać przykładowo informacje o pliku:

  • nazwa pliku (String)
  • ścieżka katalogu (String)
  • litera dysku (String)
  • ukryty (Boolean)
  • archiwalny (Boolean)
  • tylko do odczytu (Boolean)

Trzeba pogrupować te pola, które odnoszą się do nazwy pliku osobno, a te odnośnie atrybutów osobno. Dajmy na to, że pogrupowaliśmy je w ten sposób:

Grupa NazwaPliku:

  • nazwa pliku
  • ścieżka katalogu
  • litera dysku

Grupa Atrybuty:

  • ukryty
  • archiwalny
  • tylko do odczytu

No to mamy w pseudokodzie grupy. Teraz wykorzystując rekordy stworzylibyśmy te grupy tak:

TFileNameData = record
  FileName: String;
  Path: String;
  Drive: String;
end;

TAttributes = record
  Hidden: Boolean;
  Archival: Boolean;
  ReadOnly: Boolean;
end;

TFooClass = class
private
  FFileNameData: TFileNameData;
  FAttributes: TAttributes;
public
  constructor Create;
  destructor Destroy; override;
published
  property FileNameData: TFileNameData read FFileNameData;
  property Attributes: TAttributes read FAttributes;
end;

No i jak widać z tego kodu mamy dwie właściwości rekordowe, które można tylko odczytywać. Jeżeli chce pobrać nazwę pliku zawartą we właściwości rekordowej FileNameData, zrobiłbym to tak:

{...}
var
  FooClass: TFooClass;
  FileName: String;
begin
  {..}

  FileName := FooClass.FileNameData.FileName;

  {..}

Jak widać wpisujemy najpierw nazwę klasy, później grupę FileNameData i nazwę właściwości (tu FileName). Teraz potrzebuję odczytać każdą z pól rekordów inną funkcją, oto ich lista:

function GetFileName: String;
function GetPath: String;
function GetDrive: String;
function GetHidden: Boolean;
function GetArchival: Boolean;
function GetReadOnly: Boolean;

Ale jak mam to zaprogramować? Jak grupując te sześć właściwości do dwóch grup, odczytywać każdą z nich osobnymi funkcjami?

TFooClass = class
private
  FFileNameData: TFileNameData;
  FAttributes: TAttributes;

  function GetFileName: String;
  function GetPath: Stirng;
  function GetDrive: String;
  function GetHidden: Boolean;
  function GetArchival: Boolean;
  function GetReadOnly: Boolean;
{..}

Mam już je w sekcji private, ale jak zaprogramować właściwości by do każdej przypisać osobną funkcję zachowując grupy?

0

Nie wiem czy dobrze zrozumiałem w czym problem, ale wedlug mnie to umieszczasz te funkcje w klasach - kategoriach, które sobie utworzyłeś. Może jak Misiek będzie to Tobie dokładniej podpowie, bo ja przeglądając teraz na szybko może nie za bardzo dokładnie wczytałem się w to co już masz i co chcesz.

0
John Pablo napisał(a)

Czyli w dalszym ciągu widoczne w CodeCompletion będą trzy właściwości: Property1, property2 i Property3 z tylko taką różnicą, że każda z nich posiada jeszcze subitemy (IntegerProp, StringProp, CollectionProp)

zamiast trzy właściwości wstawisz tu RAZ Data a zamiast subitemy(IntegerProp, StringProp, CollectionProp) wstawisz subitemy (Count i Index) to dostaniesz dokładnie to:

Chcę, aby w CodeCompletion właściwości Count i Index były umieszczone np. wewnątrz właściwości Data:

Ja po przeczytaniu Twoich postów dalej nie mam pojęcia co chcesz zrobić jeśli powyższe Ci nie odpowiada

FileName := FooClass.FileNameData.FileName;
Jak widać wpisujemy najpierw nazwę klasy, później grupę FileNameData i nazwę właściwości (tu FileName)

przecież to jest dokładnie to samo co na tej stronie, którą Ci podałem
Jak widać wpisujemy najpierw nazwę klasy, później grupę Property1 i nazwę właściwości (tu IntegerProp)

0

Dobrze misiekd piszesz, zrozumiałem o co Ci chodzi dopiero po przeczytaniu tego arta:

http://4programmers.net/Delphi/ARtykuły/Jak_korzystać_z_TPersistent.

Nie wiem dlaczego na początku nie zrozumiałem, może pomieszałem pojęcia czytając po angielsku... W każdym razie jedna część jest załatwiona - wykorzystując klasy zamiast rekordów mogę zrobić to tak, jak mówisz, ale nie wiem czy do końca.

Z racji tej, że nie mam teraz dostępu do kompilatora ani w ogóle dziś, mam jeszcze pewien problem (chodzi mi tylko o to, czy zadziała). Bazując na klasie z ww artykułu, jeżeli dodam do klasy TPrzykład jakieś prywatne pole, np. takie:

type
  TOpcje = class(TPersistent)
  private
    FProp1: Integer;
    FProp2: string;
    FProp3: Boolean;
  published
    property Prop1: Integer read FProp1 write FProp1;
    property Prop2: string read FProp2 write FProp2;
    property Prop3: Boolean read FProp3 write FProp3;
  end;
 
  TPrzyklad = class(TComponent)
  private
    FNowePole: TPoint;

    FOpcje: TOpcje;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Opcje: TOpcje read FOpcje write FOpcje;
  end;

to czy jakakolwiek metoda z clasy TOpcje będzie miała dostęp do odczytu FNowePole?. Do tego właśnie potrzebna jest mi taka konstrukcja. Ma być czytelna, właściwości mają być pogrupowane z tym, że każda z nich będzie odczytywać informacje innymi funkcjami. Ale te informacje będą zawarte w jednym polu w klasie "bazowej" (to tak jak bym w klasie TOpcje każdą właściwość odczytywał przez różne funkcje, z tym że te funkcje będą odczytywać informacje z pola FNowePole).

Wiem, że strasznie mieszam, ale nie jest to takie proste do wytłumaczenia (przynajmniej dla mnie)... Żeby było w miare jasne - zamieszczam poniżej kod, a pod nim do niego pytanie:

TData = class(TPersistent)
published
  property X: Cardinal read FPoint.X;
  property Y: Cardinal read FPoint.Y;
end;

TFoo = class(TObject)
private
  FPoint: TPoint;
  FData: TData
public
  constructor Create;
  destructor Destroy; override;
published
  property Data: TData read FData;
end;

{ pomijam dalszą część kodu - tu wszystko jest jasne }

Czy taki kod się skompiluje? A właśnie o to mi chodzi, aby subitem (klasa TData) mógł odczytać pole z klasy "bazowej" (TFoo).

PS: To tylko przykład, całość pisałem z głowy.

Aha, zapomniałem dodać, że nie tworze komponentu, klasa będzie operować na zbirze danych.

0

jedno podstawowe pytanie - po co?? Odpowiedz mi na nie a być może coś się będzie dało z tym zrobić. BTW po to wkładasz to w "podobiekt" aby tam było. Co Ci przeszkadza, że FProp1 nie będzie w TOpcje?? A jak "musi" być w klasie głównej to je tam wsadź i property też zostaw w klasie głównej i koniec. Widzi mi się, że na siłę chcesz zrobić rower z kwadratowymi kołami...

0
John Pablo napisał(a)
TData = class(TPersistent)
published
  property X: Cardinal read FPoint.X;
  property Y: Cardinal read FPoint.Y;
end;

to czy jakakolwiek metoda z clasy TOpcje będzie miała dostęp do odczytu FNowePole?. Do tego właśnie potrzebna jest mi taka konstrukcja. Ma być czytelna, właściwości mają być pogrupowane z tym, że każda z nich będzie odczytywać informacje innymi funkcjami. Ale te informacje będą zawarte w jednym polu w klasie "bazowej" (to tak jak bym w klasie TOpcje każdą właściwość odczytywał przez różne funkcje, z tym że te funkcje będą odczytywać informacje z pola FNowePole).

Możesz przecież przekazać wskaźnik na TFoo do klasy TData. Albo (lepiej) wskaźnik na FNowePole.
Najlepiej w trakcie konstruowania - w TFoo.Create.
No i pewnie trzeba będzie użyć funkcji zamiast bezpośredniego dostępu:

TData = class(TPersistent)
private
  FPointPtr: PPoint;
published
  property X: Cardinal read GetPointX;
  property Y: Cardinal read GetPointY;
end;

Funkcje GetPoint* możesz zrobić jako jedną, używając indeksu - zobacz Left, Top w:
http://www.delphibasics.co.uk/RTL.asp?Name=Property

0
misiekd napisał(a)

jedno podstawowe pytanie - po co??

Po co to ja już wiem, misiekd, gdyby nie było takiej potrzeby to uwierz mi, nie musiałbym kombinować a klasa już dawno by powstała.

misiekd napisał(a)

Co Ci przeszkadza, że FProp1 nie będzie w TOpcje??

Gdyby nie musiało być to nie musiałbym tworzyć subitemów właściwości.

misiekd napisał(a)

A jak "musi" być w klasie głównej to je tam wsadź i property też zostaw w klasie głównej i koniec.

No nie do końca, bo jeżeli zostawie je w klasie głównej to grupowanie szlag trafi. Potrzebuję pogodzić te dwie rzeczy, ale widzę, że to jednak nie takie proste jak się by to mogło wydawać...

vpiotr napisał(a)

Funkcje GetPoint* możesz zrobić jako jedną, używając indeksu

A czy w ten sam sposób mogę przekazać dowolny typ danych tak jak ma to miejsce w parametrach procedur czy funkcji? Bo jeżeli w ten sam sposób mogę przekazać dowolne pole (Może być tylko do odczytu) to właśnie to pole przekazałbym do klasy TData i tam odczytał jego dane. Tylko czy tak można...?

0

Dobra, powolutku bo nie wiem czy dobrze się zrozumieliśmy.

vpiotr napisał(a)

Nie, każda taka funkcja może obsłużyć tylko jeden typ danych AFAIK.

Property Coords[Index: Integer] : Longint read GetCoord write SetCoord;

Czyli tylko Integer jako indeks można?

Przedstawię jeszcze raz kod pomocniczy i pytanie:

TBoundary = class(TPersistent)
private
  function GetLowValue: Integer;
  function GetHighValue: Integer;
published
  property Low: Integer read GetLowValue;
  property High: Integer read GetHighValue;
end;

TFoo = class(TObject)
private
  FNumber: Integer;
  FBoundary: TBoundary;
public
  constructor Create;
  destructor Destroy; override;
published
  property Bound: TBoundary read FBoundary;
end;

Chcę w funkcjach GetLowValue i GetHighValue odczytać granice typu wykorzystując systemowe funkcje Low i High:

{ w GetLowValue }
Result := Low(FNumber);

{ w GetHighValue }
Result := High(FNumber)

Pytanie brzmi: co mam wpisać w definicjach funkcji GetLowValue i GetHighValue tak, żeby uzyskać dostęp do pola z klasy głównej właśnie w tych funkcjach i wyciągnąć odpowiednie informacje?

0

Dobra chłopaki, zabrałem się do pracy i doszedłem do wniosku, że bez użycia wskaźników się nie obejdzie. Napisałem prostą klasę która rozwiązuje mój problem, ale tylko dla typu String:

type
  TInfo = class(TPersistent)
  private
    pValue: ^String;
    function GetValue: String;
  public
    constructor Create(Value: String);
    destructor Destroy; override;
  published
    property Value: String read GetValue;
  end;

type
  TFoo = class(TObject)
  private
    FValue: String;
    FInfo: TInfo;
  public
    constructor Create;
    destructor Destroy; override;

    procedure SetItem(Value: String);
  published
    property Info: TInfo read FInfo;
  end;

{ ... }

constructor TInfo.Create(Value: String);
begin
  inherited Create;
  pValue := @Value;
end;

destructor TInfo.Destroy;
begin
  inherited;
end;

function TInfo.GetValue: String;
begin
  Result := pValue^;
end;

constructor TFoo.Create;
begin
  inherited Create;
  FInfo := TInfo.Create(FValue);
end;

destructor TFoo.Destroy;
begin
  FreeAndNil(FInfo);
  inherited Destroy;
end;

procedure TFoo.SetItem(Value: String);
begin
  FValue := Value;
end;

Działa bez zarzutu. Niestety z typem tablicowym mam kłopoty... Jeżeli FValue jest typem tablicowym (własnym) to w metodzie GetValue (oczywiście wszystko jest zmienione dla tablic, a metoda ta zwraca liczbę) chcę zwrócić wielkość tablicy funkcją Length takim kodem:

function GetValue: Integer;
begin
  Result := Length(pValue^);
end;

kompilator wyrzuca bląd missing operator semicolon... Dlaczego?

Wtedy pole pValue jest typem wskaźnikowym na tablicę dynamiczną:

{ ... }
type
  TTable = array of String;

{ ... }

  pValue: ^TTable;

W jaki sposób mam to zrobić?

0

A tak w ogóle to w jaki sposób pobrać wskaźnik do tablicy? Można to w ogóle zrobić? Kombinowałem już z tym i kompilator wszystko kompiluje, ale jak przyjdzie linijka, w której chciałbym odczytać pośrednio informacje z tablicy przez wskaźnik wywala błąd...

Taki kod nie przejdzie:

{ ... }
type
  TArray = array of String;

var
  aFoo: TArray;
  pFoo: ^TArray;
  iLen: Integer;
begin
  SetLength(aFoo, 1);
  aFoo[0] := 'FooPtrApp'

  pFoo := @aFoo;

  iLen := Length(pFoo^); //ta linijka jest poprawna z punktu widzenia kompilatora, ale powoduje błąd podczas działania aplikacji...

  { ... }

Więc w jaki sposób mam to zrobić? Jak pobrać wskaźnik do tablicy i jak przez niego odczytywać dane skoro ten zapis nie pozwala na to?

Wzorowałem się na artykule z książki Adama Boducha - Delphi 7 Kompendium programisty. Dodam jeszcze, że nie korzystałem jeszcze ze wskaźników, nie lubię ich i nie bardzo wiem jak się nimi posługiwać. Bardzo proszę o pomoc w związku z nimi. Dziękuję.

0
vpiotr napisał(a)

Spróbuj High()

Funkcja ta zwraca indeks ostatniego pola w tablicy, nie długość jej samej. Choć nie ma to większego znaczenia, bo można by do High() dodać 1.

Nie wiem co robiłem źle, taki kod działa bez problemu:

type
  TArray = array of String;

var
  aFoo: TArray;
  PFoo: ^TArray;
  iLen: Integer;
begin
  SetLength(aFoo, 3);
  aFoo[0] := 'Field 0';
  aFoo[1] := 'Field 1';
  aFoo[2] := 'Field 2';

  PFoo := @aFoo;

  iLen := Length(PFoo^);

  WriteLn('Length: ', iLen);
  WriteLn('PFoo^[0]: ', PFoo^[0]);
  WriteLn('PFoo^[1]: ', PFoo^[1]);
  WriteLn('PFoo^[2]: ', PFoo^[2]);

  ReadLn;
end.

No dobra, kwestia pobierania adresu tablicy jest z głowy, ale mam znów kolejny problem... Mianowicie jeśli w jednym wskaźniku mam adres jakiejś zmiennej, w jaki sposób przepisać ten adres do drugiego wskaźnika tak, żeby wskazywały na tą samą zmienną? Taki kod nie skutkuje:

type
  TArray = array of String;

var
  aFoo: TArray;
  pFoo1: ^TArray;
  pFoo2: ^TArray
begin
  { ... }

  pFoo1 := @aFoo;
  pFoo2 := pFoo1; //tutaj nie wiem co zrobić, by skopiować adres z pFoo1 do pFoo2

  { ... }

Ma ktoś pojęcie jak się to robi? istnieje do tego jakaś procedura czy ręcznie trzeba jakiś trick zastosować?

0

Dziękuję wszystkim za pomoc, rozwiązałem problem. Może i nie przepisując adres tablicy z jednego do drugiego wskaźnika, ale dzięki przekazywaniu tablicy w parametrze poszczególnych konstruktorów (i tak jeśli parametr jest opatrzony klauzulą var, nie jest tworzona nowa instancja zmiennej tylko przekazywany jest tak jakby jej adres, czyli działa podobnie jak wskaźniki i całkowicie rozwiązuje to mój problem).

W każdym razie dziękuję jeszcze raz za pomoc. Pozdrawiam.

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