GrapeCity.devlog

グレープシティ株式会社のDeveloper Tools〈開発支援ツール〉の、製品のTIPSや発売などに関する最新情報をお届けします。

WPFビューワに現代風デザインのカスタムボタンを追加する

先日公開した記事で、「ActiveReports for .NET」のWPFビューワの外観を、ライブラリを使って現代風にカスタマイズする方法を紹介しました。

このとき、ツールバーへのカスタムボタンの追加方法についても紹介していたのですが、

ついでにカスタムのボタンも追加してみます。今回はボタンの表示だけで、クリックしたときの処理の設定方法はまた別の機会にご紹介したいと思います。

と、ボタンがクリックされた後の処理については説明しませんでした。

今回は先日の記事の続きとして、WPFビューワのツールバーにレポートをPDFとExcelにエクスポートするカスタムボタンを実装する方法を説明したいと思います。

カスタムボタンの追加(おさらい)

WPFビューワをカスタマイズするには、付属のデフォルトテンプレート(DefaultWPFViewerTemplates.xaml)をプロジェクトに追加し、そのXAMLを編集することでカスタマイズを行います。

XAMLなので、コントロールに相当するタグを削除すればコントロールが削除されます。またタグの追加によってコントロールを追加することもできます。詳しくは前回の記事を参照してください。

 DefaultWPFViewerTemplates.xaml(前回の記事より抜粋)
<Separator />
<Button
    Name="PdfExportButton"
    Content="PDFに保存"
    Style="{StaticResource MaterialDesignRaisedButton}" />
<Button
    Name="ExcelExportButton"
    Content="Excelに保存"
    Margin="10,0,0,0"
    Style="{StaticResource MaterialDesignRaisedButton}" />

コマンドの使用

さて、ここからが前回の続きで、追加したコントロールに処理を実装する方法を解説していきます。

WPFは、Windowsフォームと同様に、コードビハインドの方法で処理を記述することも可能です。しかし今回のようにコントロールのソースだけが提供されているという状態ではやや困難です。

WPFでは、ビューと実装を分離するための機能が用意されています。その一つに「コマンド」があります。ここではコマンドを使ってビューワと処理を結びつけます。

コマンドを使用するためには、以下のものを用意します。

  • コマンド(ICommandインターフェイスを実装したクラス)
  • コマンドを返すクラス(※)

(※「コマンドを返すクラス」を使用せずに、直接コマンドをインスタンス化して使用する方法もあります)

コマンドは System.Windows.Input.ICommandインターフェイスを実装したクラスになります。このインターフェイスには2つのメソッドと1つのイベントがあります。

メソッド:

  • Execute:コマンドとして実行したい処理を記述するメソッド
  • CanExecute:Executeの実行が可能かどうかをbool値で返すメソッド

イベント:

  • CanExecuteChanged:CanExecuteの値が変わったときに発生するイベント

作成するクラスは以下です。

class MyCommand : ICommand
{
    public void Execute(object parameter)
    {
        // 実行したい処理
    }
    public bool CanExecute(object parameter)
    {
        // 実行の可否を返す。
    }
    // CanExecuteが変更されたときに立ち上げる。
    public event EventHandler CanExecuteChanged;
}

次に、コマンドを返すクラスを作ります。WPFでは基本的に「プロパティ」で通信が行われるため、コマンドの受け渡しもプロパティで行う必要があります。

namespace MyNameSpace
{
    class MyCommandHandler
    {
        // ICommand の固定されたインスタンス
        private static readonly ICommand myCommand = new MyCommand();

        // ICommand を返すプロパティ
        public ICommand MyCommandProperty
        {
            get {
                return myCommand;
            }
        }
    }
}

次に、XAML側にこれらのクラスを紐付けます。いくつか記述する場所があります。

名前空間を使いたい場合、.xamlファイル冒頭の「ResourceDictionary」に、名前空間をプレフィックスに変換する指定を追加します。

<ResourceDictionary
...
xmlns:myNameSpace="clr-namespace:MyNameSpace">

コマンドを使いたいコントロールやコンテナの「リソース」(Resources)の部分に「コマンドを返すクラス」を記述します。この記述によって、インスタンスとしてXAML側では使うことができます。またインスタンスとしての名称も指定します。ここではappcommandとしています。

<ToolBar ...>

    <ToolBar.Resources>
        ...
        <wpfExport:ExportHandler x:Key="appcommand" />
        ...

そして、実際にコマンドを使うコントロールに、Command属性とCommandParameter属性を追加します。Command属性のPathにはICommandを返すプロパティ名を指定します。

<Button
    Name="MyCommandButton"
    Command="{Binding Source={StaticResource appcommand}, Path=MyCommandProperty}"
    CommandParameter="{Binding (パラメータとして渡したいオブジェクト)}"
    Content="コマンドを実行" />

これによって、コントロールからコマンドが実行できるようになります。

なお、ICommandを返すプロパティがアクセスされるのは実行時ではありません。XAMLが読み込まれコントロールがロードされた時点でアクセスされて、コントロール側にICommandインスタンスが保持されます。このためICommandのインスタンスは確定されたものである必要があります。「コマンドを返すクラス」でICommandを「static readonly」のように変更を許さないようにしたのはそのためでした。

カスタムボタンにコマンドを実装する

前回のプロジェクトを例に、実際にコマンドを追加してみます。

まず、プロジェクトに、コマンドとなるクラスとコマンドを返すクラスを追加します。しかしいちいち両方を追加するのは面倒なので、コマンドを内部クラスとしてみます。

また、前回は「PDFに保存」するボタンと、「Excelに保存」するボタンを追加しました。処理は2つあります。しかしどちらもそう複雑ではなく、やることはほぼ同じなので、まとめてしまいます。

using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
using GrapeCity.ActiveReports.Viewer.Wpf;
using WPFViewer_UI_Customization;

namespace WpfExport
{
    public class ExportHandler
    {
        private enum ExportType { Pdf, Excel }

        // 「PDFに保存」用のICommand
        private static readonly ICommand _pdfExportCommand = new ExportCommand(ExportType.Pdf);
        // 「Excelに保存」用のICommand
        private static readonly ICommand _excelExportCommand = new ExportCommand(ExportType.Excel);

        public ICommand ExcelExport
        {
            get
            {
                return _excelExportCommand;
            }
        }

        public ICommand PdfExport
        {
            get
            {
                return _pdfExportCommand;
            }
        }

        private class ExportCommand : ICommand
        {
            public ExportCommand(ExportType type)
            {
                this.Type = type;
            }

            private readonly ExportType Type;

            public void Execute(object parameter)
            {
                if (parameter == null || !(parameter is Viewer))
                {
                    MessageBox.Show("不正なparameterです");
                    return;
                }

                Viewer viewer = (Viewer)parameter;

                // PDFのときとExcelのときで開くウィンドウを分ける
                if (this.Type == ExportType.Pdf)
                {
                    var dlg = new PdfExportWindow(viewer);
                    dlg.ShowDialog();
                }
                else
                {
                    var dlg = new ExcelExportWindow(viewer);
                    dlg.ShowDialog();
                }
            }

            public bool CanExecute(object parameter)
            {
                return true;
            }

            public event EventHandler CanExecuteChanged;
        }
    }
}

フィールドを2つ、プロパティを2つ用意することで、処理を分けました。

パラメータとしてはWPFビューワを受け取ります。WPFビューワにはExportやRenderというメソッドがあり、現在表示されているレポートをエクスポートすることができます。実際のエクスポートにはこの機能を利用します。

エクスポートの実行は他のウィンドウに任せます。エクスポートには、様々なオプションを指定することが可能です。ここではその一部のオプションを指定してからエクスポートを行うようにしています。
簡単なウィンドウなので、こちらでは単純にコードビハインドで実装しています。

f:id:f_sanjin:20181226170330p:plain
PDFエクスポートのダイアログ
f:id:f_sanjin:20181226170404p:plain
Excelエクスポートのダイアログ
たとえばPDFエクスポートウィンドウは、以下のようなコードになっています。ウィンドウはメインのウィンドウ同様「MahApps」のMetroWindowを継承してデザインを揃えます。また、完了ダイアログもMahApps.Metro.Controls.Dialogsを使用して現代風にカスタマイズしてみます。

 コードビハインド
using System;
using GrapeCity.ActiveReports.Viewer.Wpf;
using GrapeCity.ActiveReports.Export.Pdf.Page;
using GrapeCity.ActiveReports.Rendering.IO;
using Microsoft.Win32;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;

namespace WPFViewer_UI_Customization
{
    /// <summary>
    /// PdfExportWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class PdfExportWindow : MetroWindow
    {
        public PdfExportWindow(Viewer viewer)
        {
            this.Viewer = viewer;
            InitializeComponent();
        }

        private Viewer Viewer;

        private async void ExportButton_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new SaveFileDialog
            {
                Filter = "PDFファイル (*.pdf)|*.pdf|すべてのファイル(*.*)|*",
                FileName = "ActiveReports",
                Title = "保存先のファイルを選択してください"
            };

            if (dialog.ShowDialog() == true)
            {
                // エクスポートの各種設定を行います。
                Settings pdfSetting = new Settings();

                if ((bool)EncryptCheckBox.IsChecked)
                {
                    pdfSetting.Encrypt = true;

                    string password = PasswordTextBox.Password;
                    if (!String.IsNullOrEmpty(password))
                    {
                        pdfSetting.UserPassword = password;
                    }
                }

                // RenderingExtensionを設定して、レポートを描画します。
                PdfRenderingExtension pdfRenderingExtension 
                    = new PdfRenderingExtension();
                FileStreamProvider outputProvider 
                    = new FileStreamProvider(new DirectoryInfo(Path.GetDirectoryName(dialog.FileName)),
                       Path.GetFileNameWithoutExtension(dialog.FileName));

                // 出力ファイルがすでに存在する場合は上書きします。
                outputProvider.OverwriteOutputFile = true;

                Viewer.Render(pdfRenderingExtension, outputProvider, pdfSetting);
                // ダイアログを表示
                await this.ShowMessageAsync("成功", "エクスポートが完了しました。");
                this.Close();
            }
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }
}

イベントで処理を行い、コントロールのプロパティから値を取得しています。Windowsフォームとあまり変わらずに使えるのがわかると思います。

 XAML

対応するXAMLの部分もメインウィンドウ同様、<Window>タグを<metro:MetroWindow>に置き換え、以下の宣言を追加します。

<metro:MetroWindow
...
    xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls"

なお、WPFの場合、デフォルトではコントロールに名前が付いていません。コードからプロパティを取得するためには、XAML中にx:Nameを使って名前属性をつける必要があります。ここではチェックボックスとテキストボックスに名前属性を設定します。

<CheckBox
    x:Name="EncryptCheckBox"
    ...>
    暗号化する
</CheckBox>
<StackPanel Orientation="Horizontal">
    <Label Margin="20" Content="パスワード" />
    <PasswordBox
        x:Name="PasswordTextBox"
        ... />
</StackPanel>

DefaultWPFViewerTemplates.xamlの方では、3箇所に修正を加えています。ボタンの「CommandParameter="{Binding ElementName=viewer1}"」によってWPFビューワ(viewer1)をパラメータとして渡しています。

 先頭付近
<ResourceDictionary
    ...
    xmlns:wpfExport="clr-namespace:WpfExport">
    <!-- C#の名前空間WpfExportを"wpfExport"として登録 -->
 ツールバーのリソース
<ToolBar.Resources>
    <!--  globals  -->
    <Converters:CommandConverter x:Key="CommandConverter" />
    <Converters:ResourceImageConverter x:Key="ResourceImageConverter" />
    <AR:LocalResources x:Key="res" />
    <!-- WpfExport.ExportHandler型の変数を"appcommand"として宣言 -->
    <wpfExport:ExportHandler x:Key="appcommand" />
 ツールバーに追加したボタン
<Button
    Name="PdfExportButton"
    Content="PDFに保存"
    Command="{Binding Source={StaticResource appcommand}, Path=PdfExport}"
    CommandParameter="{Binding ElementName=viewer1}"
    Style="{StaticResource MaterialDesignRaisedButton}" />
<Button
    Name="ExcelExportButton"
    Content="Excelに保存"
    Command="{Binding Source={StaticResource appcommand}, Path=ExcelExport}"
    CommandParameter="{Binding ElementName=viewer1}"
    Margin="10,0,0,0"
    Style="{StaticResource MaterialDesignRaisedButton}" />

実行結果

実行結果は以下です。完了のダイアログを含めて、現代風デザインのカスタムボタンを実装することができました。

f:id:GrapeCity_dev:20181227101038g:plain


作成したサンプルはこちらで公開しています。ActiveReports for .NET 12.0J SP2で作成されています。NuGetパッケージを復元して実行してください。

ダウンロード(zipファイル:37.3KB)


グレープシティのWPFコンポーネント

グレープシティでは今回紹介した「ActiveReports for .NET」以外にも、WPFアプリケーション開発を支援するコンポーネントを多数取り揃えています。

単体での利用はもちろん、組み合わせて利用していただくことで開発効率を飛躍的に向上させることが可能です。

以下のWebページに、弊社のWPF用開発コンポーネントのラインナップや、WPFアプリケーションの開発に役立つ情報をまとめてありますので、ぜひ一度ご覧ください。

  • グレープシティ株式会社のDeveloper Tools〈開発支援ツール〉ではエンジニア経験者を幅広く募集しています。
  • グレープシティ株式会社のDeveloper Tools〈開発支援ツール〉の製品のデモアプリケーションをお試しください。