インフラジスティックス・ジャパン株式会社のBlog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

XamCalendar: 日付表示形式のカスタマイズ(和暦表示も可能!?

先日、XamCalendar コントロールについて、日付部分の表示形式をカスタマイズしたい(和暦表示変換するロジックを独自に入れたい)というご相談をいただきました。
XamCalendarでは以下の図のように、日単位、月単位、年単位の表示を行うことができ、各日付や年月をクリックするとより詳細な表示へドリルダウンし、反対にヘッダー部分をクリックするとより広範囲の情報を表示するな挙動となっています。

image

今回は上の図の赤枠の箇所、現在の表示状態を示すキャプション部分と今日日付を選択する今日日付ボタンの日付表示をカスタマイズしていきます。本ポストのタイトルにも書いていますが、(弊社コントロールはプロパティ設定による和暦表示には対応していないものの)和暦表示のカスタムロジックを自前で用意すれば和暦表示を行うこともできます!

また、以下で紹介している再テンプレート方法とValueConverterクラスの利用は、カレンダーコントロールに限らず幅広く応用できるものですので、参考にしていただけるかと思います。

注: こちらのエントリ執筆後に2018 Vol.1 よりカレンダーコントロールで和暦がサポートされました。そのため、より簡単に和暦表示が行えます。ただし、和暦以外のカスタマイズについてはこの内容を引き続きご活用いただけます。

blogs.jp.infragistics.com

   

■再テンプレートを使う!

ウィンドウにXamCalendarを1つ配置した後、早速該当箇所に手を施していきたいと思いますが、ここで再テンプレートという大技を使います。

カスタマイズを加えたい該当の箇所は内部的にはButtonコントロールで構成されているのですが、この部分に適用されているスタイル定義がプロパティとして公開されているわけではないため、このボタンの部分を含むスタイル定義を再定義し変更しなければなりません。(弊社では、製品がデフォルトで使用しているスタイルを再定義して改変することを再テンプレートと呼んでいます。)
弊社のXAML製品は、各コントロールを構成しているXAML要素のスタイル定義を全て公開しており、それらはインストールフォルダの ”DefaultStyles” ディレクトリに格納されています。今回の対象となるXamCalendarの該当箇所のスタイル定義は “インストールフォルダ\DefaultStyles\XamCalendar” 下の generic.shared.xaml にです。このデフォルトスタイルファイルをプロジェクトへ追加し、以下のコードの様にResourceDictionaryに登録することで、アプリケーション内でこの部分のスタイルをオーバライドします。(ファイル全体ではなく、該当の部分をコピー&ペーストで同一リソース内に定義する方法でも問題ありません。)

        <Grid.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="generic.shared.xaml"/> 
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Grid.Resources>

ここまでが、再テンプレートの基本的な流れです。

まずはじめに今日日付ボタンの日付フォーマットを変更します。
TargetType に "igEditors:XamCalendar" が設定されているスタイル(generic.shared.xaml のかなり下の方)が該当のスタイルとなっているので、このスタイルの中の今日日付ボタンの箇所(<!--Today button-->というコメントが入っています。)を以下のように修正します。

              <!--Today button-->
              <Button Grid.Row="1"
		              IsTabStop="False"
		              Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Today}"
		              Style="{TemplateBinding TodayButtonStyleResolved}"
		              Visibility="{TemplateBinding TodayButtonVisibility}">
                <Button.ContentTemplate>
                  <DataTemplate>
                    <TextBlock Text="{Binding StringFormat='yyyy-MM-dd'}"/>
                  </DataTemplate>
                </Button.ContentTemplate>
	              <ig:Commanding.Command>
		              <igEditorsPrim:CalendarCommandSource EventName="Click" CommandType="Today"/>
	              </ig:Commanding.Command>
              </Button>

<Button.ContentTemplate>の箇所を加えており、ボタンの更に内部で利用されているTextBlockのTextプロパティに対してフォーマットを仕込んでいます。(ハイフン区切り ’yyyy-MM-dd’ に変えてみました。)

これで、まずは日付ボタンのフォーマッティングが完了です。

image

■ValueConverterを使う!

続いてはヘッダー部分の日付フォーマットを変更しようと思いますが、こちらは少し複雑です。
安易に先ほどと同様の方法でフォーマッティングを行うと、 月単位 → 年単位 という様に表示単位を変えた時に表示形式が年に変わらず、テストフェーズで怒られます。
XamCalendar には CurrentMode というプロパティがあり、現在の表示モードが月単位なのか、年単位なのか、それ以外のものなのか判断できるので、このケースではこのプロパティを参照してケースバイケースでフォーマッティング方式を変えなければいけません・・・

そこで Converter を利用して、CurrentMode を参照しつつ適切なフォーマッティングを行い変換後の文字列を返すということを実装します。(今回のケースでは複数の値をコンバータクラスへ渡したいため、 IValueConverter ではなく IMultiValueConverter を利用します。)

まずは以下のように IMultiValueConverter インタフェースを継承したクラスを作成します。
クラス内では、
第一引数 Values[0] に、対象の日付を
第二引数 Values[1] に、CurrentMode(現在の表示単位)を
第三引数 Values[2] に、カスタマイズ無しの状態で出力していた値
を利用してフォーマットを行っています。簡単な例として”yyyyねん”といったフォーマッティングを行っていますが、ここで和暦変換のロジック独自に実装すれば和暦表示にすることも可能です。

    public class MyConverter : IMultiValueConverter
    {

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //対象の日付
            DateTime targetDate = (DateTime)values[0];
            //モード
            Infragistics.Controls.Editors.CalendarZoomMode mode = (Infragistics.Controls.Editors.CalendarZoomMode)values[1];
            //本来出力しようとしていた値
            String defaultValue = (string)values[2];

            if (values[0].GetType() == typeof(DateTime))
            {
                if (mode == Infragistics.Controls.Editors.CalendarZoomMode.Months)
                {
                    //月表示の場合
                    return targetDate.ToString("yyyyねん");
                }
                else
                {
                    if (mode == Infragistics.Controls.Editors.CalendarZoomMode.Days)
                    {
                        //非表示の場合
                        return targetDate.ToString("yyyyねんMMがつ");
                    }
                }
                //それ以外
                return defaultValue;
            }
            return defaultValue;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

続いて、定義したConverterを利用して、該当箇所のスタイルに変更を加えます。

名前空間の定義

xmlns:c="clr-namespace:コンバータが定義されているのネームスペース"

リソースの定義

<c:MyConverter x:Key="Formatter" />

TargetType に "igEditors:CalendarItemGroupTitle" が設定されているスタイル(generic.shared.xaml の割りと下の方)が該当のスタイルとなっているので、このスタイルの中のヘッダのボタンの箇所<!--  CalendarItemGroup FirstDayInGroup  -->というコメントが入っています。)を以下のように修正します。

<!--  CalendarItemGroup FirstDayInGroup  -->
<Button x:Name="headerContent"  Grid.Column="1" 
		Foreground="{TemplateBinding ComputedForeground}"
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
		IsTabStop="False"
    igPrim:XamlHelper.Focusable="False"
    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
    Style="{StaticResource BorderlessButtonStyle}"
    igPrim:XamlHelper.SnapsToDevicePixels="{TemplateBinding igPrim:XamlHelper.SnapsToDevicePixels}"
 
    Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type igPrim:CalendarItemGroup}}, Path=FirstDateOfGroup}">
    <button.contenttemplate>
        <datatemplate>
            <textblock>
                <textblock.text>
                    <multibinding converter="{staticresource formatter}">
                        <binding path="."/>
                        <binding relativesource="{relativesource ancestortype={x:type igprim:calendaritemgroup}}" path="currentmode"/>
                        <binding relativesource="{relativesource ancestortype={x:type igprim:calendaritemgroup}}" path="title"/>
                        <binding relativesource="{relativesource ancestortype={x:type igprim:calendaritemgroup}}" path="firstdateofgroup"/>
                        <binding relativesource="{relativesource ancestortype={x:type igprim:calendaritemgroup}}" path="lastdateofgroup"/>
                    </multibinding>
                </textblock.text>
            </textblock>
        </datatemplate>
    </button.contenttemplate>
	<ig:Commanding.Command>
		<igEditorsPrim:CalendarCommandSource EventName="Click" CommandType="ZoomOutCalendarMode" ParameterBinding="{Binding Path=Group, RelativeSource={RelativeSource TemplatedParent}}"/>
	</ig:Commanding.Command>
</Button>

Multibingingの各バインド設定(18行目~21行目)に以下のような表記がありますが、これは Visual Tree 上の先祖のエレメントを辿って calendaritemgroup 取得していて、更に Path の指定で各プロパティを Converter クラスへ渡しています。

<binding relativesource="{relativesource ancestortype={x:type igprim:calendaritemgroup}}" path="currentmode"/>

今回の例では受け手側(Converter)では第4引数(firstdateofgroup)、第5引数(lastdateofgroup)を利用していませんが、これらはそれぞれ表示範囲の最初の日付と最後の日付を渡しているので、もし”平成10年~平成22年”というような表記をする場合には必要になります。

さて、実行してみましょう。

image

Converterで指定した通りにフォーマットが行われました。Converter を使ったフォーマッティングでは自在に返却値を変えられるので和暦表示でも独創的なフォーマットでも何でもOKです!

また、今回はXamCalendarを例に 再テンプレート と Converter の使い方をご紹介しましたが、この2つのカスタマイズ方法を使えば、かなり幅広くカスタマイズできるようになります。是非、今後の実装のご参考にしていただければと思います!

※今回のサンプル(WPF, C#)は こちら から!


[再テンプレートの注意点]
再テンプレートはカスタマイズの自由度がかなり高くなる反面、実装は複雑で、コントロール自体の内部のTree構成やデザイン、イベント等を簡単に変える事ができてしまうため、実装には注意が必要です。
また、製品のバージョンを上げる際には、これらのテンプレートファイル自体に変更が加えられる可能性があるため、再度デフォルトテンプレートを取得し同様のカスタマイズを加える作業が必要となります。

関連リンク:
再テンプレートに関するお知らせ

NetAdvantage は購入前にもトライアル利用が可能です!是非一度お試しください。

NetAdvantage トライアル版ダウンロード

jp.infragistics.com