/ 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ごとの細かい設定が大量に施されているので、大胆にいじくり回すにはかなり勇気がいる感じ。



コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です