かずきのBlog

C#やJavaやRubyとメモ書き

目次

Blog 利用状況

ニュース

わんくまBlogが不安定になったため、前に書いてたはてなダイアリーにメインを移動します。
かずきのBlog@Hatena
技術的なネタは、こちらにも、はてなへのリンクという形で掲載しますが、雑多ネタははてなダイアリーだけに掲載することが多いと思います。
コメント
プログラマ的自己紹介
お気に入りのツール/IDE
プロフィール
経歴
広告
アクセサリ

書庫

日記カテゴリ

[C#][WPF]WPFでカレンダー表示する部品って無いんだよね

カレンダーが無いなら作ればいいじゃない?
というわけで作ってみよう。

超汎用的なものを作るのはめんどくさいので、仕様はかなり絞ってみようと思う。

  1. 年月日をDateTime型で指定すると、その月のカレンダーを表示して、指定した日を選択状態にする
  2. 曜日は日曜日はじまり
  3. もちろん選択された日付を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に乗せると、こんな感じになる。

image

とりあえずいい感じ。
んで、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>

この時点で実行すると、↓のような感じになる。

image

段々それっぽくなってきてるかな。
後は、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>

実行すると、カレンダーっぽくなってきた。

image

今日は眠いのでここまで。

投稿日時 : 2008年1月20日 3:12

Feedback

# [C#][WPF]カレンダーを作ってみよう 2008/01/20 12:03 かずきのBlog

[C#][WPF]カレンダーを作ってみよう

# re: [C#][WPF]WPFでカレンダー表示する部品って無いんだよね 2008/01/21 1:28 nsharp

はじめまして。( ゚д゚)ノ

日付の列挙に関して:
このサンプルの場合、クエリー実行は後まわしにした方がロジックがすっきりしますよ。

こんな感じです。

  var date = (DateTime) value;

  // 該当月の1日と末日
  var firstDay = new DateTime(date.Year, date.Month, 1);
  var lastDay = new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));

  // カレンダー表示の開始日と終了日
  var startDate = firstDay.AddDays(DayOfWeek.Sunday - firstDay.DayOfWeek);
  var endDate = lastDay.AddDays(DayOfWeek.Saturday - lastDay.DayOfWeek);

  return Enumerable.Range(0, (endDate - startDate).Days + 1).Select(n => startDate.AddDays(n));

# re: [C#][WPF]WPFでカレンダー表示する部品って無いんだよね 2008/01/21 6:35 かずき

>nsharpさん
確かにそうですね~。
断然見やすくなりますね(^^

# re: [C#][WPF]WPFでカレンダー表示する部品って無いんだよね 2008/01/21 14:02 nsharp

LINQフル活用で、ということでしたら、無限リストを相手にしてみるのもおもしろいかもしれません。

  static IEnumerable<DateTime> Past(this DateTime d) {
    while (true) {
      d = d.AddDays(-1);
      yield return d;
    }
  }

  static IEnumerable<DateTime> Future(this DateTime d) {
    while (true) {
      d = d.AddDays(1);
      yield return d;
    }
  }

んで、

  var date = (DateTime) value;

  // 該当月の1日と末日
  var firstDay = new DateTime(date.Year, date.Month, 1);
  var lastDay = firstDay.AddMonths(1).AddDays(-1);

  var past = date.Past().TakeWhile(d => d >= firstDay || d.DayOfWeek != DayOfWeek.Saturday).Reverse();
  var present = Enumerable.Repeat(date, 1);
  var future = date.Future().TakeWhile(d => d <= lastDay || d.DayOfWeek != DayOfWeek.Sunday);

  return past.Concat(present).Concat(future);

まあ、お遊びということで・・・。

# ???C#????????????????????????????????????????????????1???WPF??? | ???????????? 2011/05/31 10:45 Pingback/TrackBack

???C#????????????????????????????????????????????????1???WPF??? | ????????????

# sac longchamp pas cher 2012/10/17 23:14 http://www.sacslongchamppascher2013.com

You have brought up a very superb details , thankyou for the post.

# uDPIVLJOCkxnXdMQuyy 2014/08/28 1:31 http://crorkz.com/

lA3wCM Hello, you used to write great, but the last several posts have been kinda boring??? I miss your great writings. Past several posts are just a little out of track! come on!

タイトル  
名前  
Url
コメント