Как создать XAML FlowDocument с заголовками и нижними колонтитулами страниц при визуализации в XPS?

Я ищу идею чистого общего способа описания повторяющихся верхних и нижних колонтитулов страниц в XAML FlowDocument без какого-либо кода. Он должен отображаться правильно только при визуализации в XPS из C#.

1 ответов


у меня была такая же проблема несколько месяцев назад, и я нашел эти ссылки очень полезными: многостраничные отчеты WPF Часть IV разбиение на страницы http://www.codeproject.com/KB/WPF/PimpedDocumentPaginator.aspx

основной метод, который я использовал, состоял в создании пользовательского пагинатора путем вывода из DynamicDocumentPaginator следующим образом:

internal class HeaderFooterPaginator<THeaderModel, TFooterModel> : DynamicDocumentPaginator where THeaderModel : PageNumberModel, new() where TFooterModel : PageNumberModel, new()
{
...
}

в моем случае, THeaderFooterModel и TFooterModel являются подклассами PageNumberModel введите, как мне нужно, верхний или нижний колонтитул, чтобы показать номер текущей страницы.

public class PageNumberModel
{
    public int PageNumber { get; set; }
}

пользовательский пагинатор делегирует исходному XPS-пагинатору большую часть своей работы, поэтому он сохраняет его в конструкторе.

на THeaderModel и TFooterModel типы позволяют пагинатору извлекать XAML DataTemplates для каждого типа, что позволяет указать макет верхнего и нижнего колонтитулов в XAML, не прибегая к пользовательскому коду чертежа.

в моем коде, верхний и Нижний колонтитулы имеют фиксированный размер, поэтому когда пагинатор создается, он извлекает шаблоны верхнего и нижнего колонтитулов, чтобы определить, сколько места резервировать.

в примере кода в предоставленных ссылках метод, который они используют для резервирования пространства для верхнего и нижнего колонтитулов, заключается в использовании масштабного преобразования для сжатия исходного содержимого. Вместо этого я говорю оригинальному пагинатору использовать уменьшенный размер страницы, а затем добавить страницу, созданную оригинальным пагинатором, в ContainerVisual и установить его Offset. Вы, вероятно, не смогли бы этого сделать, если бы размер верхних и нижних колонтитулов был динамическим, потому что количество страниц будет постоянно меняться.

единственное другое осложнение, которое я могу вспомнить, было то, что вам нужно использовать Dispatcher очередь при добавлении колонтитулов (см. AddHeaderOrFooterToContainerAsync ниже). Привязка данных не работает иначе. Мы немного подрываем модель рендеринга WPF, чтобы заставить это работать.

все это было бы довольно трудно объяснить, не включая код, поэтому я прикрепил пользовательский код визуализации ниже. Я вычеркнул некоторые нерелевантные вещи, поэтому, если он не компилируется, как это, вероятно, поэтому : -)

обратите внимание, что смещение номера страницы передается, потому что наш документ XPS состоит из нескольких разделов FlowDocument, и вызывающий код отслеживает текущий общий номер страницы.

надеюсь, что это помогает!

internal class HeaderFooterPaginator<THeaderModel, TFooterModel> : DynamicDocumentPaginator where THeaderModel : PageNumberModel, new() where TFooterModel : PageNumberModel, new()
{
    private readonly double _footerHeight;
    private readonly DataTemplate _footerTemplate;
    private readonly double _headerHeight;
    private readonly DataTemplate _headerTemplate;
    private Size _newPageSize;
    private Size _originalPageSize;
    private readonly DynamicDocumentPaginator _originalPaginator;
    private readonly int _pageNumberOffset;
    private readonly FlowDocument _paginatorSource;
    private const double HeaderAndFooterMarginHeight = 5;
    private const double HeaderAndFooterMarginWidth = 10;

    public HeaderFooterPaginator(int pageNumberOffset, FlowDocument document)
    {
        if (document == null)
        {
            throw new ArgumentNullException("document");
        }

        _paginatorSource = document;

        if (_paginatorSource == null)
        {
            throw new InvalidOperationException("Could not create a clone of the document being paginated.");
        }

        _originalPaginator = ((IDocumentPaginatorSource) _paginatorSource).DocumentPaginator as DynamicDocumentPaginator;

        if (_originalPaginator == null)
        {
            throw new InvalidOperationException("The paginator must be a DynamicDocumentPaginator.");
        }

        _headerTemplate = GetModelDataTemplate(typeof (THeaderModel));
        _footerTemplate = GetModelDataTemplate(typeof (TFooterModel));

        var headerSize = GetModelContentSize(new THeaderModel { PageNumber = int.MaxValue }, _headerTemplate, _originalPaginator.PageSize);
        var footerSize = GetModelContentSize(new TFooterModel { PageNumber = int.MaxValue }, _footerTemplate, _originalPaginator.PageSize);

        _headerHeight = double.IsInfinity(headerSize.Height) ? 0 : headerSize.Height;
        _footerHeight = double.IsInfinity(footerSize.Height) ? 0 : footerSize.Height;

        _pageNumberOffset = pageNumberOffset;

        SetPageSize(new Size(document.PageWidth, document.PageHeight));
    }

    private void AddHeaderOrFooterToContainerAsync<THeaderOrFooter>(ContainerVisual container, double areaWidth, double areaHeight, Vector areaOffset, FrameworkTemplate template, int displayPageNumber)  where THeaderOrFooter : PageNumberModel, new()
    {
        if (template == null)
        {
            return;
        }
        var visual = GetModelContent(new THeaderOrFooter { PageNumber = displayPageNumber }, template);

        if (visual != null)
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
            {
                visual.Measure(_originalPageSize);
                visual.Arrange(new Rect(0, 0, areaWidth, areaHeight));
                visual.UpdateLayout();

                var headerContainer = new ContainerVisual { Offset = areaOffset };
                headerContainer.Children.Add(visual);
                container.Children.Add(headerContainer);
            }));
        }
    }

    public override void ComputePageCount()
    {
        _originalPaginator.ComputePageCount();
    }

    private static void FlushDispatcher()
    {
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.ApplicationIdle, new DispatcherOperationCallback(delegate { return null; }), null);
    }

    private static FrameworkElement GetModelContent(object dataModel, FrameworkTemplate modelTemplate)
    {
        if (modelTemplate == null)
        {
            return null;
        }

        var content = modelTemplate.LoadContent() as FrameworkElement;
        if (content == null)
        {
            return null;
        }

        content.DataContext = dataModel;

        return content;
    }

    private static Size GetModelContentSize(object dataModel, FrameworkTemplate modelTemplate, Size availableSize)
    {
        var content = GetModelContent(dataModel, modelTemplate);
        if (content == null)
        {
            return Size.Empty;
        }

        FlushDispatcher();
        content.Measure(availableSize);
        return content.DesiredSize;
    }

    private DataTemplate GetModelDataTemplate(Type modelType)
    {
        var key = new DataTemplateKey(modelType);
        return _paginatorSource.TryFindResource(key) as DataTemplate;
    }

    public override ContentPosition GetObjectPosition(object value)
    {
        return _originalPaginator.GetObjectPosition(value);
    }

    public override DocumentPage GetPage(int pageNumber)
    {
        if (!_originalPaginator.IsPageCountValid)
        {
            ComputePageCount();
        }

        var originalPage = _originalPaginator.GetPage(pageNumber);

        var newPage = new ContainerVisual();

        var displayPageNumber = _pageNumberOffset + pageNumber;
        var internalWidth = _originalPageSize.Width - 2*HeaderAndFooterMarginWidth;
        AddHeaderOrFooterToContainerAsync<THeaderModel>(newPage, internalWidth, _headerHeight, new Vector(HeaderAndFooterMarginWidth, HeaderAndFooterMarginHeight), _headerTemplate, displayPageNumber);

        var smallerPage = new ContainerVisual();
        smallerPage.Children.Add(originalPage.Visual);
        smallerPage.Offset = new Vector(HeaderAndFooterMarginWidth, HeaderAndFooterMarginHeight + _headerHeight);
        newPage.Children.Add(smallerPage);

        AddHeaderOrFooterToContainerAsync<TFooterModel>(newPage, internalWidth, _footerHeight, new Vector(HeaderAndFooterMarginWidth, _originalPageSize.Height - HeaderAndFooterMarginHeight - _footerHeight), _footerTemplate, displayPageNumber);

        return new DocumentPage(newPage, _originalPageSize, originalPage.BleedBox, originalPage.ContentBox);
    }

    public override int GetPageNumber(ContentPosition contentPosition)
    {
        return _originalPaginator.GetPageNumber(contentPosition);
    }

    public override ContentPosition GetPagePosition(DocumentPage page)
    {
        return _originalPaginator.GetPagePosition(page);
    }

    private void SetPageSize(Size pageSize)
    {
        _originalPageSize = pageSize;

        // Decrease the available page size by the height of the header and footer. The page is offset by the header height later on.
        var sizeRect = new Rect(pageSize);
        sizeRect.Inflate(-HeaderAndFooterMarginWidth, -(HeaderAndFooterMarginHeight + _footerHeight/2 + _headerHeight/2));

        _originalPaginator.PageSize = _newPageSize = sizeRect.Size;

        // Change page size of the document to the size of the content area
        _paginatorSource.PageHeight = _newPageSize.Height;
        _paginatorSource.PageWidth = _newPageSize.Width;
    }

    public override bool IsPageCountValid
    {
        get { return _originalPaginator.IsPageCountValid; }
    }

    public override int PageCount
    {
        get { return _originalPaginator.PageCount; }
    }

    public override Size PageSize
    {
        get { return _newPageSize; }
        set { SetPageSize(value); }
    }

    public override IDocumentPaginatorSource Source
    {
        get { return _originalPaginator.Source; }
    }
}