• Kurs MUI - część 8

20.02.2005 14:43, autor artykułu: Grzegorz Kraszewski
odsłon: 2756, powiększ obrazki, wersja do wydruku,

Listy i listviewy 1

Te bardzo użyteczne gadżety często sprawiają kłopoty początkującym MUI-owcom, gdyż jak to zwykle bywa, coś co jest bardzo wszechstronne i konfigurowalne, z reguły bywa też trudne do opanowania. Ale krok po kroku można zapanować nad listami i z powodzeniem używać ich w swoich programach do maksimum wykorzystując ukryte możliwości.

Zacznijmy od wzajemnych powiązań między obiektami klasy List i Listview. Obiekt List odpowiada za przechowywanie informacji i ich wyświetlenie, natomiast Listview dodaje do tego suwaki i ich obsługę, oraz obsługę myszy (strzałki, zaznaczanie, drag&drop). Dlatego bardzo rzadko używa się samodzielnych obiektów List. Najczęściej są one podłączone do obiektów Listview. Aby nie nudzić dłużej teoretycznie proponuję, żebyśmy przeszli do najprostszego przykładu listy, jakim jest

Lista tekstowa

MUI Kolejnymi pozycjami takiej listy są po prostu łańcuchy tekstowe. Przykład takiej listy to program Przyklad9a. Jak widać w oknie znajduje się obiekt Listview do którego w momencie tworzenia podłączony jest (przy pomocy taga MUIA_Listview_List) obiekt List. Należy zwrócić szczególną uwagę na to, aby przy tworzeniu listy nie podawać atrybutów klasy List dla obiektu Listview i odwrotnie! Później (ale po stworzeniu GUI) wszystkie operacje można wykonywać na listviewie, odpowiednie metody i atrybuty zostaną przekazane do obiektu List. Obiektowi temu należy przypisać ramkę (obiektowi List, a nie Listview, to często spotykany błąd!). Listom tylko do odczytu (takim w które użytkownik nie może wpisywać elementów) dajemy ramkę MUIV_Frame_ReadList, pozostałym ramkę MUIV_Frame_InputList. Jest to oczywiście sprawa estetyki i pewnej logiki wyglądu GUI, lista będzie równie dobrze działać bez ramki, zalecam jednak trzymanie się ugruntowanych zwyczajów. Naturalnie w tekstach umieszczanych na liście można skorzystać z wszelkich udogodnień MUI, a więc stylów, kolorów i obrazków w tekście. Przykładem jest pogrubiony napis "Amiga" w przykładzie 9a.

Słów kilka o wstawianiu elementów do listy. Elementy można umieścić na liście od razu przy jej tworzeniu podając adres tablicy elementów z tagiem MUIA_List_SourceArray. Tablica zawiera adresy elementów i na końcu wartość 0. Do wstawiania w czasie wykonywania programu służą dwie metody: MUIM_List_Insert i MUIM_List_InsertSingle. Zacznę od tej drugiej, jak łatwo się domyślić służy ona do wstawiania pojedynczego elementu. Parametry tej metody to adres elementu do wstawienia oraz miejsce wstawiania. W wypadku tekstów możemy podać bezpośrenio tekst

DoMethod (listview, MUIM_List_InsertSingle, "piąty element", 3);


gdyż kompilator i tak wstawi tam adres. Oprócz bezpośredniego podania pozycji wstawiania MUI oferuje nam kilka użytecznych możliwości:

MUIV_List_Insert_Top - wstawia element na początek,
MUIV_List_Insert_Active - wstawia element przed elementem na którym znajduje
się kursor,
MUIV_List_Insert_Bottom - wstawia element na końcu,
MUIV_List_Insert_Sorted - jeżeli lista jest automatycznie sortowana, element
wstawiany jest w wynikające z porządku sortowania miejsce.


Do hurtowego wstawiania elementów lepiej nadaje się metoda MUIM_List_Insert. Oczekuje ona jako parametru tablicy adresów elementów do wstawienia, oraz ilości tych elementów. Jeżeli jako ilość podamy -1, elementy z tablicy będą wstawiane aż do napotkania adresu 0. Należy zadbać o to, aby w przypadku podania -1 jako ilości elementów, tablica na pewno kończyła się adresem 0. Oto prosty przykład. Zwracam uwagę na to, że wbrew pozorom tablica 'teksty' zawiera adresy napisów, a nie same napisy. Podobnie metodzie przekazywany jest adres tablicy, a nie sama tablica.

char *teksty[] = {"tralala", "barabara", "fikumiku", NULL};

DoMethod (listview, MUIM_List_InsertSingle, teksty, -1,
 MUIV_List_Insert_Active);


Warto zauważyć, że przy użyciu MUIA_List_SourceArray elementy z tablicy są wstawiane w takim porządku, w jakim występują. Przy wykorzystaniu tablicy w metodzie MUIM_List_Insert jest odwrotnie - elementy wstawiane są od końca.

Listy dowolnych elementów

Oczywiście lista napisów jest przypadkiem najczęściej stosowanym, ale nie jedynym. Wyobraźmy sobie następujacą strukturę danych:

struct Towar
 {
  char *nazwa;
  long ile_na_skladzie;
 };


Jeżeli chcielibyśmy wyświetlić listę towarów z podaniem ilości każdego z nich w magazynie, trzebaby każdą strukturę zamienić na napis (np. za pomocą funkcji sprintf()), a w przypadku odczytu tego co użytkownik wybrał dokonać odwrotnej konwersji. Jest to nieefektywne i powoduje większe zużycie pamięci (liczba w postaci tekstu zajmuje więcej miejsca). Możemy tego uniknąć, korzystając z faktu, że lista MUI jest przygotowana na przechowywanie dowolnych struktur. W tym celu cztery kluczowe funkcje przetwarzania elementów są wykonane jako hooki, które sami możemy zdefiniować. Poniżej omówię je odnosząc się do naszego przykładu z (bez skojarzeń proszę...) towarem.

MUI MUIA_List_ConstructHook - ta funkcja wywoływana jest w momencie dodawania elementu do listy. Jednym z jej parametrów (umieszczonym w rejestrze A1 procesora) jest adres struktury elementu do wstawienia, np. ten który podajemy przy wywołaniu MUIM_List_InsertSingle. Natomiast ostatecznie do listy dołączany jest wskaźnik który przekazujemy przy wyjściu z funkcji. Z reguły zadaniem konstruktora jest skopiowanie przekazywanej struktury (która może być przecież zmienną lokalną). W przypadku naszej struktury Towar dochodzi jeszcze jedno zadanie - skopiowanie tekstu wskazywanego przez pole 'nazwa', ten tekst może być również zmienną lokalną. W tym momencie nasuwa się pytanie - w jaki sposób zarezerwować pamięć na tworzone kopie? Można oczywiście za każdym razem wywoływać AllocMem (), ale w przypadku małych elementów prowadzi to do fragmentacji pamięci. Warto więc skorzystać z pul pamięci (memory pools) i funkcji AllocPooled (). I tu kolejne udogodnienie. Taka pula jest automatycznie tworzona przez MUI i jej adres dostajemy w rejestrze A2. Oto więc konstruktor dla naszego Towaru

long TowarConstructor (struct Towar *towar reg(a1), APTR mempool reg(a2))
 {
  struct Towar *t_copy;
  char *n_copy;

  if (t_copy = AllocPooled (mempool, sizeof (struct Towar)))
   {
    if (n_copy = AllocPooled (mempool, strlen (towar->nazwa) + 1))
     {
      strcpy (n_copy, towar->nazwa);
      t_copy->nazwa = n_copy;
      t_copy->ile_na_skladzie = towar->ile_na_skladzie;
      return (long)t_copy;
     }
    else FreePooled (mempool, t_copy, sizeof (struct Towar));
   }
  return NULL;
 }


Jak widać najpierw tworzę kopię struktury Towar, następnie kopiuję nazwę i adres tej nowej nazwy wstawiam do kopii Towaru. Wartość pola 'ile_na_skladzie' kopiuję bezpośrednio. Dzięki temu oryginalna struktura Towar może zostać zniszczona (a więc może być zmienną lokalną). W przypadku niemożności zarezerwowania pamięci na kopie konstruktor zwraca zero. W tej sytuacji do listy nic nie jest dodawane.

MUIA_List_DestructHook - funkcja odpowiedzialna za likwidację obiektu wywoływana w momencie jego usuwania z listy, lub w momencie likwidacji samej listy. Do jej obowiązków należy zwolnienie zasobów zajętych przez konstruktor. W rejestrach procesora otrzymujemy te same parametry - zwalniany obiekt w A1 i pulę pamięci w A2. Oto przykład likwidacji Towaru

long TowarDestructor (struct Towar *towar reg(a1), APTR mempool reg(a2))
 {
  if (towar->nazwa) FreePooled (mempool, towar->nazwa, strlen (towar->nazwa)
   + 1);
  if (towar) FreePooled (mempool, towar, sizeof (struct Towar));
  return 0;
 }


Ważna jest kolejność zwalniania pamięci. Przy odwróceniu kolejności pokazanej powyżej adres napisu byłby pobierany z pamięci wolnej(!), a więc mogłby już być zmieniony, a to skończyłoby się Guru.

MUIA_List_DisplayHook - ta funkcja odpowiada za przetworzenie struktury danych na to, co widzimy na ekranie. W rejestrze A1 otrzymujemy tu jak zwykle adres naszej struktury danych. Natomiast w A2 odkryjemy adres tablicy, która pomieści adresy napisów przeznaczonych do wyświetlenia. Naszym obowiązkiem jest wypełnić tę tablicę. Dlaczego tablica? Otóż lista w MUI może być wielokolumnowa i każda pozycja w tablicy tekstów odpowiada jednej kolumnie. O tym jednak później, a na razie mamy jedną kolumnę, więc w A2 znajduje się wskaźnik na tablicę jednoelementową. DisplayHook daje nam też możliwość dodatkowej obróbki danych przed ich pokazaniem. W naszym przykładzie ułatwimy pracę magazynierowi wyróżniając towary, których ilość na składzie wynosi zero (trzeba natychmiast zamówić nową partię). Oto więc hook wyświetlający:

long TowarDisplayer (struct Towar *towar reg(a1), char **tekst reg(a2))
 {
  static char textl[64];
  
  if (towar->ile_na_skladzie > 0)
    sprintf (textl, "%s %ld", towar->nazwa, towar->ile_na_skladzie);
  else
    sprintf (textl, "33b%s %ld33n", towar->nazwa, towar->ile_na_skladzie);
   
  *tekst = textl;
  return 0;
 }


Jak widać bufor, do którego wpisujemy gotowy tekst, musi być zmienną statyczną (albo globalną) dlatego, że samo umieszczanie tekstu na ekranie odbywa się poza naszą funkcją.

MUIA_List_CompareHook - jest funkcją porównującą dwa elementy z tablicy. Jest ona używana do sortowania elementów, jeżeli będziemy chcieli, żeby lista była automatycznie sortowana. Funkcja musi porównać dwa elementy i dać wynik -1, 0, lub 1 w zależności od tego który element ma być wyżej na liście (zero, jeżeli elementy są równe). W naszym przykładzie sortowanie będzie się odbywało według nazw towarów, więc funkcja będzie dość prosta:

long TowarComparer (struct Towar *towar1 reg(a1), struct Towar *towar2
 reg(a2))
 {
  return (Stricmp (towar1->nazwa, towar2->nazwa));
 }


Dzięki użyciu funkcji porównującej z utlilty.library towary będą posrtowane alfabetycznie z uwzględnieniem zainstalowanych w systemie lokali.

Listy wielokolumnowe

MUI Po spojrzeniu na naszą listę towarów można dojść do wniosku, że aż się prosi ustawienie w jednej kolumnie nazw, a w drugiej ilości w magazynie. Lista zdecydowanie zyska na czytelności. Trzeba więc zrobić listę wielokolumnową. Postać listy definiujemy atrybutem MUIA_List_Format. Jego domyślna wartość to "", co oznacza zwykłą listę z jedną kolumną. Kolumny oddzielamy od siebie przecinkami, każdy przecinek oznacza przejście do następnej kolumny, więc kolumn jest zawsze o jedną więcej od przecinków. Przykładowo listę trójkolumnową zdefiniujemy następująco:

MUIA_List_Format, (long)",,",


Między przecinkami możemy umieścić szereg słów kluczowych precyzujących format kolumny i uatrakcyjniających wygląd listy. A oto one:

DELTA - określa odległość w pikselach między daną a następną kolumną. Jak łatwo zauważyć nie ma sensu podawanie tego parametru po ostatnim przecinku. Wartość domyślna to 4 piksele i nie warto jej zmieniać bez wyraźnego powodu.

PREPARSE - łańcuch tekstowy dodawany przed każdy tekst drukowany w tej kolumnie. Typowe zastosowanie polega tu na użyciu kodów formatujących MUI, na przykład "33r" wyrówna nam kolumnę do prawej, a "332" wyróżni kolumnę białym kolorem liter.

BAR - między tą a następną kolumną zostanie narysowana pionowa linia w stylu 3D. Tego parametru również nie ma sensu podawać w ostatniej kolumnie.

WEIGHT - proporcja miejsca przeznaczanego na kolumny. Działanie tego parametru jest identyczne jak opisanego kilka odcinków wcześniej atrybutu MUIA_HorizWeight klasy Area. Standardowo WEIGHT dla każdej kolumny wynosi 100, a więc każda jest tak samo szeroka. Jeżeli jednej z kolumn przypiszemy WEIGHT=200, to będzie dwa razy szersza od pozostałych.

MAXWIDTH - określa maksymalną szerokość kolumny w procentach szerokości całej listy. Podanie -1 oznacza, że kolumna może być maksymalnie tak szeroka jak najdłuższy umieszczony w niej napis. Jest to jednocześnie wartość domyślna tego parametru.

MINWIDTH - podobnie jak wyżej, ale tym razem dla minimalnej szerokości kolumny. -1 oznacza, że kolumna musi być co najmniej tak szeroka jak najdłuższy w niej napis. Również tu -1 jest wartością domyślną.

COL - tu można podać numer kolumny. Ten z pozoru bezsensowny parametr nabierze w Waszych oczach wartości, jeżeli powiem, że atrybut MUIA_List_Format może być zmieniany w czasie działania programu w już istniejącej liście. W ten sposób można łatwo poprzestawiać kolumny bez przerabiania DisplayHooka.

Oto przykładowy format listy zaczerpnięty z programu Przykład9c

MUIA_List_Format, "MAXWIDTH=100 BAR,PREPARSE33r",


Da nam on listę dwukolumnową, o nieograniczonej szerokości pierwszej kolumny (druga ma MAXWIDTH równe domyślnie -1, zatem będzie tylko tak szeroka, jak najszerszy z tekstów), między kolumnami będzie pionowa kreska, zaś druga kolumna będzie wyrównana do prawej. Oczywiście na dwukolumnową listę musi być przygotowany DisplayHook, do wypełnienia będziemy mieli tym razem dwa pola w tablicy tekstów. Oto kod

long TowarDisplayer (struct Towar *towar reg(a1), char **teksty reg(a2))
 {
  static char textl[64], ilosc[12];

  if (towar->ile_na_skladzie > 0)
   {
    teksty[0] = towar->nazwa;
    sprintf (ilosc, "%ld", towar->ile_na_skladzie);
   }
  else
   {
    sprintf (textl, "33b%s33n", towar->nazwa);
    teksty[0] = textl;
    sprintf (ilosc, "33b%ld33n", towar->ile_na_skladzie);
   }
  teksty[1] = ilosc;
  return 0;
 }


Proponuję zwrócić uwagę na bezpośrednie przypisanie wskaźnika na nazwę towaru (teksty[0] = towar->nazwa). Dzięki temu, że konstruktor kopiuje nazwy towarów mam pewność, że napis istnieje przez cały czas istnienia listy i spokojnie mogę skopiować jedynie wskaźnik nie posługując się statycznym buforem 'textl'. A w przypadku zerowej ilości towaru sprintf() służy mi tylko do wstawienia w tekst sekwencji formatujących. Ilość towaru drukuję również sprintf()-em w klasyczny sposób. Zwracam uwagę na to, że nie jest potrzebny (a wręcz byłby szkodliwy) znak końca linii.

Na tym zakończę ten odcinek, co nie znaczy że pożegnamy się z listami i listviewami. Temat ten jest na tyle obszerny i ważny, że poświęcę mu również następną część. W której to części pokażę dodatkowe możliwości list, a także jak można obsłużyć listą zbiory danych nie mieszczące się w pamięci komputera. Dodatek - tutaj.

    
dodaj komentarz
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem