【WPF】自作カレンダー その2(動的生成)

公開日:2019-06-19
最終更新:2019-06-20
※この記事は外部サイト(https://www.doraxdora.com/blog/2017/10/16/...)からのクロス投稿です

自作カレンダーその2です。
今回は、Xamlではなくコードからカレンダーを生成します。

最終的に年間カレンダー的なものにしようと思うので、
Xamlで定義すると Grid だらけになってしまうし、色々やろうと思ったらやっぱり動的に生成した方がやりやすいような気がするので。

前回のプログラムはこちら。
【WPF】自作カレンダー その1(とりあえず当月を表示)

スタイル定義の変更

まずはスタイルの外出しをします。

StyleDic.xaml


    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
                        xmlns:Mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"  
                        xmlns:local="clr-namespace:CalendarSample.Style">  

        <!-- グループ:通常-->  
        <Style x:Key="gp-normal" TargetType="GroupBox" >  
            <Setter Property="VerticalAlignment" Value="Top" />  
            <Setter Property="HorizontalAlignment" Value="Left" />  
            <Setter Property="Background" Value="#FFFFFFFF" />  
            <Setter Property="Foreground" Value="#FF777777" />  
            <Setter Property="Height" Value="300" />  
            <Setter Property="Width" Value="500" />  
            <Setter Property="Margin" Value="10, 10, 0, 0" />  
        </Style>  

        <!-- グリッド:カレンダー用 -->  
        <Style x:Key="grid-calendar" TargetType="Grid" >  
            <Setter Property="VerticalAlignment" Value="Center" />  
            <Setter Property="HorizontalAlignment" Value="Center" />  
            <Setter Property="Background" Value="#FFFFFFFF" />  
            <Setter Property="Height" Value="258" />  
            <Setter Property="Width" Value="450" />  
            <Setter Property="Margin" Value="0, 0, 0, 0" />  
        </Style>  

        <!-- テキストブロック:日付セル(通常) -->  
        <Style x:Key="txb-date" TargetType="TextBlock">  
            <Setter Property="FontSize" Value="12" />  
            <Setter Property="Foreground" Value="Black" />  
            <Setter Property="Padding" Value="0, 10, 10, 0" />  
            <Setter Property="VerticalAlignment" Value="Top" />  
            <Setter Property="HorizontalAlignment" Value="Right" />  
        </Style>  

        <!-- テキストブロック:日付セル(土曜) -->  
        <Style x:Key="txb-date-sat" TargetType="TextBlock" BasedOn="{StaticResource txb-date}">  
            <Setter Property="Foreground" Value="Blue" />  
        </Style>  

        <!-- テキストブロック:日付セル(日曜) -->  
        <Style x:Key="txb-date-sun" TargetType="TextBlock" BasedOn="{StaticResource txb-date}">  
            <Setter Property="Foreground" Value="Red" />  
        </Style>  

        <!-- 四角形:外枠用 -->  
        <Style x:Key="rec-border" TargetType="Rectangle">  
            <Setter Property="Stroke" Value="Black" />  
            <Setter Property="StrokeThickness" Value="1" />  
            <Setter Property="Grid.ColumnSpan" Value="7" />  
            <Setter Property="Grid.RowSpan" Value="7" />  
        </Style>  

        <!-- 四角形:下線用 -->  
        <Style x:Key="rec-underlining" TargetType="Rectangle">  
            <Setter Property="Stroke" Value="Black" />  
            <Setter Property="StrokeThickness" Value="1" />  
            <Setter Property="Height" Value="1" />  
            <Setter Property="VerticalAlignment" Value="Bottom" />  
            <Setter Property="Grid.ColumnSpan" Value="7" />  
        </Style>  

        <!-- 四角形:曜日 -->  
        <Style x:Key="rec-week" TargetType="Rectangle">  
            <Setter Property="Fill" Value="#FF8000" />  
            <Setter Property="VerticalAlignment" Value="Stretch" />  
            <Setter Property="HorizontalAlignment" Value="Stretch" />  
            <Setter Property="Grid.Row" Value="0" />  
            <Setter Property="Margin" Value="1, 1, 0, 0" />  
            <Setter Property="Panel.ZIndex" Value="0" />  
        </Style>  

        <!-- 四角形:曜日 -->  
        <Style x:Key="rec-week-sat" TargetType="Rectangle" BasedOn="{StaticResource rec-week}">  
            <Setter Property="Margin" Value="1, 1, 1, 0" />  
        </Style>  

        <!-- 四角形:日付セル(通常) -->  
        <Style x:Key="rec-date" TargetType="Rectangle">  
            <Setter Property="VerticalAlignment" Value="Stretch" />  
            <Setter Property="HorizontalAlignment" Value="Stretch" />  
            <Setter Property="Fill" Value="Transparent" />  
            <Setter Property="Margin" Value="0, -1, -1, 0" />  
            <!-- トリガーを使ってマウスオーバーで背景色を変更する -->  
            <Style.Triggers>  
                <Trigger Property="IsMouseOver" Value="true">  
                    <Trigger.Setters>  
                        <Setter Property="Margin" Value="0, 0, 0, 0" />  
                        <Setter Property="Fill" Value="#60d0ff" />  
                        <Setter Property="Opacity" Value="0.1"/>  
                    </Trigger.Setters>  
                </Trigger>  
            </Style.Triggers>  
        </Style>  

        <!-- 四角形:日付セル(土曜) → 最後の列を微調整-->  
        <Style x:Key="rec-date-sat" TargetType="Rectangle" BasedOn="{StaticResource rec-date}">  
            <Setter Property="Margin" Value="0, -1, 0, 0" />  
        </Style>  

        <!-- 四角形:日付セル(選択) -->  
        <Style x:Key="rec-date-selected" TargetType="Rectangle">  
            <Setter Property="VerticalAlignment" Value="Stretch" />  
            <Setter Property="HorizontalAlignment" Value="Stretch" />  
            <Setter Property="Stroke" Value="Black" />  
            <Setter Property="StrokeDashArray" Value="1 1" />  
            <Setter Property="StrokeThickness" Value="1"/>  
            <Setter Property="Fill" Value="#007acc" />  
            <Setter Property="Opacity" Value="0.2"/>  
        </Style>  

        <!-- ラベル:曜日ヘッダ-->  
        <Style x:Key="lb-week" TargetType="Label">  
            <Setter Property="VerticalAlignment" Value="Center" />  
            <Setter Property="HorizontalAlignment" Value="Center" />  
            <Setter Property="Grid.Row" Value="0" />  
            <Setter Property="FontSize" Value="11" />  
            <Setter Property="Padding" Value="0" />  
        </Style>  

    </ResourceDictionary>  

ポイントは、日付セルの上をマウスオーバーした際と、クリックして選択した際に背景色を変更するところですかね。

画面の変更

画面に年月を選択するコンボボックスと、選択された日付を表示するラベルを追加しました。
後は カレンダー用の Grid がなくなってすっきりしました。

MainWIndow.xaml


<Mah:MetroWindow x:Class="CalendarSample.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        xmlns:local="clr-namespace:CalendarSample"  
        xmlns:Mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"  
        GlowBrush="{DynamicResource AccentColorBrush}"  
        mc:Ignorable="d"  
        WindowStartupLocation="CenterScreen"   
        Title="カレンダーサンプル" Height="400" Width="525">  
    <Window.Resources>  
        <ResourceDictionary Source="/Style/StyleDic.xaml"/>  
    </Window.Resources>  
    <Grid x:Name="MainContainer">  
        <Label x:Name="lbYearMonth" Content="年月:" HorizontalAlignment="Left" Margin="10,20,0,0" VerticalAlignment="Top" />  
        <ComboBox x:Name="cbYearMonth" HorizontalAlignment="Left" Margin="61,20,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="cbYearMonth_SelectionChanged"/>  
        <Grid x:Name="CalendarContainer" HorizontalAlignment="Left" Height="317" Margin="0,53,0,0" VerticalAlignment="Top" Width="517"/>  
        <Label x:Name="lbDate" Content="選択された日付:" HorizontalAlignment="Left" Margin="222,20,0,0" VerticalAlignment="Top"/>  
        <Label x:Name="lbSelectedDate" Content="" HorizontalAlignment="Left" Margin="333,20,0,0" VerticalAlignment="Top"/>  
    </Grid>  
</Mah:MetroWindow>  

プログラム修正

新規クラス追加

新規で、年月コンボボックス用と、日付セル用のクラスを追加します。

MonthInfo.cs


using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  

namespace CalendarSample  
{  
    /// <summary>  
    /// 年月情報  
    /// </summary>  
    public class MonthInfo  
    {  
        /// <summary>  
        /// コンストラクタ  
        /// </summary>  
        /// <param name="yearMonth"></param>  
        public MonthInfo(String yearMonth)  
        {  
            this.YearMonth = yearMonth;  
            this.YearMonthWithKanji = getYear() + "年" + getMonth() + "月";  
        }  

        /// <summary>  
        /// 年月  
        /// </summary>  
        public String YearMonth { set; get; }  

        /// <summary>  
        /// 年月(YYYY年MM月)  
        /// </summary>  
        public String YearMonthWithKanji { set; get; }  

        /// <summary>  
        /// 年を返します.  
        /// </summary>  
        /// <returns></returns>  
        public String getYear()  
        {  
            if (String.IsNullOrEmpty(YearMonth)) return "";  
            return YearMonth.Substring(0, 4);  
        }  

        /// <summary>  
        /// 月を返します.  
        /// </summary>  
        /// <returns></returns>  
        public String getMonth()  
        {  
            if (String.IsNullOrEmpty(YearMonth)) return "";  
            return YearMonth.Substring(4, 2);  
        }  
    }  
}  

DateInfo.cs


using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  

namespace CalendarSample  
{  
    /// <summary>  
    /// 日付情報  
    /// </summary>  
    public class DateInfo : MonthInfo  
    {  
        /// <summary>  
        /// コンストラクタ  
        /// </summary>  
        /// <param name="yearMonth"></param>  
        /// <param name="day"></param>  
        public DateInfo(String yearMonth, String day) : base(yearMonth)  
        {  
            this.YearMonthDay = yearMonth + day;  
            this.Date = new DateTime(int.Parse(getYear()), int.Parse(getMonth()), int.Parse(day));  
        }  

        public DateTime Date { set; get; }  

        /// <summary>  
        /// 年月日  
        /// </summary>  
        public String YearMonthDay { set; get; }  

        /// <summary>  
        /// 年月日(YYYY年MM月DD日)  
        /// </summary>  
        public String getYearMonthDayWithKanji()  
        {  
            return String.Format("{0:yyyy年MM月dd日(ddd)}", Date);  
        }  

        /// <summary>  
        /// 日を返します.  
        /// </summary>  
        /// <returns></returns>  
        public String getDay()  
        {  
            if (String.IsNullOrEmpty(YearMonthDay)) return "";  
            return YearMonthDay.Substring(6, 2);  
        }  
    }  
}  

コードビハインドの修正

年月コンボボックスの項目設定と、カレンダーの生成メソッドを追加したりしました。

MainWindow.xaml.cs

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  

using MahApps.Metro.Controls;  

namespace CalendarSample  
{  
    /// <summary>  
    /// MainWindow.xaml の相互作用ロジック  
    /// </summary>  
    public partial class MainWindow : MetroWindow  
    {  
        private Rectangle selectedRec;  
        private Style selectedRecStyle;  

        public MainWindow()  
        {  
            InitializeComponent();  

            // 前月から6カ月分のリストを作成し、当月を選択させる  
            List<MonthInfo> list = new List<MonthInfo>();  
            DateTime now = DateTime.Now;  
            DateTime dt = now.AddMonths(-1);  
            for (int i = 0; i < 6; i++ )  
            {  
                list.Add(new MonthInfo(String.Format("{0:yyyyMM}", dt)));  
                dt = dt.AddMonths(1);  
            }  
            this.cbYearMonth.ItemsSource = list;  
            this.cbYearMonth.DisplayMemberPath = "YearMonthWithKanji";  
            this.cbYearMonth.SelectedIndex = 1;  

            createCalendar(this.cbYearMonth.SelectedItem as MonthInfo);  
        }  

        /// <summary>  
        /// 年月コンボの選択変更イベントハンドラー.  
        /// </summary>  
        /// <param name="sender"></param>  
        /// <param name="e"></param>  
        private void cbYearMonth_SelectionChanged(object sender, SelectionChangedEventArgs e)  
        {  
            createCalendar(cbYearMonth.SelectedItem as MonthInfo);  
        }  

        /// <summary>  
        /// 指定された年月のカレンダーを作成  
        /// </summary>  
        /// <param name="monthInfo"></param>  
        private void createCalendar(MonthInfo monthInfo) {  

            CalendarContainer.Children.Clear();  

            // グループボックス  
            GroupBox calendarGroup1 = new GroupBox();  
            calendarGroup1.Header = monthInfo.YearMonthWithKanji;  
            calendarGroup1.Style = FindResource("gp-normal") as Style;  

            // グリッド  
            Grid calendarGrid1 = new Grid();  
            calendarGrid1.Style = FindResource("grid-calendar") as Style;  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(20) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });  
            calendarGrid1.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });  

            // グリッド枠線  
            Rectangle border = new Rectangle();  
            border.Style = FindResource("rec-border") as Style;  
            calendarGrid1.Children.Add(border);  

            for (int col = 0; col < 7; col++)  
            {  
                Rectangle week = new Rectangle();  
                week.Style = (col == 6) ? FindResource("rec-week-sat") as Style : FindResource("rec-week") as Style;  
                week.SetValue(Grid.ColumnProperty, col);  
                calendarGrid1.Children.Add(week);  

                Label lbWeek = new Label();  
                lbWeek.Style = FindResource("lb-week") as Style;  
                lbWeek.Content = ("日月火水木金土").Substring(col, 1);  
                lbWeek.SetValue(Grid.ColumnProperty, col);  
                calendarGrid1.Children.Add(lbWeek);  
            }  

            // ヘッダ行下線  
            Rectangle underlining = new Rectangle();  
            underlining.Style = FindResource("rec-underlining") as Style;  
            calendarGrid1.Children.Add(underlining);  

            // グループボックスにグリッドを追加  
            calendarGroup1.Content = calendarGrid1;  
            CalendarContainer.Children.Add(calendarGroup1);  

            // 当月の月初を取得  
            var firstDate = new DateTime(int.Parse(monthInfo.getYear()), int.Parse(monthInfo.getMonth()), 1);  

            // 曜日番号の取得  
            int dayOfWeek = (int)firstDate.DayOfWeek;  

            // 月末を取得  
            int lastDay = firstDate.AddMonths(1).AddDays(-1).Day;  

            // 1日から月末までを走査  
            for (int day = 1; day <= lastDay; day++)  
            {  
                // セル位置  
                int index = (day - 1) + dayOfWeek;  
                // 横位置  
                int x = index % 7;  
                // 縦位置  
                int y = index / 7;  

                // テキストブロックを生成してグリッドに追加  
                var tb = new TextBlock();  
                tb.Text = string.Format("{0}", day);  
                // 土日は文字色を変更する  
                if (x == 0)  
                {  
                    tb.Style = FindResource("txb-date-sun") as Style;  
                }  
                else if (x == 6)  
                {  
                    tb.Style = FindResource("txb-date-sat") as Style;  
                }  
                else  
                {  
                    tb.Style = FindResource("txb-date") as Style;  

                }  
                calendarGrid1.Children.Add(tb);  
                tb.SetValue(Grid.ColumnProperty, x);  
                tb.SetValue(Grid.RowProperty, y + 1);  

                // 四角形を生成してグリッドに追加  
                // セルの枠線などを表示し、イベントをハンドリングする用  
                var rec = new Rectangle();  
                DateInfo dt = new DateInfo(monthInfo.YearMonth, string.Format("{0:00}", day));  
                rec.DataContext = dt;  
                // 枠線を調整  
                rec.Style = (x == 6) ? FindResource("rec-date-sat") as Style : FindResource("rec-date") as Style;  
                // イベント設定  
                rec.MouseLeftButtonDown += date_MouseLeftButtonDown;  
                calendarGrid1.Children.Add(rec);  
                rec.SetValue(Grid.ColumnProperty, x);  
                rec.SetValue(Grid.RowProperty, y + 1);  
            }  
        }  

        /// <summary>  
        /// セル(日)をクリックした際のイベントハンドラ.  
        /// </summary>  
        /// <param name="sender"></param>  
        /// <param name="e"></param>  
        private void date_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
        {  
            // 既に選択されたセルがある場合はスタイルを戻す  
            if (selectedRec != null)  
            {  
                selectedRec.Style = selectedRecStyle;  
            }  

            // 選択されたセルの取得  
            Rectangle rec = sender as Rectangle;  

            // 選択セルの保持  
            selectedRec = rec;  
            selectedRecStyle = rec.Style;  

            // 選択時のスタイルに変更  
            rec.Style = FindResource("rec-date-selected") as Style;  

            // ラベルに日付をセット  
            lbSelectedDate.Content = (rec.DataContext as DateInfo).getYearMonthDayWithKanji();  
        }  
    }  
}  

起動してみる

起動後は当月を表示するようにしました。

日付を選択すると、選択箇所の背景色が変わり、選択された日付が右上に表示されます。

まとめ

なんとなく使えそうな気がしてきました。
バインディングなんかがうまく利用できていませんが、それは追々ということで。

次回は、年間カレンダーにしようかと思います。

ではでは。

記事が少しでもいいなと思ったらクラップを送ってみよう!
0
+1
@doraxdoraの技術ブログ 主に Java, C#, Python, Javascript の記事を載せていく予定。

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする