9. Listy… Do nikogo, czyli podejmujemy trudne wybory

W moim poprzednim odcinku udało nam się napisać nasz pierwszy formularz. Składał się on z kilku pól tekstowych i przycisków. W praktyce jednak napotykamy znacznie więcej kontrolek. Najważniejszą z nich są listy. Listy pozwalają nam zaznaczyć jedną (lub więcej) opcji z przedstawionej kolekcji. Raz pozwolą nam na wywoływanie dalszych opcji, raz jedynie poproszą o wskazanie jakiegoś ustawienia… Pośrednio listę posiadał nasz pierwszy program – mówię tu o menu pozwalającym się witać i/lub żegnać. Menu są bardzo użyteczne, mają jednak też kilka ograniczeń. Przede wszystkim każda opcja w nich jest bezpośrednio powiązana z jakimś zachowaniem. Czasem to bardzo praktyczne podejście, ale bywa, że wcale go nie potrzebujemy. A to nie wspominając nawet o fakcie, że menu nie mogą być polem formularza. Oczywiście w API Eltena znajdziemy funkcje pozwalające na tworzenie list, które do formularzy jak najbardziej mogą należeć… Ale nie muszą.

Czy na pewno?

Najprostszym przypadkiem wyboru jest ten, w którym chcemy się upewnić, czy użytkownik na pewno chce wykonać daną akcję, np. usunąć jakiś plik, porzucić pisany tekst, zmienić ustawienie. Takie okno zawiera dwie opcje: “Tak” oraz “Nie”. Do jego stworzenia służy funkcja “confirm”.

confirm("Do you want to hear a fairy tale?") {
  alert("I don't know any fairy tales.")
}

Tylko tyle, mamy ładne okno z opcjami “Tak” oraz “Nie”. Często jednak nasze wybory są znacznie trudniejsze. Jak dodać na przykład opcję “Nie wiem”?

Trudne wybory

case selector(["No", "Yes", "I don't know"], "Do you want to hear a fairy tale?")
  when 0
    alert("If not, then not!")
  when 1
    alert("I don't know any fairy tales.")
  when 2
    alert("Get over it, I can't decide for you!")
end

Funkcja “selector” przyjmuje przynajmniej dwa parametry:

  • Pierwszym jest lista opcji,
  • Drugim nagłówek.

Tylko tyle i aż tyle. Funkcja zwraca numer wybranej opcji, numerowany od zera.

Uciekł nam escape [1]

Nasze pierwsze okno miało pewną utraconą cechę – pozwalało na zamykanie go przez wciśnięcie escape. Oczywiście funkcja “selector” także na to pozwoli.

case selector(["No", "Yes", "I don't know"], "Do you want to hear a fairy tale?", 0, 0)
  when 0
    alert("If not, then not!")
  when 1
    alert("I don't know any fairy tales.")
  when 2
    alert("Get over it, I can't decide for you!")
end

Dodaliśmy do funkcji “selector” dwa parametry, dwa zera: * Trzeci to numer (od zera) domyślnie zaznaczonej opcji, tu opcja pierwsza, domyślnie, * Czwarty to ten, o który nam chodzi: wartość, która zostanie zwrócona po wciśnięciu escape. Parametrem zwracanym może być numer opcji, ale także jakaś wartość inna, na przykład “-1”, by odróżnić wciśnięcie escape od wyboru.

Wszystko pięknie ładnie, ale gdzie te formularze?

Prosta ankieta

form = Form.new([
  edt_name = EditBox.new("Full name"),
  lst_gender = ListBox.new(["Female", "Male"], "Gender"),
  btn_accept = Button.new("Continue"),
  btn_cancel = Button.new("Cancel")
], 0, false, true)
btn_cancel.on(:press) {form.resume}
btn_accept.on(:press)  {
  alert("Your name is #{edt_name.text}, you are a #{(lst_gender.index==0)?("female"):("male")}.")
}
form.cancel_button = btn_cancel
form.accept_button = btn_accept
form.wait

Tworzymy tu prosty formularz pytający nas o imię i nazwisko oraz płeć. W pierwszym wypadku stosujemy pole tekstowe. Rozwiązaniem całkiem niepraktycznym byłoby przecież tworzenie listy wszystkich zarejestrowanych w Polsce (czy innym kraju) imion i nazwisk i proszenie o wybranie elementu z nich. W wypadku płci sprawa wygląda zupełnie inaczej, opcje są tylko dwie, a więc prościej prosić użytkownika o wskazanie jednej z nich. Choć z obecnymi tendencjami to kto wie, czy i to pole kiedyś nie będzie wymagało typu tekstowego. 😀

Listy reprezentowane są przez klasę “ListBox”. W konstruktorze pierwszym parametrem jest tablica z nazwami opcji, drugim zaś etykieta.

Nieco bardziej zaskoczyć może konstrukcja w funkcji “alert”, konkretnie drugi nawias klamrowy. Jest to jeden z wielu tzw. “onelinerów” (“jednoliniowców”). To charakterystyczne dla języków programowania zwroty, znacznie ułatwiające wykonywanie pewnych rutynowych czynności. Składnia

(condition)?(return_on_true):(return_on_false)

oznacza, że jeśli warunek “condition” zostanie spełniony, wyrażenie zwróci “return_on_true”, w przeciwnym wypadku “return_on_false”. W naszym wypadku automatycznie dostosowujemy informację o płci do wybranego indeksu naszej listy: “index” to po prostu numer zaznaczonej opcji, podobnie jak w wypadku tablic i właściwie całego informatycznego świata liczony od zera.

Ale ja jestem obojniakiem!!!

Czasem się zdarzy, że zechcemy stworzyć listę, w której użytkownik może zaznaczyć kilka opcji. Z pomocą przychodzą nam… tak tak, zgadliście, to flagi. Podobnie jak swój zestaw flag mają pola tekstowe, mają je również listy. O ile flagi pól tekstowych były definiowane w klasie “EditBox::Flags”, dla list znajdą się w “ListBox::Flags”. Wybór jest dość pokaźny, a ta, o którą nam chodzi, nosi nazwę “ListBox::Flags::MultiSelection”.

lectures=["Maths", "English", "Physics", "Computer science", "History"]
form = Form.new([
  lst_lectures = ListBox.new(lectures, "Select lectures you'd like to attend", 0, ListBox::Flags::MultiSelection),
  btn_register = Button.new("Register me"),
  btn_cancel = Button.new("Cancel")
], 0, false, true)
btn_cancel.on(:press) {form.resume}
btn_register.on(:press) {
  lecs=[]
  for i in 0...lectures.size
    lecs.push(lectures[i]) if lst_lectures.selected[i]
  end
  alert("I would try to register you for the following lectures: #{lecs.join(", ")}, if not for the developer who didn't bother to write a registration procedure. Very sad.")
}
form.cancel_button=btn_cancel
form.accept_button=btn_register
form.wait

Program prosi studentów o wybranie listy zajęć, na które chcieliby się zarejestrować, po czym informuje interesantów, na jakie przedmioty planowali się zapisać. Niestety, następnie słyszymy, że program nie potrafi wykonać tej czynności, bo programista nie miał ochoty jej napisać, za co autorowi kodu należałoby się zdecydowanie zapoznanie bliższe z dywanikiem u prezesa. Widzimy tu proste zastosowanie pętli “for”, pozwalające nam na szybkie stworzenie tablicy z nazwami przedmiotów, na które studenci pragną się zarejestrować. Funkcja “join” łączy tekst w tablicach, jako parametr przyjmując separator. Działa to tak:

["Ala", "ma", "kota"].join(" ")
:   #=> "Ala ma kota"
["Ala", "ma", "kota"].join(", ")
:   #=> "Ala, ma, kota"
["Ala", "ma", "kota"].join("")
:   #=> "Alamakota"

Atrybut “selected” naszej listy to tablica, która dla każdego indeksu – numeru opcji od zera – zawiera “true”, jeśli opcja jest zaznaczona, a “false” w przeciwnym wypadku. Można też zauważyć, że w konstruktorze naszej listy zestaw flag był dopiero czwartym parametrem. Co jest trzecim? Co oznacza to tajemnicze zero? To nic innego jak początkowy indeks, innymi słowy numer opcji, na której domyślnie znajdzie się kursor.

Gdy listy zostaną wydziedziczone

Listy często są częściami formularza, ale wcale być nie muszą. W samym Eltenie widzimy wiele przykładów, gdy lista jest jedynym elementem prezentowanym użytkownikowi. Oczywiście nic nie stoi na przeszkodzie, by nasz formularz miał tylko jedno pole, ale prócz dodatkowych linijek kodu i większego obciążenia pamięci takie zachowanie ma pewne niepożądane efekty: * Osoby, które używają tematu dźwiękowego, przy otwieraniu się naszej listy usłyszą całkowicie zbędny dźwięk formularza, * W dodatku klawisz tab będzie miał zachowanie, na którym wcale nam nie zależy.

lectures=["Maths", "English", "Physics", "Computer science", "History"]
lst_lecture = ListBox.new(lectures, "Select a lecture you'd like to attend")
lst_lecture.on(:key_escape) {lst_lecture.resume}
lst_lecture.on(:select) {
  alert("I would try to register you for the #{lectures[lst_lecture.index]}, if not for the developer who didn't bother to write a registration procedure. Very sad.")
}
lst_lecture.wait

Praca domowa

Rozbudujmy nasz poprzedni kalkulator o ładną listę: pola na dwie liczby, lista z wyborem operacji, przycisk do wykonania obliczeń i zamykania programu.

Przypisy

  1. Jaka szkoda, że w polskim ta gra słów nie zwraca niczyjej uwagi…

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *