Paneeli-ikkuna
(sheet)
Esittely

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:
- A) Completionhandler voi antaa arvon toisen luokan
muuttujalle, joka perii (inherits) luokan NSObject. Tiedon
saanti tuloksesta on järjestettävä seuraamalla kyseisen
muuttujan arvon muuttumista tarkkailijalla (observer)
- B) Completionhandlerin sisällä kutsutaan funktiota, jonka
parametriksi annetaan sheetistä saatu tulos
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)