Cocoa 用程式操控視窗 (中)

Window 與 View 的生命周期

 

記錄一下自己測試的順序,這也是為什麼在 ViewController 中要設定 Window 的代理不能在第一個 viewDidLoad 之後做,因為此時 window 還沒有 windowDidLoad。要等到 viewWillAppear 時才能設定 window 的代理。

  • viewDidLoad
  • windowDidLoad
  • viewWillAppear
  • viewDidAppear
  • viewWillLayout
  • viewDidLayout
  • viewWillLayout
  • viewDidLayout
  • ------------------
  • windowShouldClose
  • windowWillClose
  • viewWillDisappear
  • viewDidDisappear

 

尋找元件

 

由 Outlet 名稱尋找

 

在 Windows 的 C++ Builder 時代,要處理一個視窗中的元件,例如要找 fmMain 視窗中一個 Label 叫 lbText 的內容,就直接寫

String sStr = fmMain->lbText->Caption 

就可以了,但我在 Mac 程式中,起初一直搞不定。

在 ViewController 中,還可以把元件拉到程式中,設一個 Outlet

@IBOutlet weak var lbText: NSTextField!

在程式中就可以直接使用 lbText。不過若是由A視窗要找B視窗的 Outlet,之前一直試不出來,後來才想通問題在哪裡。

在「程式開啟視窗 (不使用 Segue)」這一段中,曾經實作出視窗並呈現,也就是這一行。

OptionWin!.orderFront(self)

直覺想要處理該視窗的元件,就使用了

OptionWin!.lbText

卻產生了錯誤。

現在才知道應該要如此轉換

 

// 先找出 Window 的 contentViewController , 並且轉成我們的程式類別 OptionView

let vcOption = OptionWin!.contentViewController as! OptionView

 

// 這樣才能操作元件

vcOption.lbText.stringValue = "Hello!"

 

重點在於,寫 MS Window 的程式時,通常就處理視窗 (WinForm) 和元件即可。

但在 Mac 中,則有 NSWindowController、NSWindow、NSViewController、NSView 四種層面要區別,還有 NSWindowDelegate 代理,然後才是處理 NSView 當中的元件。這中間只要沒處理好,就是一堆錯誤來報答你。

由 tag 尋找

NSView 元件有一個 tag 屬性,可以填入一個整數。[官網說明]

若我們先在 lbText 這個 Label 的 tag 填入 100,則程式中可以這樣找到它。

 

// 因為 viewWithTag 是 NSView 的成員函式,所以要用 vcOption.view 來處理

let lbNew = vcOption.view.viewWithTag(100) as! NSTextField

 

lbNew.stringValue = "Hello!"

 

由 tag 來找並不是很方便,過去在 Windows 時代也用過,當時主要是用在臨時產生的一堆元件,例如樹狀目錄中的眾多節點,此時就對於不同的節點給予不同的 tag,方便程式辨識處理。

遍歷元件

view 還有一個 subviews 成員函式,可以找出該 view 的子視圖組。[官方說明]

使用遞迴的方式,就可以把所有的元件找出來了。

 

    @IBAction func btRun(_ sender: Any) {

        SearchItem(item: self.view,level: 1)

    }

    

    func SearchItem (item: NSView, level: Int){

        let subs = item.subviews

        // 遍歷元件

        for subitem in subs {

            // 印出層級與內容

            print ("\(level) : \(subitem)")

            // 遞迴處理

            SearchItem(item: subitem, level: level+1)

        }

    }

 

底下是一個 TableView 和一個 Button,以及它的結構。

image

底下則是用上面的遍歷方式,把所有的子視窗列出來,排版是後來手動處理的。

上圖與底下列表雙方似乎不太能完全對應起來?

 

1 : <NSButton: 0x101932fa0>

  2 : <NSButtonBezelView: 0x10180dac0>

  2 : <NSButtonTextField: 0x101815220>

1 : <NSScrollView: 0x1020d1800>

  2 : <_NSScrollViewContentBackgroundView: 0x10193a980>

    3 : <NSVisualEffectView: 0x101815e90>

  2 : <NSClipView: 0x101933d80>

    3 : <NSTableView: 0x101934080>

  2 : <NSClipView: 0x101935070>

    3 : <NSBannerView: 0x101935580>

      4 : <NSVisualEffectView: 0x101936cf0>

      4 : <_NSBannerDecorationView: 0x101936fd0>

    3 : <NSTableHeaderView: 0x101934a90>

  2 : <NSScroller: 0x101938fe0>

  2 : <NSScroller: 0x1019385b0>

 

遍歷 + 判斷元件種類

利用遍歷也可以處理指定的元件種類。

底下程式是利用遍歷把全部 Button 的標題換成 "Hello"。

 

    @IBAction func btRun(_ sender: Any) {

        SearchItem(item: self.view)

    }

    

    func SearchItem (item: NSView) {

        let subs = item.subviews

        // 遍歷元件

        for subitem in subs {

            // 判斷是不是能轉成 NSButton 

            if let myButton = subitem as? NSButton {

                myButton.title = "Hello"

            }

            SearchItem(item: subitem)

        }

    }

 

遍歷 + identifier

許多元件都有 identifier 這個識別碼,因此設定識別碼再加上遍歷,就可以找到指定的元件。

例如先把某個 Button 的 identifier 設定為 "mybt"。

底下程式是利用遍歷把該 Button 的標題換成 "Hello"。

 

    @IBAction func btRun(_ sender: Any) {

        SearchItem(item: self.view)

    }

    

    func SearchItem (item: NSView) {

        let subs = item.subviews

        // 遍歷元件

        for subitem in subs {

            // 有 identifier 的元件才處理

            if let id = subitem.identifier {

                // 判斷 identifier 的內容,要用 rawVale 處理

                if id.rawValue == "mybt" {

                    // 將元件轉換成 NSButton

                    let myButton = subitem as! NSButton

                    myButton.title = "Hello"

                }

            }

            SearchItem(item: subitem)

        }

    }

 

重要度:
文章分類:
電腦標籤:

發表新回應