Cocoa 用程式操控視窗 (下)

 

本篇要實作二個視窗,一個主視窗,一個子視窗,類似在 MS Window 開發程式時的 fmMain、fmOption 二個視窗。

重點在於主視窗可以開啟子視窗,子視窗可以關閉,但並不是像 Mac 的視窗那樣真的關閉,只是隱藏起來。當再次開啟副視窗時,則再次呈現,畫面中的內容也保持一樣。

同時也希望各視窗之間可以彼此操控對方的元件。

 

初步構想

 

  1. 先設定全域變數 vcMain 和 vcOption 來記錄二個視窗的 ViewController。

  2. 要開啟子視窗時,先判斷子視窗是否已經實作了?若無,則實作它,若已實作了則顯示它。

  3. 實作視窗後,記錄在全域變數中。

  4. 子視窗關閉時,採用隱藏方式。

  5. 利用全域變數去控制對方視窗的元件。

 

設立基本視窗

 

在 Storyboard 中,初始已經有一個 Window Controller 和 View Controller 了,那是我們的主視窗,所以要再拉一個 Window Controller 在畫面上,而這就是我們要的子視窗。

image

除了視窗,我們同時放上了四個元件,主視窗也是同樣放四個元件。

一個 Show 按鈕。

一個 Label 標籤。

一個文字輸入欄位。

一個 Close 按鈕。

當我按下子視窗的 Show,我希望能把主視窗的輸入欄位中的文字呈現在子視窗的 Label 上。

同樣,主視窗的 Show 按下時,也會在主視窗的 Lable 呈現子視窗中輸入欄位的內容。

如此一來就表示各視窗可以彼此操控其他視窗的元件。

子視窗最底下的 Close 按鈕是要關閉視窗,其實只是想隱藏起來。

主視窗最底下則是 Open 按鈕,它是要開啟子視窗用的。

這些操作是模擬以前在 MS Window 使用 C++ Builder 操作視窗的模式。我對 Mac 視窗習慣不了解,所以先用 MS Window 的習慣來思考。

 

新增程式

 

此時還要新增一個 Cocoa Class,Subclass of: 選擇 NSViewController,Class 填上 OptionViewController,這是要給處理子視窗的程式。

image

然後按下子視窗左邊的紅框處 (View Controller) ,將其右邊的 Class 設定為 OptionViewController,也就是我們剛剛產生的程式,要由它來控制子視窗。

image

 

連結主視窗與子視窗

 

先前有試過三種方法來開啟子視窗。

第一種是直接在主視窗的 Button 建立 Segue 來 Show 子視窗。不過因為我們不是單純要呈現子視窗,而是還要判斷是否已經建立過?已建立則要用顯視的方式,因此這個方法不適合。

第二種方法是完全用程式處理,這部份我不熟,還有些地方不清楚,所以也不採用。

這裡採用第三種方法,就是由主視窗拉一個 Segue 到子視窗,命名為 sgShowOption,等一下再用程式來呼叫它。

image

 

設定全域變數

 

首先在 ViewController.swift 檔案中填加二個全域變數,用來記錄主視窗 vcMain 和子視窗 vcOption 二個 ViewController。

image

第一個全域變數 vcMain 在同一個檔案中設定,也就是在 viewDidLoad 時設定 vcMain = self 即可。

image

同樣,切換到 OptionViewController.swift 檔案,當子視窗建立時,在此時設定 vcOption = self 即可記錄下子視窗。

image

 

主視窗開啟子視窗

 

主視窗有一個按鈕是要開啟子視窗的,將它拉一個 @IBAction 到 ViewController.swift 檔案中,內容如下。

它是檢查 vcOption 是否是空值 nil,如果是空的,表示子視窗還沒有建立過,就執行 performSegue 來建立子視窗並開啟,也就是紅框那一行。

前面提過,子視窗建立時,vcOption 就會記錄子視窗了。

因此第二次開啟時,因為 vcOption 已經有記錄了,所以就直接將視窗顯現出來,也就是黃框那一行,使用 orderFront 呈現子視窗。

image

補充:orderFront 會把視窗顯現出來,但並不會取得焦點,可以改用 makeKeyAndOrderFront 就可以了。

關閉子視窗時執行隱藏

 

子視窗要關閉時,我們不打算真的關閉,只想隱藏,因此在子視窗 Close 的 Button 拉一個 @IBAction 到 OptionViewController.swift 檔案中,程式如下,只是把視窗隱藏起來。

image

如果希望使用者按下視窗左上角紅色的關閉,也依然執行隱藏,則必須要先將 OptionViewController 繼承 NSWindowDelegate。

image

然後在 viewWillAppear 指定 window 的代理

image

最後在 OptionViewController 類別中實作 windowShouldClose 函式,執行隱藏子視窗,然後傳回 false,視窗就不會真的關閉了。

image

 

讀取其它視窗的元件

 

最後要來實作二個視窗互相讀取對方元件的功能。

首先在子視窗中,把文字輸入欄位與 Lable 分別拉到 OptionViewController.swift 中,命名為 lbText 和 edEdit。

主視窗也做同樣的動作。

image

再來建立 Show Button 的功能,程式如下。意思就是先判斷 vcMain 是否存在,如果存在,則將它的 edEdit 的內容呈現在子視窗的 lbText 這個 Label 上。

image

主視窗 ViewController.swift 的作法也相同。

image

 

實際測試成果

 

實際測試,執行成果如下。

image

按下左邊主視窗的 Open,右邊子視窗就會開啟。

在子視窗的輸入欄位中填入 123 (紅圈1),再按下主視窗的 Show 按鈕 (紅圈2),此時主視窗的 Label 就如期呈現 123 (紅圈3),符合程式當初設計的要求。

同樣,主視窗輸入 456,按下子視窗的 Show 按鈕,右邊的 Label 也呈現 456。

此時不論是按下子視窗的 Close 或是左上角紅色的關閉,子視窗都會消失。

再按下主視窗的 Open,子視窗又會再度呈現,畫面中的輸入欄位與 Label 都保持原來的內容,表示是原來的視窗,並不是重新建立的新視窗。

測試終於成功了!

 

完整程式碼

 

ViewController.swift : 主視窗的程式,紅色是自行加入的程式碼。

import Cocoa

 

var vcMain: ViewController? = nil

var vcOption: OptionViewController? = nil

 

class ViewController: NSViewController {

 

    @IBOutlet weak var lbText: NSTextField!

    @IBOutlet weak var edEdit: NSTextField!

    

    override func viewDidLoad() {

        super.viewDidLoad()

 

        // Do any additional setup after loading the view.

        

        vcMain = self

    }

 

    override var representedObject: Any? {

        didSet {

        // Update the view, if already loaded.

        }

    }

 

    @IBAction func btShow(_ sender: Any) {

        if vcOption != nil {

            lbText.stringValue = vcOption!.edEdit.stringValue

        }

    }

    

    @IBAction func btOpen(_ sender: Any) {

        if vcOption == nil {

            performSegue(withIdentifier: "sgShowOption", sender: self)

        } else {

            vcOption!.view.window!.makeKeyAndOrderFront(self)

        }

    }

}

 

OptionViewController.swift : 子視窗的程式,紅色是自行加入的程式碼。

 

import Cocoa

 

class OptionViewController: NSViewController , NSWindowDelegate {

    

    @IBOutlet weak var lbText: NSTextField!

    @IBOutlet weak var edEdit: NSTextField!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do view setup here.

        vcOption = self

    }

    

    override func viewWillAppear() {

        view.window!.delegate = self

    }

    

    @IBAction func btShow(_ sender: Any) {

        if vcMain != nil {

            lbText.stringValue = vcMain!.edEdit.stringValue

        }

    }

    

    @IBAction func btClose(_ sender: Any) {

        view.window!.orderOut(self)

    }

    

    func windowShouldClose(_ sender: NSWindow) -> Bool {

        view.window!.orderOut(self)

        return false

    }

}

 

最後真心話

 

原本上一段就結束了,但想想還是忍不住要說句真心話,若是用 C++ Builder 在 MS Window 來寫同樣的東西,實在簡單多了:建一個子視窗,二個視窗互相 include,彼此放置元件,然後四個 Button 各寫幾行簡單程式就解決了...也許各寫一行就夠了。

不過也有人說,MS Window 那種架構是很差的,Mac 這種才符合 MVC 規範,我是不太懂,對 Mac 也是完全初學,還沒寫到複雜的東西,所以也無法判斷哪一種說法才是對的,就等我在 Mac 上多寫些程式再說了。

 

重要度:
文章分類:

發表新回應

借我放一下廣告