Tulvajarvi Saukkola


NSPopupButton - alasvetovalikko

Konffaus käyttöliittymäeditorissa

Interface Builderissa raahaa ikkunaan NSPopupButton

Tietosisältö otetaan taulukosta eli Array Controllerista. Raahaa sellainen työkalupakista xib-näkymän vasempaan laitaan, ja konffaa se:

-  avaa Assistant editor ja ctrl-raahaa siihen viiva vasemman reunan Array Controller -kuvasta, tee sinne Outlet

-  Identity Inspector: Label eli anna sille jokin kutsumanimi (esim. arrayHeimot), ei tarvitse olla sama kuin muuttujan nimi

-  Attributes Inspector: Laita oikea Class Name, esimerkiksi Pari (katso lopussa). Avaimia ei tarvitse lisätä

Seuraavaksi konffaa NSPopupButton:

-   avaa Assistant editor ja ctrl-raahaa siihen viiva näytön PopupButtonista, tee sinne Outlet, ja anna osuva nimi, esimerkiksi popupbtnHeimot

-   Identity Inspector: Label varmaankin voidaan antaa mutta en ole kokeillut

-   Attributes Inspector: ei mitään

-   Bindings Inspector / Content
    Bind to: edellä annettu Array Label, eli esim. arrayHeimot
    Controller Key: arrangedObjects
    Model Key Path: jätä tyhjäksi
   
-   Binding Inspector / Content Objects: ei saa olla ruksattuna 'Bind to'

-   Bindings Inspector / Content Values
    Bind to: edellä annettu Array Label, eli esim. arrayHeimot
    Controller Key: arrangedObjects
    Model Key Path: se avain (Key) Array:sta mikä näytetään popupButtonissa (Tieto jos Pari)

-   Bindings Inspector / Selected Index
    Bind to: edellä annettu Array Label, eli esim. arrayHeimot
    Controller Key: selectionIndex
    Model Key Path: jätä tyhjäksi

Array Controlleriin tiedot viedään objektitaulukkona, objektina oltavana luokka (Dictionary ei kelpaa), esimerkiksi class Pari (ks. alempana)

Koodausesimerkkejä  (Swift 4.2)

Jos NSArrayController ei ole käytössä, niin tässä hyödyllisimmät perusfunktiot. Jäljempää löytyy lisää soveltuvia.
Älä oleta missään, että jotain on valittuna alasvetovalikossa tai taulukossa, vaan aina tarkista asia.

popupKolme.addItems(withTitles: ["yks", "kaks", "kol"])   // luodaan arvot valikkoon
popupKolme.addItem(withTitle: "kuus")
let vali = popupKolme.indexOfSelectedItem   // mikä on valittuna, vali=-1 jos ei ole valintaa
let zz = popupKolme.itemTitle(at: vali)    // haetaan sen arvo
   // jos vali==-1, sovellus kaatuu;
   // zz = "" jos ei löydy tällä indeksillä
popupKolme.selectItem(withTitle: "")    // poistaa valinnan
popupKolme.selectItem(withTitle: "nel")  // jos "nel" ei ole, poistaa valinnan
// valitse tyhjä paikka valikosta:
let ii = popupSisa.indexOfItem(withTitle: "")  // tarkista ettei ole -1
popupSisa.selectItem(at: ii)

let xx = popupKolme.indexOfItem(withTitle: "kaks")   // nimeltään tiedetyn vaihtoehdon sijainti
if xx == -1 { // ei löydy  }
popupKolme.selectItem(at: xx)  //  jos xx==-1, sovellus kaatuu

popupSisa.removeAllItems()  // tyhjennetään koko taulukko


Näissä seuraavissa NSArrayController on käytössä.
Älä oleta missään, että jotain on valittuna alasvetovalikossa tai taulukossa, vaan aina tarkista asia.
Seuraavassa array-alkuinen muuttuja on tyyppiä NSArrayController.

Edellä on ehdotettu, että alasvetovalikon yhteydessä käytettäisiin luokkaa Pari, jossa on Nimi ja id. Se on tällainen. Sen tietosisältö on Dictionarynä.

import Cocoa

class Pari: NSObject
{
    @objc var idit: Int = -1
    @objc var Tieto: String = ""
   
    @objc func Nimi() -> String
    {
        return Tieto
    }
   
    @objc func idPari() -> Int
    {
        return idit
    }
   
    @objc init(idi: Int, nimi: String)
    {
        idit = idi
        Tieto = nimi
    }
}

Alasvetovalikkoa varten tiedot kerätään seuraavasti. Tämä esimerkki käyttää tietokantaa, mutta tiedot voi toki viedä NSArrayControlleriin vastaavalla tavalla vakiotaulukostakin. Tämän kutsumisen jälkeen meillä on tiedot paikoillaan alasvetovalikossa:

// tietokantafunktiot: FMDB 2.7
// kysz = SQL-kysely, jossa kysyttävä Int ja String, esim.
// let kysz = "SELECT idHei AS eka, Nimi AS tokaz FROM Heimo ORDER BY tokaz"
// eka = true, jos valikon eka kenttä jätetään tyhjäksi
// outputtina true tai false (jos virheitä)
// c = paritListaan(kysz, taulu: arrayHeimot, db: db!, eka: true)
func paritListaan(kysz: String, taulu: NSArrayController, db: FMDatabase, eka: Bool) -> Bool
{
   if kysz.isEmpty
   {
      print("M74-01: input virhe, kysz on tyhjä")
      return false
   }
       
   // tyhjennetään ensin taulu
   taulu.content = nil

   do
   {
      let rs = try db.executeQuery(kysz, values: nil)
       
      if eka==false    // Laitetaan ensimmäiseksi alkioksi valikkoon tyhjä
      {
         taulu.addObject(Pari(idi: -1, nimi: "")
      }
       
      while rs.next()
      {
         let idi = Int(rs.int(forColumn: "eka"))  // jos kannassa NULL, saadaan 0
         let z = rs.string(forColumn: "tokaz")
         let tokaz = (z == nil) ? "" : z!
         taulu.addObject(Pari(idi: idi, nimi: tokaz)
      }
   }
   catch
   {
      print("M74-02 \(error.localizedDescription)")
      return false
   }
       
   taulu.setSelectionIndex(0)    // alasvetovalikon ylin kenttä valituksi
       
   return true
}


 
Kun käytössä on NSArrayController, käytä sitä mahdollisimman moneen, äläkä suoraan popupbuttoniin, ellei ole pakko.

Ylimmän heimon asettaminen näkyviin:
arrayHeimot.setSelectionIndex(0)   // ei kaada sovellusta vaikka taulukko tyhjä

arrayHeimot.setSelectionIndex(-1)  // ei toimi terveesti:
// Ei kaada sovellusta, näytöllä eka on ruksattuna, mutta  arrayHeimot.selectedObjects  jää tyhjäksi

Tietynnimisen heimon valitseminen (eli asettaminen näkyviin):
let ik = popupHeimo.indexOfItem(withTitle: "Fabaceae") // -1 ei oo
if ik == -1
{
    print("Arvoa 'Fabaceae' ei löydy")
    return false
}
arrayHeimot.setSelectionIndex(ik)


Alasvetovalikosta valitun heimon noutaminen:
let tempx = arrayHeimot.selectedObjects as NSArray
if tempx.count == 0 
{
    print("Ei ole mitään valittuna (ohjelmointivirhe tai arvo valitsematta)")
    return false
}
let hhh: Pari = tempx.object(at: 0) as! Pari
let iHeimo = hhh.idPari()     // Int
let Heimonimi = hhh.Nimi()   // String


Tarkastetaan onko mitään heimoa valittu:
if arrayHeimot.selectionIndex==Foundation.NSNotFound
{
   // mitään ei ole valittuna
}


Tyhjennetään valikko
arrayHeimot.content = nil  // tyhjentää vain sisällön
// tyhjentää vain kontrollin, eikä sitä enää voi valita:
popupYksi.removeAllItems()
 

Jos edellä ei ole mitään heimoa valittuna, niin tuloksena on tyhjä taulukko tempx. Ilman tarkistuksia seuraava rivi kaataa sovelluksen.

Jos jostain syystä haluat poistaa kaikki valinnat, niin se tapahtuu näin:
arrayHeimot.setSelectionIndexes(NSIndexSet() as IndexSet)

Tyhjennetään valikko
arrayHeimot.content = nil

Alasvetovalikko piilotetaan kokonaan:
popupbtnHeimo.isHidden = true

Valikon elementtien lukumäärä:
let kpl = (arrayHeimot.arrangedObjects as! [Pari]).count


Valikon arvon valitsemisen pyydystys, tapa 1: tehdään käyttöliittymäeditorissa alasvetovalikolle Action. Tämä on suositeltavin tapa asian hoitamiseen. Kun käyttäjä valitsee arvon valikosta (saman arvon tai vaihtaa arvon), niin Action-moduuli käynnistyy.  Huomattava on, että jos ohjelmallisesti asettaa näkyville haluamansa valikon arvon, niin Action-moduuli ei käynnisty. Sen sijaan seuraavassa kuvattu tarkkailijan Action käynnistyy.

Valikon arvon valitsemisen pyydystys, tapa 2: #selector
Laitetaan windowDidLoad() -funktioon rivi:
popupAlbumi.action = #selector(onALBselected)
Ja luokan muiden funktioiden joukkoon vastaava funktio:
@objc private func onALBselected()
{
    let tt = popupAlbumi!
    //let irj = tt.indexOfSelectedItem   // valikon indeksi
    let irz = tt.titleOfSelectedItem!  // valikon teksti
    var iri = -2
    for item in arrayAlbumi.arrangedObjects as! [AnyObject]
    {
        let hh: Pari = item as! Pari   // haetaan tekstiä vastaava Parin id
        if hh.Nimi() == irz
        {
            iri = hh.idPari()
            break
        }
    }
    // iri==-2 tarkoittaa virhettä
    // tee jotain arvolla iri ja irz
}


Valikon arvon valitsemisen pyydystys, tapa 3: lisää rivien valinnalle tarkkailija:
arrayAlbumi.addObserver(self, forKeyPath:"selectionIndex", options:NSKeyValueObservingOptions.new, context:nil)

Tarkkailun tulos hyödynnetään korvaamalla (override) tarkkailijan action omalla. Funktion inputeilla selvitetään mistä action tuli:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
// if keyPath == "selectionIndex"  {  }
// if object as! NSObject == arrayAlbumi  {  }
    let tempx = arrayAlbumi.selectedObjects as NSArray
    let hhh: Pari = tempx.object(at: 0) as! Pari
    let iAlbumi = hhh.idPari()   // Int
    let albumiz = hhh.Nimi()   // String
    if iAlbumi == -1
    {
        println("Valittiin tyhjä")
        return
    }
    print("valittu \(albumiz)")

    //  tee tuloksella jotain
}

Tarkkailija pitää poistaa sitten, kun sitä ei enää tarvita, esim luokan deinitiliaisoinnin yhteydessä:
deinit
{
   arrayAlbumi.removeObserver(self, forKeyPath: "selectionIndex", context: nil)
}




--------------------------------
(sivua muokattu 22.1.2020)