Skip to main content

Register a Custom Page in the Report Wizard

  • 11 minutes to read

This document describes how to extend the End-User Report Designer‘s Report Wizard with a custom page that allows you to edit the report page settings. For instance, you can display this page after selecting the report type (for empty and data-bound reports).

wpf-report-wizard-custom-page

[!exampleWPF Report Designer - How to register a custom page in the Report Wizard

Perform the following steps to accomplish this task:

  1. Create a custom page Presenter by inheriting it from the abstract WizardPageBase<TView, TModel> class (from the DevExpress.Data.WizardFramework namespace). Use the TView and TModel type parameters to associate the page Presenter with an appropriate View and Model (they are created later). In this presenter, implement the logic which passes data between the Model and View, specify the next wizard page type and define which page buttons should be available.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Drawing.Printing;
    using System.Linq;
    using DevExpress.Data.WizardFramework;
    using DevExpress.DataAccess.Wizard.Presenters;
    using DevExpress.Mvvm.DataAnnotations;
    using DevExpress.XtraReports.Wizards;
    
    // ...
            where TModel : CustomReportModel { XtraReportModel IWizardPage<XtraReportModel>.Model 
                { get { return Model; } set { Model = (TModel)value; } }
    
            public ChoosePageSettingsPage(IChoosePageSettingsPageView view) : base(view) { }
    
            public override bool FinishEnabled { get { return Model.ReportType == ReportType.Empty; } }
            public override bool MoveNextEnabled { get { return Model.ReportType != ReportType.Empty; } }
    
            public override Type GetNextPageType() { return Model.ReportType == ReportType.Empty 
                    ? null 
                    : typeof(ChooseDataSourceTypePage<XtraReportModel>); }
    
            public override void Begin() {
                View.PaperKind = Model.PaperKind;
                View.Portrait = Model.Portrait;
                View.PageMargins = Model.PageMargins;
            }
    
            public override void Commit() {
                Model.PaperKind = View.PaperKind;
                Model.Portrait = View.Portrait;
                Model.PageMargins = View.PageMargins;
            }
        }
    
  2. Declare an interface which identifies the wizard page View.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Drawing.Printing;
    using System.Linq;
    using DevExpress.Data.WizardFramework;
    using DevExpress.DataAccess.Wizard.Presenters;
    using DevExpress.Mvvm.DataAnnotations;
    using DevExpress.XtraReports.Wizards;
    
    // ...
            PaperKind PaperKind { get; set; }
            bool Portrait { get; set; }
            Margins PageMargins { get; set; }
        }
        [POCOViewModel(ImplementIDataErrorInfo = true)]
    
  3. Create the page ViewModel by inheriting it from the WizardPageBase class (from the DevExpress.Xpf.DataAccess.DataSourceWizard namespace) and implementing the interface declared above. This ViewModel processes data which is displayed in the user interface.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Drawing.Printing;
    using System.Linq;
    using DevExpress.Data.WizardFramework;
    using DevExpress.DataAccess.Wizard.Presenters;
    using DevExpress.Mvvm.DataAnnotations;
    using DevExpress.XtraReports.Wizards;
    
    // ...
            IChoosePageSettingsPageView {
    
            public override string Description { get { return "Choose the required page settings."; } }
    
            public virtual IEnumerable<PaperKindViewInfo> AvailablePaperKinds {get; protected set;}
            public virtual PaperKindViewInfo SelectedPaperKind { get; set; }
    
            public virtual bool Portrait { get; set; }
    
            [Range(0,300)]
            public virtual int LeftMargin { get; set; }
            [Range(0, 300)]
            public virtual int RightMargin { get; set; }
            [Range(0, 300)]
            public virtual int TopMargin { get; set; }
            [Range(0, 300)]
            public virtual int BottomMargin { get; set; }
    
            PaperKind IChoosePageSettingsPageView.PaperKind {
                get { return (PaperKind)SelectedPaperKind.Id; }
                set { SetPaperKind(value); }
            }
            Margins IChoosePageSettingsPageView.PageMargins {
                get { return new Margins(LeftMargin, RightMargin, TopMargin, BottomMargin); }
                set { SetMargins(value); }
            }
    
            public ChoosePageSettingsPageViewModel() {
                var printerSettings = new PrinterSettings();
                AvailablePaperKinds = printerSettings.PaperSizes.Cast<PaperSize>().Select(paperSize => {
                    return new PaperKindViewInfo() {
                        Id = (int)paperSize.Kind,
                        DisplayName = paperSize.PaperName,
                        SizeText = string.Format("{0}x{1}", paperSize.Width, paperSize.Height) };
                }).ToArray();
            }
    
            void SetPaperKind(PaperKind value) {
                var info = AvailablePaperKinds.SingleOrDefault(x => (PaperKind)x.Id == value)
                    ?? AvailablePaperKinds.Single(x => (PaperKind)x.Id == PaperKind.Letter);
                SelectedPaperKind = info;
            }
    
            void SetMargins(Margins value) {
                LeftMargin = value.Left;
                RightMargin = value.Right;
                TopMargin = value.Top;
                BottomMargin = value.Bottom;
            }
        }
    }
    
  4. Write a XAML template with the ViewModel type referenced by a Key to define the page’s visual appearance and layout. The specified Key is used to locate the corresponding template automatically.

    <Application x:Class="CustomReportWizard.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:local="clr-namespace:CustomReportWizard"
                 xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
                 xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
                 xmlns:sys="clr-namespace:System;assembly=mscorlib"
                 xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
                 StartupUri="MainWindow.xaml">
        <Application.Resources>
            <DataTemplate x:Key="{x:Type local:ChoosePageSettingsPageViewModel}">
                <Grid VerticalAlignment="Center">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*" />
                        <ColumnDefinition Width="3*" />
                        <ColumnDefinition Width="1*" />
                    </Grid.ColumnDefinitions>
                    <dxlc:LayoutControl Grid.Column="1">
                        <dxlc:LayoutGroup Orientation="Vertical">
                            <dxlc:LayoutItem Label="Orientation: " LabelVerticalAlignment="Top">
                                <UniformGrid Columns="2" Rows="1" Grid.Column="2" Margin="0,0,0,12">
                                    <RadioButton Content="Portrait" IsChecked="{Binding Portrait, Mode=TwoWay}" />
                                    <RadioButton Content="Landscape" IsChecked="{Binding Portrait, Converter={dxmvvm:BooleanNegationConverter}}"  />
                                    </UniformGrid>
                            </dxlc:LayoutItem>
                            <dxlc:LayoutItem Label="Paper Kind: " LabelVerticalAlignment="Top" >
                                <StackPanel Orientation="Vertical" Grid.Column="2" Grid.Row="2" Margin="0,0,0,12">
                                    <dxe:ComboBoxEdit DisplayMember="DisplayName" IsTextEditable="False" 
                                          ItemsSource="{Binding AvailablePaperKinds}" EditValue="{Binding SelectedPaperKind, UpdateSourceTrigger=PropertyChanged}">
                                        <dxe:ComboBoxEdit.ItemTemplate>
                                            <DataTemplate>
                                                <StackPanel>
                                                    <TextBlock Text="{Binding DisplayName}" />
                                                    <TextBlock Text="{Binding SizeText}" Foreground="LightGray" />
                                                </StackPanel>
                                            </DataTemplate>
                                        </dxe:ComboBoxEdit.ItemTemplate>
                                    </dxe:ComboBoxEdit>
                                    <TextBlock Text="{Binding SelectedPaperKind.SizeText}" Foreground="Gray" />
                                </StackPanel>
                            </dxlc:LayoutItem>
                            <dxlc:LayoutGroup Orientation="Horizontal" >
                                <dxlc:LayoutGroup Orientation="Vertical" >
                                    <dxlc:LayoutItem Label="Left Margin:">
                                        <dxe:SpinEdit EditValueType="{x:Type sys:Int32}" EditValue="{Binding LeftMargin, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
                                    </dxlc:LayoutItem>
                                    <dxlc:LayoutItem Label="Top Margin:" >
                                        <dxe:SpinEdit EditValueType="{x:Type sys:Int32}" EditValue="{Binding TopMargin, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
                                    </dxlc:LayoutItem>
                                </dxlc:LayoutGroup>
                                <dxlc:LayoutGroup Margin="24,0,0,0" ItemLabelsAlignment="Local" Orientation="Vertical" >
                                    <dxlc:LayoutItem Label="Right Margin:" >
                                        <dxe:SpinEdit EditValueType="{x:Type sys:Int32}" EditValue="{Binding RightMargin, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
                                    </dxlc:LayoutItem>
                                    <dxlc:LayoutItem Label="Bottom Margin:" >
                                        <dxe:SpinEdit EditValueType="{x:Type sys:Int32}" EditValue="{Binding BottomMargin, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
                                    </dxlc:LayoutItem>
                                </dxlc:LayoutGroup>
                            </dxlc:LayoutGroup>
                        </dxlc:LayoutGroup>
                    </dxlc:LayoutControl>
                </Grid>
            </DataTemplate>
        </Application.Resources>
    </Application>
    
  5. Create an XtraReportModel class descendant, add custom fields storing the report page settings and override the Equals method to take the added fields into account.

    using DevExpress.XtraReports.Wizards;
    using System.Drawing.Printing;
    
    namespace CustomReportWizard {
        class CustomReportModel : XtraReportModel {
            public PaperKind PaperKind { get; set; }
            public Margins PageMargins { get; set; }
    
            public CustomReportModel() {
                PaperKind = PaperKind.Letter;
                PageMargins = new Margins(100, 100, 100, 100);
            }
            public CustomReportModel(CustomReportModel model) : base(model) {
                PaperKind = model.PaperKind;
                PageMargins = new Margins(model.PageMargins.Left, model.PageMargins.Right, model.PageMargins.Top, model.PageMargins.Bottom);
            }
    
            public override object Clone() {
                return new CustomReportModel(this);
            }
    
            public override bool Equals(object obj) {
                var other = obj as CustomReportModel;
                return other != null
                    && base.Equals(obj)
                    && PaperKind == other.PaperKind
                    && PageMargins == other.PageMargins;
            }
    
            public override int GetHashCode() {
                return base.GetHashCode() ^ PaperKind.GetHashCode() ^ PageMargins.GetHashCode();
            }
        }
    }
    
  6. Override the existing ChooseReportTypePage Presenter to set your custom page as the next page.

    using System;
    using System.Collections.Generic;
    using DevExpress.DataAccess.Sql;
    using DevExpress.DataAccess.Wizard;
    using DevExpress.DataAccess.Wizard.Services;
    using DevExpress.Entity.ProjectModel;
    using DevExpress.XtraReports.Wizards;
    using DevExpress.XtraReports.Wizards.Presenters;
    using DevExpress.XtraReports.Wizards.Views;
    
    namespace CustomReportWizard {
        class CustomChooseReportTypePage : ChooseReportTypePage<XtraReportModel> {
            public CustomChooseReportTypePage(IChooseReportTypePageView view, IConnectionStorageService connectionStorageService,
                DataSourceTypes dataSourceTypes, IWizardRunnerContext context, ISolutionTypesProvider solutionTypesProvider) : 
                base(view, connectionStorageService, dataSourceTypes, context, solutionTypesProvider) { }
    
            public override bool MoveNextEnabled {
                get { return true; }
            }
    
            public override Type GetNextPageType() {
                if(View.ReportType == ReportType.Standard || View.ReportType == ReportType.Empty)
                    return typeof(ChoosePageSettingsPage<CustomReportModel>);
                return base.GetNextPageType();
            }
        }
    }
    
  7. Implement the IWizardCustomizationService interface which provides four wizard customization methods. In this implementation, register the previously created Presenters, ViewModel and Model, and add the logic that builds the report.

    using System;
    using DevExpress.DataAccess;
    using DevExpress.DataAccess.Wizard;
    using DevExpress.DataAccess.Wizard.Model;
    using DevExpress.Xpf.DataAccess.DataSourceWizard;
    using DevExpress.Xpf.Reports.UserDesigner.ReportWizard;
    using DevExpress.XtraReports.UI;
    using DevExpress.XtraReports.Wizards;
    using DevExpress.XtraReports.Wizards.Presenters;
    
    
    namespace CustomReportWizard {
        class WizardCustomizationService : IWizardCustomizationService {
            void IDataSourceWizardCustomizationService.CustomizeDataSourceWizard(DataSourceWizardCustomizationModel customization, 
                ViewModelSourceIntegrityContainer container) { }
    
            void IWizardCustomizationService.CustomizeReportWizard(ReportWizardCustomizationModel customization, 
                ViewModelSourceIntegrityContainer container) {
                customization.Model = new CustomReportModel();
                container.RegisterType<ChoosePageSettingsPage<CustomReportModel>>();
                container.RegisterType<ChooseReportTypePage<XtraReportModel>, CustomChooseReportTypePage>();
                container.RegisterViewModel<IChoosePageSettingsPageView, ChoosePageSettingsPageViewModel>();
            }
    
            bool IDataSourceWizardCustomizationService.TryCreateDataSource(IDataSourceModel model, out object dataSource, 
                out string dataMember) {
                dataSource = null;
                dataMember = null;
                return false;
            }
    
            bool IWizardCustomizationService.TryCreateReport(XtraReportModel model, out XtraReport report) {
                var customModel = model as CustomReportModel;
                if(customModel == null || model.ReportType == ReportType.Template || model.ReportType == ReportType.Label) {
                    report = null;
                    return false;
                }
                IDataComponent dataSource = null;
                string dataMember = null;
                if(customModel.ReportType != ReportType.Empty) {
                    var dataComponentCreator = new DataComponentCreator();
                    dataSource = dataComponentCreator.CreateDataComponent(model);
                    dataMember = dataSource.DataMember;
                }
                var builder = new DevExpress.Xpf.Reports.UserDesigner.ReportWizard.ReportBuilder(dataSource, dataMember);
    
                report = new XtraReport();
                report.PaperKind = customModel.PaperKind;
                report.Margins = customModel.PageMargins;
                builder.Build(report, customModel);
                return true;
            }
        }
    }
    
  8. Assign your custom IWizardCustomizationService implementation to the ReportDesignerBase.ServicesRegistry property as shown below.

    <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:CustomReportWizard"
            xmlns:dxrud="http://schemas.devexpress.com/winfx/2008/xaml/reports/userdesigner" 
            xmlns:dxrudw="http://schemas.devexpress.com/winfx/2008/xaml/reports/userdesigner/wizard"
            xmlns:dxda="http://schemas.devexpress.com/winfx/2008/xaml/dataaccess"
            x:Class="CustomReportWizard.MainWindow" WindowState="Maximized"
            Title="MainWindow" >
        <Grid>
            <dxrud:ReportDesigner>
                <dxrud:ReportDesigner.ServicesRegistry>
                    <dxda:TypeEntry ServiceType="{x:Type dxrudw:IWizardCustomizationService}" ConcreteType="{x:Type local:WizardCustomizationService}" />
                </dxrud:ReportDesigner.ServicesRegistry>
            </dxrud:ReportDesigner>
        </Grid>
    </Window>
    
See Also