【Xamarin.Mac】プロジェクト内のリソースファイルを実行時にアプリから読み込む
やりたいこと
Xamarin.Macで作ったデスクトップアプリで、プロジェクト内に用意した設定やテンプレートなどのファイルを実行時に読み込ませたい。
背景・利用シーン
Xamarin.Macでlog4netを使うため、起動時にXMLで書かれたファイルを読みこんでlog4netに渡したい。
また、アプリに簡易的なWebサーバを持たせたい時にテンプレートをファイルを読み込みたいという場面があり、その際にも同様の方法で実現した。
手順
今回はファイルを読み込む例としてプロジェクト内のJSONファイルを読み込み、前回作成した表に表示することにする。
前回の記事:DelegateとDataSourceを使ったTableViewにインクリメンタルサーチを実装する
ファイルの追加
読み込むJSONファイルを作成する。
プロジェクト下にResourcesというフォルダがあるのでここにファイルを追加する。ファイル名はmembers.jsonとした。
members.json
{
"members": [
{
"Name": "a2see",
"TwitterId": "a2see"
},
{
"Name": "うぇるち",
"TwitterId": "welchi_"
},
{
"Name": "想間ミレイ",
"TwitterId": "zerovabi"
},
{
"Name": "月見ねぎとろ",
"TwitterId": "tukimi_negitoro"
},
{
"Name": "創好リナ",
"TwitterId": "TsukusuLina"
},
{
"Name": "でんじぃ",
"TwitterId": "den_oldman"
},
{
"Name": "とりえ",
"TwitterId": "trpla226"
},
{
"Name": "なもなき",
"TwitterId": "nam0naki"
}
]
}
ビルドアクションの設定
ソリューションウィンドウからmembers.jsonファイルのコンテキストメニューを表示し、「プロパティ」をクリックする。
ビルド > ビルドアクションドロップダウンで「EmbeddedResource」を選択する。
EmbeddedResourceを選ぶと、「リソースID」にデフォルトのものが設定される。これをクリップボードにコピーしておく。
今回は「tablesearch.Resources.members.json」というIDになった。
ファイルを読み込みの処理を記述する
今回はViewControllerの生成時にファイルを読みこむようにする。
Assembly#GetManifestResourceStream()にリソースIDを渡すことで、Streamとして取得することができる。
あとはStreamを文字列として読み込み、欲しい形に変換して前回作った表示処理に食わせる。
なお、JSONのパースのにあたってNugetでNewtonsoft.Jsonを導入した。
ViewController.cs(該当部分)
// Resource内のファイルを取得
var assm = Assembly.GetExecutingAssembly();
var membersStream = assm.GetManifestResourceStream("tablesearch.Resources.members.json");
// string型としてファイルの最後まで読み込む
var membersJson = new StreamReader(membersStream).ReadToEnd();
// 取得したJSONをList<Member>に変換
var membersJObj = JObject.Parse(membersJson);
var membersJArray = (JArray)membersJObj["members"];
Members = membersJArray
.Select(jt => jt.ToObject<Member>())
.ToList().AsReadOnly();
JSONファイルの内容をテーブルに表示することができた。
環境
Mac OS X 10.15.6
Visual Studio for Mac Community 8.8.1(build 37)
Xamarin Mac 7.0.0.15
XCode Version 12.2
付録
前回のコードに変更を加えて作成した今回のViewController.csファイルの全容を公開しておく。
ViewController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using AppKit;
using Foundation;
using Newtonsoft.Json.Linq;
namespace tablesearch
{
public partial class ViewController : NSViewController
{
readonly MemberTableViewDataSource dataSource = new MemberTableViewDataSource();
IReadOnlyCollection<Member> Members;
public ViewController(IntPtr handle) : base(handle)
{
// Resource内のファイルを取得
var assm = Assembly.GetExecutingAssembly();
var membersStream = assm.GetManifestResourceStream("tablesearch.Resources.members.json");
// string型としてファイルの最後まで読み込む
var membersJson = new StreamReader(membersStream).ReadToEnd();
// 取得したJSONをList<Member>に変換
var membersJObj = JObject.Parse(membersJson);
var membersJArray = (JArray)membersJObj["members"];
Members = membersJArray
.ToList()
.Select(jt => jt.ToObject<Member>())
.ToList()
.AsReadOnly();
dataSource.Members = new List<Member>(Members);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MemberTableView.DataSource = dataSource;
MemberTableView.Delegate = new MemberTableViewDelegate(dataSource);
}
public override NSObject RepresentedObject
{
get
{
return base.RepresentedObject;
}
set
{
base.RepresentedObject = value;
// Update the view, if already loaded.
}
}
partial void KeywordChanged(NSObject sender)
{
var keyword = ((NSSearchField)sender).StringValue;
dataSource.Members = Members
.Where(member => member.Name.Contains(keyword))
.ToList();
MemberTableView.ReloadData();
}
}
public class MemberTableViewDataSource : NSTableViewDataSource
{
public List<Member> Members { get; set; } = new List<Member>();
public override nint GetRowCount(NSTableView tableView)
{
return Members.Count;
}
}
public class MemberTableViewDelegate : NSTableViewDelegate
{
private const string CellIdentifier = "MemberCell";
private MemberTableViewDataSource dataSource;
public MemberTableViewDelegate(MemberTableViewDataSource dataSource)
{
this.dataSource = dataSource;
}
public override NSView GetViewForItem(NSTableView tableView,
NSTableColumn tableColumn,
nint row)
{
NSTextField view = (NSTextField)tableView.MakeView(CellIdentifier, this);
if (view == null)
{
view = new NSTextField();
view.Identifier = CellIdentifier;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Editable = false;
view.Selectable = false;
}
var member = dataSource.Members[(int)row];
switch(tableColumn.Title)
{
case "Name":
view.StringValue = member.Name;
break;
case "Twitter ID":
view.StringValue = member.TwitterId;
break;
}
return view;
}
}
}
また、Newtonsoft.Jsonでデシリアライズするに当たってMemberクラスの定義も変更したので、変更後のコードを以下に掲載する。
Member.cs
namespace tablesearch
{
public class Member
{
public string Name;
public string TwitterId;
public Member(string name, string twitterId)
{
Name = name;
TwitterId = twitterId;
}
}
}
Discussion