Iso-Syöte


Paneeli-ikkuna (sheet)

Esittely

Sheet window

Yläkuvassa on alla ikkuna Paik_katso, jossa esitellään paikan tiedot. Siellä on painettu painiketta Muokkaa, jolloin tämä modaalinen sheet (Paik_muok) avautuu. Nyt annetaan uusia tietoja tai perutaan muokkaus, ja tieto siitä kumpi tehtiin, välitetään lähtöikkunalle. Itse tiedot tallennetaan tietokantaan, joten niitä ei välitetä lähtöikkunalle outputtina.

Ohjelmointitavat

Seuraavassa on kuvattu kaksi vaihtoehtoista tapaa sheet-ikkunan toteuttamiseksi. Tapa 1 antaa mahdollisuuden, että modaalisesta sheet-ikkunasta avataan tavallinen ei-modaalinen ikkuna, esimerkiksi karttakuva. Tapa 2 ei tällaista mahdollista, koska sheet on app-modal eli koko sovellus on jumissa kunnes käyttäjä sulkee sheetin, mutta sheetistä saa helposti ulos monimutkaisetkin tulokset. Tapa 1 on ohjelmateknisesti hieman monimutkaisempi, mutta koodi loogisempi lukea.


TAPA  1  (window-modal)

Uusi sheet-tiedosto perustetaan Xcodessa tekemällä uusi Cocoa-luokka (valikosta File -> New -> File). Seuraavassa ikkunassa valitaan NSWindowController, ja ruksataan xib-tiedosto.

xib-tiedostossa hoidettavaa käyttöliittymäeditorissa:
-    laita ikkunaan vähintään yksi button jolla suljet ikkunan
-    ikkuna / Identity inspector: Class = NSWindow (on oletusarvo)
-    ikkuna / Attributes inspector:
    -    Title: ei tule näkyviin paneelikäytössä
    -    Behavior: poista ruksi 'Visible At Launch' (vain jää 'Restorable')
    -    Memory: ruksattuna vain Deferred

Modaalinen sheet avataan beginSheet() -funktiolla, jonka yhtenä parametrinä on funktio (closure, completionhandler). Kyseinen completionhandler sisältää toimenpiteet mitä tehdään, kun sheet on onnistuneesti avautunut ja käyttäjä on sen sulkenut. Jos sheet on vain info eikä sieltä tarvita outputtia, completionhandler -input on nil.

Funktio beginSheet() avaa modaalisen sheetin, eikä sitä kutsunut funktio jää odottamaan mitä tapahtuu, eikä kutsuja siis saa tietoa mikä oli tulos sheetistä. Myöskään ei ole mahdollista, että completionhandler antaisi arvon kutsuvan luokan (Paik_katso) jollekin tavanomaiselle luokkamuuttujalle (Int, Bool). Mutta tiedon vastaanottamiseen on kaksi erilaista tapaa:
Vaihtoehto B ei toimi, jos kutsuttu funktio on liian monipuolinen. Mutta A tuntuu aina toimivan.

Seuraavassa koodauksen ydinkohdat yllä olevan esimerkkikuvan avulla, kielenä Swift 4:


Paik_katso / Vaihtoehto A:

Kooditiedostoon laitetaan oma luokka varsinaisen luokan Paik_katso lisäksi:
class Valvottava: NSObject     // (luokan on perittävä NSObject)
{
    @objc dynamic var muutos = -1   // tätä tarkkaillaan observerin avulla
}


Luokkamuuttuja:
var valvo: Valvottava?

Perustetaan tarkkailija windowDidLoad() -funktioon ikkunan luokassa tai applicationDidFinishLaunching() -funktioon luokassa AppDelegate:
valvo = Valvottava()
valvo!.addObserver(self, forKeyPath: "muutos", options: NSKeyValueObservingOptions.new, context: nil)


// siivotaan lopuksi tarkkailija pois:
deinit
{
    valvo!.removeObserver(self, forKeyPath: "muutos", context: nil)
}

Korvataan luokan tarkkailija omalla versiolla, josta saadaan muutos käyttöön:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
   if valvo!.muutos <> -1
   {
      // tarpeelliset toimenpiteet
   }
}


Jos luokassa tarkkaillaan useaa muuttujaa, tulee vain yksi ylläoleva funktio. Input keyPath sisältää muuttujan nimen, edellä "muutos".

Sheet avataan action-funktiossa 'Muokkaa paikkaa' seuraavasti:
paikmuok = Paik_muok(windowNibName: NSNib.Name(rawValue: "Paik_muok"))
paikmuok!.db = db!   // viedään inputiksi tässä tietokanta
self.window!.beginSheet(paikmuok!.window!, completionHandler:
{ (vastaus: NSApplication.ModalResponse) -> Void in
      self.valvo!.muutos = vastaus.rawValue
} )
print("testi") // tämä toteutuu heti
// sitten jatkuu edellä olevassa funktiossa observeValue()



Paik_katso / Vaihtoehto B:

Sheet avataan action-funktiossa 'Muokkaa paikkaa' seuraavasti:
paikmuok = Paik_muok(windowNibName: NSNib.Name(rawValue: "Paik_muok"))
paikmuok!.db = db!   // viedään inputiksi tässä tietokanta
self.window!.beginSheet(paikmuok!.window!, completionHandler:
{ (vastaus: NSApplication.ModalResponse) -> Void in
       self.paluuMuokkauksesta(vastaus.rawValue)
} )
print("testi") // tämä toteutuu heti
// sitten jatkuu funktiossa paluuMuokkauksesta()



Paik_muok:
luokkamuuttujat:
var db: FMDatabase?    // tätä on käytetty edellä esimerkissä
var kantamuutos = false // tätä ylläpidetään


Sheet-ikkunan sulkeminen, jolloin välitetään vastauksena tieto onko käyttäjä tallentanut mitään tietokantaan:
@IBAction func buttonSulje(_ sender: AnyObject)
{
   // Näin annetaan outputiksi mikä tahansa integer
   let tulos: NSApplication.ModalResponse = NSApplication.ModalResponse(rawValue: (kantamuutos) ? paikkax.idPaikka : -1)
   self.window!.sheetParent!.endSheet(self.window!, returnCode: tulos)
}



TAPA  2 / App-modal

Uusi sheet-tiedosto perustetaan Xcodessa tekemällä uusi Cocoa-luokka (valikosta File -> New -> File). Seuraavassa ikkunassa valitaan NSWindowController, ja ruksataan xib-tiedosto.

xib-tiedostossa hoidettavaa käyttöliittymäeditorissa:
-    laita ikkunaan vähintään yksi button jolla suljet ikkunan
-    ikkuna / Identity inspector:  Class = NSPanel
-    ikkuna / Attributes inspector:
    -    Title: ei tule näkyviin paneelikäytössä
    -    Behavior: poista ruksi 'Visible At Launch' (vain jää 'Restorable')
    -    Memory: ruksaa molemmat (Deferred ja One Shot)

Seuraavassa koodauksen ydinkohdat yllä olevan esimerkkikuvan avulla, kielenä Swift 4:

Paik_katso:
luokkamuuttuja:
var paikmuok: Paik_muok?

Paik_muok:n kutsumisessa on seuraavaa. Ensin perustetaan sheet ja viedään sen luokalle dataa. Kutsutaan luokan funktiota avaaSheet, josta saadaan sheetiltä haettu tulos:

paikmuok = Paik_muok(windowNibName: NSNib.Name(rawValue: "Paik_muok"))
paikmuok!.db = db!   // viedään inputiksi tässä tietokanta
let tulos = paikmuok!.avaaSheet(window)


Nyt tulos sisältää tiedon mitä sheet-ikkunasta haettiin ja sheet on suljettu.

Paik_muok:
luokkamuuttujat:
var db: FMDatabase?    // tätä on käytetty edellä esimerkissä
var kantamuutos = false // tätä ylläpidetään


Tämä on koodattu aivan samoin kuin tavallinen ikkunaa esittävä luokka, mutta lisäksi siinä on seuraavat modaalisuuden hallinnat:

func avaaSheet(_ kutwin: NSWindow) -> String
{
   let w = self.window!
   kutwin.beginSheet(w, completionHandler: nil)
   NSApp.runModal(for: kutwin)
   // nyt odotetaan käyttäjän toimenpiteitä ikkunassa
   return kantamuutos
}


Sheet-ikkunan sulkeminen on action-metodissa esimerkiksi 'Valmis':
self.window!.sheetParent?.endSheet(self.window!)
NSApp.stopModal()
 // jatkuu sieltä missä laitettiin modaaliseksi




--------------------------------
(sivua muokattu 18.12.2019)