SettingsFlyout

/ C#, Windows 8

Windowsストアアプリのオプション設定は通常「設定チャーム」の中にあります。
Windows 8の画面右端をスライド(マウスの場合は右下or右上にカーソル移動)するとニョキっと出てくるアレがチャームで、その中の「設定」と書かれたボタンをタップすると設定チャームが開いて、アクティブになっているアプリ固有の設定などを行うことができます。

アプリは「フライアウト」と呼ばれるポップアップに各種設定項目を詰め込むわけですが、このフライアウト、以前は自前でXAMLとコードを用意しなくてはならず面倒だったのですが、VS2013では「ポップアップの設定」なるテンプレートが付属してくるようになり、少しだけラクになりました。
XAMLはともかく、設定チャームとして登録するためのコードは相変わらず必要なので、リファレンスを参考に試しに触ってみたところ、以前の形とほぼ変わらず普通に使うことができるようです。

が、以前のものも新しいテンプレもひとつ問題が…。
それは、フライアウトからファイルピッカーなどを呼び出すと、ピッカーから戻ってきた時にフライアウトが閉じてしまう、というもの。
拙作「こよみプリント」や「Simple Rec」には設定チャーム内に画像ファイルを選択するためのボタンが配置してあり、押すとファイルピッカーが開くのですが、こういった作りの場合、普通にやるとファイルピッカーが閉じた時点でチャームも閉じてしまいます。

というわけで、ちょっと力技ですが、この問題を回避する方法をば。
簡単に言うと、フライアウトが閉じたときにTagプロパティを見て何か設定されていればフライアウトを再度開くだけ、です。
以下、Windows 8.1のSettingsFlyoutを使った場合のサンプルコードです。
グダグダ書かれてますが、各々ハイライト部分に相当する記述を追加してあげればOK。
ちなみに、SettingsPaneへのイベント登録はApp.xaml.csのOnWindowCreatedをオーバーライドするのがセオリーっぽいのでご注意あれ。

public MainPage()
{
	this.InitializeComponent();

	Windows.UI.ApplicationSettings.SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
}

void MainPage_CommandsRequested(Windows.UI.ApplicationSettings.SettingsPane sender, Windows.UI.ApplicationSettings.SettingsPaneCommandsRequestedEventArgs args)
{
	var settingsCommand = new Windows.UI.ApplicationSettings.SettingsCommand("Options", "Options", (_) =>
		{
			var settingFlyout = new SettingsFlyout1();

			settingFlyout.Unloaded += (s, _) =>
				{
					var flyout = (SettingsFlyout)s;
					if (flyout.Tag != null)
						flyout.Show();
				};

			settingFlyout.Show();
		});

	args.Request.ApplicationCommands.Add(settingsCommand);
}
async void OpenImage_Click(object sender, RoutedEventArgs e)
{
	try
	{
		this.Tag = "@";

		var picker = new Windows.Storage.Pickers.FileOpenPicker()
		{
			ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail,
			SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary,
		};
		picker.FileTypeFilter.Add(".png");
		picker.FileTypeFilter.Add(".jpg");
		picker.FileTypeFilter.Add(".jpeg");

		var selectedFile = await picker.PickSingleFileAsync();

		if (selectedFile != null)
		{
			//TODO - processing image

			var image = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
			var stream = await selectedFile.OpenReadAsync();
			await image.SetSourceAsync(stream);

			SelectedImage.Source = image;
		}
	}
	catch
	{
		throw;
	}
	finally
	{
		this.Tag = null;
	}
}

Office文書のPDF変換

/ .NET, C#

Offce2007以降、PDF形式へのエクスポートが標準でサポートされるようになりました。
というわけで、.NETアプリケーションからOfficeファイルのPDFエクスポートを実行する方法を紹介したいと思います。

静的な参照設定を行う場合は、必要なOfficeアプリのObject Libraryを参照設定しておいて、普通にVBAオブジェクトを.NETアプリから操作すれば良いわけですが、これだとそもそもOffice2007以降がインストールされていないPCでは参照エラーで正常動作しません。

そこで登場するのが遅延バインディング(Late Binding)という方法。
論より証拠、Excel→PDF変換のサンプルコードを載せておきます。

Word、PowerPointについても同様のイメージで実装できますが、Officeアプリによってそれぞれライブラリ仕様が異なりますので、流用できるのは基本的な流れ(アプリインスタンス作成→ファイルオープン→エクスポート→クローズ)だけになります。
そのため、少し面倒ですが、各OfficeアプリのMSDN Developer Referenceなどを参照してプロパティやメソッドの利用方法を調べつつ、実装していく必要があります。
VBAのオブジェクトをインテリセンス無しに記述して操作することになるので、とにかくパラメータの数や位置に注意が必要です。
リファレンス無しではまず作れないわけですが、どうも英語の資料しかなさげなのが厳しいところです。

public bool Excel2Pdf(string sourcePath, string targetPath)
{
	const int xlTypePDF = 0;
	const int xlQualityStandard = 0;

	bool result = false;
	object missing = Type.Missing;

	object excel = null;
	object workbooks = null;
	object workbook = null;

	try
	{
		excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));

		if (excel != null)
		{
			workbooks = excel.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, excel, null);

			if (workbooks != null)
			{
				workbook = workbooks.GetType().InvokeMember("Open", BindingFlags.InvokeMethod, null, workbooks,
					new object[]
					{
						sourcePath, missing, true, missing, missing, missing,
						missing, missing, missing, missing, missing, missing, missing, missing, missing
					}, null);

				if (workbook != null)
				{
					workbook.GetType().InvokeMember("ExportAsFixedFormat", BindingFlags.InvokeMethod, null, workbook,
						new object[]
						{
							xlTypePDF, targetPath, xlQualityStandard, true, false, missing, missing, missing, missing
						}, null);
					result = true;
				}
			}
		}
	}
	catch
	{
	}
	finally
	{
		if (workbook != null)
		{
			workbook.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, workbook,
				new object[]
				{
					false, missing, missing
				}, null);
			workbook = null;
		}
		if (workbooks != null)
			workbooks = null;
		if (excel != null)
		{
			excel.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, excel, null, null);
			excel = null;
		}
		GC.Collect();
		GC.WaitForPendingFinalizers();
		GC.Collect();
		GC.WaitForPendingFinalizers();
		GC.Collect();
		GC.WaitForPendingFinalizers();
	}
	return result;
}

ここでは単純にOK/NGを返すだけのメソッドになっていますが、実際にはエラー処理などの追加が必要になるかと思います。
また、パスワードロックされているOffice文書を開いた場合など、変換処理中にOfficeのウィンドウが表示されることがあるのですが、そのウィンドウが前面表示されないために操作感が悪くなってしまうケースが見受けられました。
これについては、Win32APIのFindWindow+SetForegroundWindowメソッドで最前面表示する手法を使うことによって回避できるようです。


編集可能なDataGridのテンプレ

/ C#, Silverlight

DataGridコントロールの復習がてら、簡易タイムレコーダー「Azume」に旅費交通費の入力/印刷機能を追加してみました。

表示のみ(ReadOnly)で利用する場合は比較的簡単に扱えるDataGridですが、編集可能にしようと思うと色々と面倒なことになるのがいつものパターン。
ありがちなのが「編集用セルとしてComboBoxコントロールを使うパターン」で、これまでにも何度かトライしているのですが、これといった定番の実装パターンができていなかったので、少し整理してみました。

■データソース定義

public class ExpensesEntity : BaseEntity
{
	public DateTime Date { get; set; }
}

public class DateItemsSoruce
{
	public DateTime Date { get; set; }
	public string DisplayDate
	{
		get
		{
			return Date.ToString("M/d (ddd)");
		}
	}
}

// (説明用に値は直接放り込んでいます…)

// DataGridのItemsSourceに設定
public ObservableCollection<ExpensesEntity> listExpenses =
	new ObservableCollection<ExpensesEntity>(
		new ExpensesEntity { Date = DateTime.Parse("2011/08/10") },
		new ExpensesEntity { Date = DateTime.Parse("2011/08/11") },
	);

// DateComboBoxのItemsSourceに設定
public ObservableCollection<DateItemsSource> listDate =
	new ObservableCollection<DateItemsSource>(
		new DateItemsSource { Date = DateTime.Parse("2011/08/01") },
		// …
		new DateItemsSource { Date = DateTime.Parse("2011/08/31") },
	);

■XAML定義

<UserControl.Resources>
	<DataTemplate x:Key="DateTemplate">
		<TextBlock Text="{Binding Date, StringFormat='M/d (ddd)', ConverterCulture=ja-JP}"/>
	</DataTemplate>
	<!-- HACK - SelectedItem、SelectedValueのバインディング定義はXAML上では行わない -->
	<DataTemplate x:Key="DateEditTemplate">
		<ComboBox DisplayMemberPath="DisplayDate" SelectedValuePath="Date"/>
	</DataTemplate>
<UserControl.Resources>

<sdk:DataGrid x:Name="dgData" AutoGenerateColumns="False"
	PreparingCellForEdit="dgData_PreparingCellForEdit"
	CellEditEnding="dgData_CellEditEnding">
	<sdk:DataGrid.Columns>
		<sdk:DataGridTemplateColumn Header="日付"
			CellTemplate="{StaticResource DateTemplate}"
			CellEditingTemplate="{StaticResource DateEditTemplate}"/>
	</sdk:DataGrid.Columns>
</sdk:DataGrid>

■コードビハインド

public EntryExpenses()
{
	InitializeComponent();

	//データグリッドのItemsSource設定
	dgData.ItemsSource = listExpenses
}

private void dgData_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
	//HACK - ComboBoxのItemsSource、Binding設定はこのタイミングで実施
	if (e.Column.Header == "日付")
	{
		var cbo = e.EditingElement as ComboBox;

		cbo.ItemsSource = listDate;

		var bind = new System.Windows.Data.Binding("Date");
		bind.Mode = System.Windows.Data.BindingMode.TwoWay;
		cbo.SetBinding(ComboBox.SelectedValueProperty, bind);
	}
}

private void dgData_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
	if (e.Column.Header == "xxxxx")
	{
		//TODO - セル編集終了時処理を記述
		//
		//	編集前後の値を取得できるので比較処理が可能
		//
		//	編集前の値: EditingElement.DataContext
		//	編集後の値: コントロール値(EditingElement)
	}
}

長々と書いてしまいましたが、ポイントとなるのは「ComboBoxのItemsSourceプロパティへのデータソース設定、SelectedValueプロパティへのバインディング設定は、XAMLではなく、PreparingCellForEditイベントで逐一実施する」という点。
どうもComboBoxコントロールのバグの気配がするのですが、当初XAML上に記述したところ、どうにもマトモに動いてくれず、あれこれいじった挙句に安定してくれたのが上記コード。
ちなみに、値のバインド先にSelectedItemプロパティを使っているサンプルも多く見かけましたが、SelectedValuePathを設定するケースにおいてはSelectedValueプロパティのほうが具合がよろしいようです。
その他はとりたてて注意する箇所はなさげで、データソース側にValidation定義を施しておけば簡単な入力検証はコードレスでいけるので、うまく型にハメることさえできればスピーディに開発できるであろうことは周知の通りです。

まぁ、分かってしまえばコードを書くのはどうってことないのですが、デザイン周りをカスタマイズしようとなると、DataGrid辺りの複雑なコントロールはあちこちいじらないといけないので、かなりヘビー。
一応Blendは使っているものの、既定の追加テンプレートはVisualStateごとの細かい設定が大量に施されているので、大胆にいじくり回すにはかなり勇気がいる感じ。


Silverlightでのレポーティング

/ C#, Silverlight

Silverlight4は簡易的な帳票出力に対応していて、UIElementを丸ごとプリンタに送り出せることは知っていたのですが、複数ページの出力はどうやるのか、いまいちイメージが沸かなかったので試してみることに。

PrintPageイベントがページ単位に発動されるのが混乱を招く感じですが、基本的にはページ毎にUIElementを設定してあげれば良い模様。
多少汎用的に書くとしたら、以下のような感じに書けば良いかと。

private List<UIElement> pageElement;

private void button1_Click(object sender, RoutedEventArgs e)
{
	//ページコンテンツ設定
	pageElement = new List<UIElement>() { txtPage1, txtPage2 };

	//印刷指示
	var pd = new PrintDocument();
	pd.Print("PrintTest");

	//印刷処理
	pd.PrintPage += (s, args) =>
	{
		//カレントページにコンテンツを設定
		args.PageVisual = pageElement[0];

		//カレントコンテンツを削除
		pageElement.RemoveAt(0);

		//次ページ有無チェック
		args.HasMorePages = (pageElement.Count > 0);
	};
}

このコードでは、テキストボックス txtPage1、txtPage2 をそれぞれ別ページに出力していますが、任意のUIElementを設定してあげれば、それがページのコンテンツになるという仕組みです。
ページコンテンツの中身は如何様にもカスタマイズできてしまうわけだし、プレビューの仕組みもそれほど難しくないと思われるので、このレポーティング機能、案外業務アプリでも使えるかもしれません。


電力使用率アプリ

/ C#, Silverlight

もはやあちこちのサイトに貼られてる東電の電力使用率表示ですが、リハビリがてらSilverlightで書いてみました。

データの取得には東京電力電力供給状況APIを使わせていただいたので、コード自体はほんの数行で出来上がりです。(以下、抜粋。XAMLも割愛)

using System.Json;

private void GetUsage()
{
	var wc = new WebClient();
	var url = "http://tepco-usage-api.appspot.com/latest.json";

	wc.DownloadStringAsync(new Uri(url));
	wc.DownloadStringCompleted += (s, args) =>
	{
		var data = JsonObject.Parse(args.Result);

		tbTime.Text = string.Format("{0:M/d H:mm}", DateTime.Parse(data["usage_updated"]).ToLocalTime());
		tbUsage.Text = string.Format("{0:0.0%}", (double)data["usage"] / (double)data["capacity"]);
	};
}

データの更新は毎時1回とのことなので自動更新は端折りましたが、更新日時の部分をクリックするとデータを再取得するようにしてあります。
でもって、計画停電中は背景色がレッド、停電していない場合はブルー表示になります。

みてくれがイマイチだけども、せっかくなのでウィジェットとしてサイドバーに貼っておくことにしませう。