カレンダーが無いなら作ればいいじゃない?
というわけで作ってみよう。
超汎用的なものを作るのはめんどくさいので、仕様はかなり絞ってみようと思う。
- 年月日をDateTime型で指定すると、その月のカレンダーを表示して、指定した日を選択状態にする
- 曜日は日曜日はじまり
- もちろん選択された日付をDateTime型で取得できる
前の月とか次の月や翌年、前月はとりあえずスルー。
とりあえず、日付群の中から1つ選ぶということでListBoxでいいだろう。
日 | 月 | 火 | 水 | 木 | 金 | 土 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 1 | 2 |
こんな感じに表示させたいので、リストボックスのテンプレートをいじる。
カラムが7つのUniformGridを2つほどStackPanelに乗せる。
1つ目のGridには、日~土までを。
2つ目のGridはIsItemsHostをTrueにして、ListBoxのItemを表示するのに使う。
<ListBox Name="calendar">
<ListBox.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextAlignment" Value="Right" />
</Style>
</ListBox.Resources>
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<StackPanel>
<UniformGrid Columns="7">
<TextBlock Text="日" />
<TextBlock Text="月" />
<TextBlock Text="火" />
<TextBlock Text="水" />
<TextBlock Text="木" />
<TextBlock Text="金" />
<TextBlock Text="土" />
</UniformGrid>
<UniformGrid Columns="7" IsItemsHost="True">
</UniformGrid>
</StackPanel>
</ControlTemplate>
</ListBox.Template>
</ListBox>
これをWindowに乗せると、こんな感じになる。
とりあえずいい感じ。
んで、ListBoxのDataContextにDateTimeを設定すると、その月のカレンダーが表示されるようにしてみようと思う。
というわけで、DateTimeをもとに、その月(と間を埋めるために必要な前月と翌月の数日)の日付の列挙に変換するコンバータを用意する。
namespace WpfCalendar
{
public class DateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
var days = (from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month))
select new DateTime(date.Year, date.Month, day)).ToList();
var first = days.First();
for (int i = 0; i < (int)first.DayOfWeek; i++)
{
days.Insert(0, days.First().AddDays(-1));
}
var last = days.Last();
for (int i = 0; i < 6 - (int)last.DayOfWeek; i++)
{
days.Add(days.Last().AddDays(1));
}
return days;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
このコンバータを使ってListBoxのItemsSourceにDataContextをバインドする。
<ListBox Name="calendar">
<ListBox.ItemsSource>
<Binding>
<Binding.Converter>
<c:DateTimeConverter xmlns:c="clr-namespace:WpfCalendar" />
</Binding.Converter>
</Binding>
</ListBox.ItemsSource>
<ListBox.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextAlignment" Value="Right" />
</Style>
</ListBox.Resources>
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<StackPanel>
<UniformGrid Columns="7">
<TextBlock Text="日" />
<TextBlock Text="月" />
<TextBlock Text="火" />
<TextBlock Text="水" />
<TextBlock Text="木" />
<TextBlock Text="金" />
<TextBlock Text="土" />
</UniformGrid>
<UniformGrid Columns="7" IsItemsHost="True">
</UniformGrid>
</StackPanel>
</ControlTemplate>
</ListBox.Template>
</ListBox>
この時点で実行すると、↓のような感じになる。
段々それっぽくなってきてるかな。
後は、ListBoxのItemTemplateとItemContainerStyleで日付部分を右詰めで表示してあげる。
<ListBox Name="calendar"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemsSource>
<Binding>
<Binding.Converter>
<c:DateTimeConverter xmlns:c="clr-namespace:WpfCalendar" />
</Binding.Converter>
</Binding>
</ListBox.ItemsSource>
<ListBox.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextAlignment" Value="Right" />
</Style>
</ListBox.Resources>
<ListBox.Template>
<ControlTemplate TargetType="{x:Type ListBox}">
<StackPanel>
<UniformGrid Columns="7">
<TextBlock Text="日" />
<TextBlock Text="月" />
<TextBlock Text="火" />
<TextBlock Text="水" />
<TextBlock Text="木" />
<TextBlock Text="金" />
<TextBlock Text="土" />
</UniformGrid>
<UniformGrid Columns="7" IsItemsHost="True">
</UniformGrid>
</StackPanel>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type sys:DateTime}">
<TextBlock Text="{Binding Day}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
実行すると、カレンダーっぽくなってきた。
今日は眠いのでここまで。