TCustomListBox i własna lista elementów

0

Tworzę kontrolkę dziedziczącą po TCustomListBox, w której właściwość Items musi być niewidoczna dla programisty;

Chciałbym, by kontrolka korzystała z własnej listy, która będzie zadeklarowana jako macierz rekordowa. Deklaracja:

type
  TExplorerContentObjectRec = packed record
    Caption: String;
    IconIndex: TExplorerObjectIconIndex;
  end;

  TExplorerContentArr = array of TExplorerContentObjectRec;

  TExplorerContent = class(TPersistent)
  private
    FContentArr: TExplorerContentArr;
  public
    constructor Create();
    destructor Destroy(); override;
  end;

  THDDExplorer = class(TCustomListBox)
  private
    FContent: TExplorerContent;
  end;

Podczas tworzenia obiektu klasy TExplorerContent zeruję rozmiar macierzy FContentArr przygotowując ją do pracy; Chcę, aby kontrolka nie korzystała z odziedziczonej właściwości Items, tylko z mojej macierzy do tworzenia widocznych itemów z jakimś tekstem; W kontrolce klasy np. TListBox tworzy się nowy item dzięki ListBox1.Items.Add('String') i to właśnie dodany łańcuch jest wyświetlany w kontrolce, u mnie chcę, by wyświetlaną nazwą był łańcuch z pola FContentArr[Index].Caption;

Sprawa jest o tyle denerwująca, że nie mogę (a przynajmniej nie wiem jak) kontrolce nakazać korzystania z mojej listy dowolnego typu, musi obsługiwać standardową Items: TStrings; Mogę dodawać nowe pozycje do listy jedynie dodając nowe linie do standardowej listy Items i one po dodaniu będą widoczne w kontrolce;

Co mnie interesuje - chcę móc tworzyć nowe pozycje w kontrolce ale nie uwidoczniać standardowej właściwości Items;

Mam dwa rozwiązania:

  • odziedziczyć właściwość Items z bazowej klasy TCustomListBox i ukryć ją by nie było do niej dostępu spoza modułu nowej kontrolki,
  • nie dziedziczyć tej właściwości (w ogóle z niej nie korzystać), a utworzyć swoją listę (lub macierz) i nakazać kontrolce korzystać z niej (co wiąże się oczywiście z ręcznym zaprogramowaniem dodawania/usuwania elementów do/z listy);

Najchętniej wykorzystałbym swoją macierz, ale co i gdzie mam zaprogramować by podczas dodawania nowego rekordu do macierzy (w kodzie) dodała się także nowa pozycja do listy (w dizajnerze)?


Przykład: TListBox

Kładę sobie kontrolkę tej klasy na formularz:

DesignerBeforeAdd.png

w oknie inspektora obiektów wybieram właściwość Items:

ObjectInspector.png

otwieram okienko do uzupełniania tej właściwości i dopisuję linię Pierwsza linia:

StringListEditor.png

zamykam okienko przyciskiem OK; Patrząc na formularz w oknie projektanta widać, że kontrolka posiada jedną pozycję o treści Pierwsza linia:

DesignerAfterAdd.png

Dzięki temu, że dodając linię do właściwości Items automatycznie na liście w kontrolce tworzy się nowa pozycja o tej treści;


Przykład: THDDExplorer (TCustomListBox)

W klasie np. THDDExplorer tworzę sobie macierz FContentArr: TExplorerContentArr jako prywatne pole klasy kontrolki; Nie korzystam w ogóle ze standardowej właściwości Items - nie dziedziczę jej; Teraz w konstruktorze tej klasy dodaję jedną pozycję:

type
  TExplorerContentObjectRec = packed record
    Caption: String;
    IconIndex: TExplorerObjectIconIndex;
  end;

  TExplorerContentArr: array of TExplorerContentObjectRec;

  THDDExplorer = class(TCustomListBox)
  private
    FContentArr: TExplorerContentArr;
  public
    constructor Create(AOwner: TComponent); override;
    {...}
  end;

implementation

constructor THDDExplorer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  SetLength(FContentArr, 1);
  FContentArr[0].Caption := 'Pierwsza linia';
  FContentArr[0].IconIndex := eoiiDesktop; //to jest nieistotne
end;

Dodałem jednen element do macierzy, ale kontrolka nie posiada jeszcze żadnego widocznego elementu na liście (na formularzu lista jest pusta); Jakimi instrukcjami i gdzie mam dodać nowy element do listy, by był widoczny w kontrolce na formularzu wykorzystując łańcuch spod FContentArr[0].Caption?


W najgorszym wypadku jeśli kontrolka musi obsługiwać standardową listę to po prostu odziedziczę właściwość Items i chcąc dodać nowe elementy do listy będę dodawał puste łańcuchy do niej (czyli HDDExplorer.Items.Add('')), równorzędnie uzupełniając moją macierz FContentArr, a podczas malowania itema będę wykorzystywał dane z tej macierzy, a nie z Items...

Zagmatwałem trochę, ale ciężko mi to opisać, a co dopiero zapytać Google... Przeglądnąłem wiele kursów tworzenia własnych kontrolek, ale nigdzie nie znalazłem odpowiedzi na moje pytanie; Jeśli ktoś nie zrozumie o co mi chodzi to postaram się opisać problem jeszcze dokładniej;

1
  1. Nie da się ukryć właściwości Items bo nie da się zmniejszyć poziomu dostępu właściwości a w klasie bazowej jest ona Public.
  2. Nie wiem czy wiesz ale do Items możesz "włożyć" dowolny obiekt - ListBox1.Items.AddObject('dupa', TObject.Create); i potem dostać się do niego ListBox1.Items.Objects[0]. Tylko trzeba pamiętać, że nie są one zwalniane po usunięciu itema/wyczyszczeniu listy.
  3. Jeśli ma to być tak jak chcesz to jedyne co Ci pozostaje to napisanie tego od podstaw. W przeciwnym razie to będzie jedynie sztukowanie a i tak ktoś, kto będzie używał Twojej kontrolki będzie mógł z kodu usuwać itemy
0
abrakadaber napisał(a)
  1. Nie da się ukryć właściwości Items bo nie da się zmniejszyć poziomu dostępu właściwości a w klasie bazowej jest ona Public.

Tego się właśnie obawiałem...

abrakadaber napisał(a)
  1. Nie wiem czy wiesz ale do Items możesz "włożyć" dowolny obiekt - ListBox1.Items.AddObject('dupa', TObject.Create); i potem dostać się do niego ListBox1.Items.Objects[0]. Tylko trzeba pamiętać, że nie są one zwalniane po usunięciu itema/wyczyszczeniu listy.

To akurat wiem, ale kiedyś miałem problem z tymi obiektami i zdarzeniem OnMeasureItem stąd wolałem zrobić to po swojemu;

abrakadaber napisał(a)

W przeciwnym razie to będzie jedynie sztukowanie a i tak ktoś, kto będzie używał Twojej kontrolki będzie mógł z kodu usuwać itemy

Tego właśnie chcę uniknąć; W ogóle nie powinno być możliwości grzebania w Items, no ale widać ominąć tego się nie da tak łatwo; Kontrolki od zera nie tworzyłem nigdy, teraz co prawda dziedziczę z TCustomListBox ale to i tak o dupę rozbić, bo większość właściwości i zdarzeń, które nie chcę, by były widoczne i żeby do nich był dostęp i tak będzie można modyfikować/wykorzystywać...

No nic, nie wszystko można dostać, dziękuję za odpowiedź :]

0

Mam jeszcze jedno pytanie odnośnie rodzica kontrolki; Kompilacja modułu kontrolki przebiega pomyślnie, jednak w momencie gdy kładę kontrolkę na formularzu dostaję komunikat Control '' has no parent window.;

Wspomnę jeszcze raz, że klasa nowej kontrolki dziedziczy po TCustomListBox, kontructor jest opatrzony klauzulą override, oraz w ciele konstruktora tworzę obiekt przez inherited Create(AOwner); Niestety nie udało mi się pokonać tego błędu i nie bardzo wiem co jest tego przyczyną; Kombinowałem z csDesigning in ComponentState, ale błąd dalej się pojawia, to samo dla if AOwner is TWinControl then Parent:=TWinControl(AOwner)...

Po utworzeniu kontrolki (położeniu jej na formularzu) potrzebuję automatycznie uzupełnić listę, czyli dodać kilka itemów i przez to jest problem; Jeśli usunę właściwość Items z klasy i wszystkie odwołania do niej - błąd się nie pojawia;

Wie ktoś dlaczego w treści błędu nie widnieje nazwa kontrolki oraz jak pozbyć się problemu z rodzicem dla nowego komponentu? Byłbym bardzo wdzięczny za wszelką pomoc;

0

pokaż kod (najlepiej cały) a na pewno konstruktor i jak masz to paint i itempaint czy podobne i nie masz tam gdzieś ustawianego fokusa? (SetFocus)

0
abrakadaber napisał(a)

pokaż kod (najlepiej cały)

Kod ma około 600 linii, ale chyba innego wyjścia nie ma;

abrakadaber napisał(a)

nie masz tam gdzieś ustawianego fokusa? (SetFocus)

Nie mam jeszcze w ogóle zaimplementowanego fokusa, więc pewnie to jest przyczyną; Tworząc kontrolkę najpierw zająłem się nowymi właściwościami oraz klasami, które będą przechowywać informacje dla tych właściwości i w jednej z nich będę manipulował zawartością listy; Jednak jeszcze nie mogę tego zrobić bo parent wariuje;

Pierwszy raz tworzę kontrolkę dziedzicząc z TCustomXXX dlatego nie wiem jeszcze co trzeba oprogramować; Wzoruję się na artykule Creating Custom Delphi Components dlatego postaram się jeszcze poczytać, ale dobrze, że piszesz o Focus bo pewnie brak tego pola i jego obsługi powoduje takie błędy;


Kod głównej klasy:

type
  THDDExplorer = class(TCustomListBox)
  private
    FIconsSet: TExplorerIconsSet;
    FColorsSet: TExplorerColorsSet;
    FPath: String;
    FFileName: TFileName;
    FShowPathControl: TCustomEdit;
  private
    function DriveType(const Letter: Char): Integer;
    function OpticalDiskAvailable(const Letter: Char): Boolean;
    function DesktopPath(): String;
    function GetContentObjectCaption(Index: Cardinal): String;
    function GetContentObjectType(Index: Cardinal): TExplorerObjectTypeIndex;
    procedure CompleteContentByPath(const Path, SelectedName: String);
  protected
    procedure SetIconsSet(Value: TExplorerIconsSet);
    procedure SetColorsSet(Value: TExplorerColorsSet);
    procedure SetPath(Value: String);
    procedure SetFileName(Value: TFileName);
    procedure SetShowPathControl(Value: TCustomEdit);
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property Align;
    property Anchors;
    property ItemHeight;
    property ShowHint;
    property TabOrder;
    property Visible;
    property PopupMenu;
  published
    property IconsSet: TExplorerIconsSet read FIconsSet write SetIconsSet;
    property ColorsSet: TExplorerColorsSet read FColorsSet write SetColorsSet;
    property Path: String read FPath write SetPath;
    property FileName: TFileName read FFileName write SetFileName;
    property ShowPathControl: TCustomEdit read FShowPathControl write SetShowPathControl;
  end;

i konstruktor:

constructor THDDExplorer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  
  FIconsSet := TExplorerIconsSet.Create();
  FColorsSet := TExplorerColorsSet.Create();
  FPath := '';
  CompleteContentByPath('', '');
  FFileName := '';
end;

Kod całego modułu tutaj; Kod niektórych procedur będzie jeszcze zmieniony, ale jakby ktoś zauważył coś niepokojącego to proszę o tym napisać;

Kontrolkę tą można bez problemu zainstalować u siebie na Delphi 7 - nie wymaga dodatkowych kontrolek czy modułów;

Pokombinuję jeszcze z tym fokusem bo coś mi się widzi, że w nim tkwi rozwiązanie problemu;


EDIT

Problem chyba rozwiązałem przez ustawienie rodzica jako AOwner i znilowanie go w destruktorze:

constructor THDDExplorer.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent := TWinControl(AOwner); // <- tu ustalam rodzica

  FIconsSet := TExplorerIconsSet.Create();
  FColorsSet := TExplorerColorsSet.Create();
  FPath := '';
  FFileName := '';
end;

destructor THDDExplorer.Destroy();
begin
  FIconsSet.Free();
  FColorsSet.Free();

  Parent := nil; // <- tu go niluję
  inherited Destroy();
end;

Dzięki temu nie dostaje błędu Control '' has no parent window. podczas "kładzenia" kontrolki na formularzu oraz dodawania itemów do listy, ale nie jestem pewien czy to całkowicie rozwiązuje problem - proszę mnie oświecić jeśli ktoś wie to na 100%;

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