[VS-Mac]VisualStudio for Mac (Cocoa)でComboBoxのデータソースクラス

## 概要
Zamarinのページ「[Databases and ComboBoxes](https://developer.xamarin.com/guides/mac/application_fundamentals/databases/#Databases-and-ComboBoxes)」には、ComboBoxの使用例としてSqliteデータベースの列名を指定してデータソースを設定する例が説明されています。  
汎用性の高そうなソースコードですが、複雑なため理解しにくい面があります。 
単純に配列をクラスに渡してデータソースを設定し、選択したアイテムを取得するように改変してみました。

## API:
[NSComboBox](https://developer.apple.com/reference/appkit/nscombobox)

## ソリューションの新規作成
省略

## InterfaceBuilder
* ViewControllerのViewにComboboxとLabelを追加
* LabelをWindowいっぱい近くまで広げておく（下のスクリーンショットでは分かりやすようにボーダーを変更しています）
* ComboBoxを選択して、AttributesInspectorのAutocompletesとUseDataSourceをチェック
* AssistantEditorを表示し、ViewController.hを選択表示
* ComboboxのOutlet作成（Name:ComboBox1）
　　（Ctrlキーを押したままComboboxをドラッグ）
* Labelも同様にOutlet作成（Name:Label1）
![Combobox01.png](https://qiita-image-store.s3.amazonaws.com/0/182396/94df60ec-a285-136f-88d3-51a9a4428200.png)

* SaveしてVisualStudioに戻る

## ソースコードの編集
### データソースクラス
1. ソリューションエクスプローラでプロジェクトを右クリックしてクラス「ComboBoxDataSource」を追加![Combobox02.png](https://qiita-image-store.s3.amazonaws.com/0/182396/c6a44254-ad9a-a686-4b6a-33fcc197b22a.png)![Combobox03.png](https://qiita-image-store.s3.amazonaws.com/0/182396/92fadcea-1eef-a5b9-e7c8-08b3e8972109.png)


2. コードを変更

```
using System;
using Foundation;
using AppKit;

namespace MyTutorial_Combobox
{
	public class ComboBoxDataSource : NSComboBoxDataSource
	{
        private string[] _lines;
		#region Constructors
        public ComboBoxDataSource(string[] lines)
        {
            _lines = lines;
        }
		#endregion

		#region Public Methods
		public string ValueForIndex(nint index)
		{
			return _lines[index];
		}
		#endregion

		#region Override Methods
		public override nint ItemCount(NSComboBox comboBox)
		{
			return _lines.Length;
		}

        public override NSObject ObjectValueForItem(NSComboBox comboBox, nint index)
        {
            NSString value = new NSString("");
            if (index > -1)
                value = new NSString(_lines[index]);
            return value;
        }

		public override nint IndexOfItem(NSComboBox comboBox, string value)
		{
            return Array.IndexOf(_lines, value);
		}

		public override string CompletedString(NSComboBox comboBox, string uncompletedString)
		{
            foreach(string line in _lines){
                if(line.ToLower().Contains(uncompletedString.ToLower())){
                    return line;
                }
            }
            return "";
   		}
		#endregion
	}
}
```

### ViewController.csを編集
```
using System;

using AppKit;
using Foundation;

namespace MyTutorial_Combobox
{
    public partial class ViewController : NSViewController
    {
        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            string[] items = new string[] { "北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県", "茨城県"
                , "栃木県", "群馬県", "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県", "石川県", "福井県"
                , "山梨県", "長野県", "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県", "京都府", "大阪府", "兵庫県"
                , "奈良県", "和歌山県", "鳥取県", "島根県", "岡山県", "広島県", "山口県", "徳島県", "香川県", "愛媛県"
                , "高知県", "福岡県", "佐賀県", "長崎県", "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県" };
            ComboBox1.DataSource = new ComboBoxDataSource(items);
			ComboBox1.Changed += (sender, e) =>
			{
                Label1.StringValue = 
                    string.Format("Event:Changed\nSelectedIndex:{0}\nShownItem:{1}\nStringValue:{2}\nIndexOf:{3}" +
                                  "\n\n直接入力した場合にはSelectedIndexは変わらないので要注意\nStringValueでデータが取得できる" +
                                  "\nindexはComboBox1.DataSource.IndexOfItem\n(ComboBox1, ComboBox1.StringValue)で"
                                  , ComboBox1.SelectedIndex
                                  , ComboBox1.DataSource.ObjectValueForItem(ComboBox1, ComboBox1.SelectedIndex)
                                  , ComboBox1.StringValue
                                  , ComboBox1.DataSource.IndexOfItem(ComboBox1, ComboBox1.StringValue));
			};

			ComboBox1.SelectionChanged += (sender, e) =>
			{
                Label1.StringValue = 
                    string.Format("Event:SelectionChanged\nSelectedIndex:{0}\nShownItem:{1}\nStringValue:{2}" +
                                  "\n\nStringValueは変更前の値なので要注意" +
                                  "\nComboBox1.DataSource.ObjectValueForItem\n(ComboBox1, ComboBox1.SelectedIndex)で取得"
                                  , ComboBox1.SelectedIndex
                                  , ComboBox1.DataSource.ObjectValueForItem(ComboBox1, ComboBox1.SelectedIndex)
                                  , ComboBox1.StringValue);
                
			};

        }
		public override void ViewWillAppear()
		{
			base.ViewWillAppear();
			this.View.Window.Title = "MyTutorial NSComboBox.DataSource";
		}
		public override void ViewWillDisappear()
        {
            base.ViewWillDisappear();
            //Windowを閉じる時にアプリも終了
			NSApplication.SharedApplication.Terminate(Self);
		}
        public override NSObject RepresentedObject
        {
            get
            {
                return base.RepresentedObject;
            }
            set
            {
                base.RepresentedObject = value;
                // Update the view, if already loaded.
            }
        }
    }
}
```

## 実行ウィンドウ
アイテムを選択した場合のSelectionChangedイベントでは、StringValueが選択前の値のままで新しく選択した値に変化していないことに注意が必要。
![Combobox04.png](https://qiita-image-store.s3.amazonaws.com/0/182396/f8f4295b-15cb-1362-b7d8-676028b56662.png)

ComboBoxに「北」と入力すると、オートコンプリートで最初に一致した「北海道」が表示された。
![Combobox05.png](https://qiita-image-store.s3.amazonaws.com/0/182396/b0dc45d8-6642-d6c7-8e4d-a796d360e725.png)
### 選択データの取得
* 一覧からitemを選択した場合にイベントからデータを取得する場合
  * index:SelectedIndex
  * value:ComboBox1.DataSource.ObjectValueForItem(ComboBox1, ComboBox1.SelectedIndex)
* Comboboxに直接入力したイベントまたはイベント外で表示中のデータを取得する場合
  * index:ComboBox1.DataSource.ObjectValueForItem(ComboBox1, ComboBox1.SelectedIndex)
  * value:ComboBox1.StringValue

## 補足説明
NSComboBoxDataSource派生クラスでデータソースを設定する場合は、
  1. ItemCount
  2. ObjectValueForItem
  3. IndexOfItem
のoverrideが必要のようです。
1.でデータ数
2.でitemを挿入していると思われます。
3.はデータの位置を取得します。
これに加えて、オートコンプリートのために 4. CompletedString をオーバーライドするようです。

### 付録
シングルトンアプリケーションなので、「ViewWillAppear()」「ViewWillDisappear()」でウィンドウ表示とクローズ処理をオーバーライドしてみました。
念のためComboBoxをDisposeしておきましたが、ここら辺の必要性はまだ勉強不足です。


## Reference:
Xamarin - Developers - Guides - Mac - Application Fundamentals - Working with Databases - [Databases and ComboBoxes](https://developer.xamarin.com/guides/mac/application_fundamentals/databases/#Databases-and-ComboBoxes)

今回のコードはXamarinのページを参考にしています。
大幅に変更していますが、一部を除き大部分の著作権はXamarinに帰属します。
### License:
Copyright (c) 2017 grayhead0603
Released under the [MIT license](http://opensource.org/licenses/mit-license.php)