◆ WPF でダイアログからの FixedDocument の印刷処理
◆ DocumentPaginator を FixedDocument の標準のものから自作のに置き換える
◆ クラス名に FixedDocument が含まれないと例外が出る

最初は信じられなかったですが 本当にクラス名を変えるだけで動いたり動かなかったりします

発生するコード

WPF の印刷関係の処理です
ボタンが押されたら FixedDocument を作ってダイアログを出して ダイアログの印刷ボタンが押されたら印刷する処理です

private void Button_Click(object sender, RoutedEventArgs e)
{
var doc = new FixedDocument();

var texts = new string[]
{
"a",
"b",
"c",
"d",
"e",
};

foreach (var text in texts)
{
var page = new FixedPage();
var label = new Label();
label.Content = text;
page.Children.Add(label);

var page_content = new PageContent();
page_content.Child = page;
doc.Pages.Add(page_content);
}

var dialog = new PrintDialog();
if ((bool)dialog.ShowDialog())
{
dialog.PrintDocument(doc.DocumentPaginator, "foo");
}
}

これは正常に動くコードで プリンターに「Microsoft Print to PDF」を選べば 指定の場所に PDF ファイルを作れます
このコードで実際に印刷処理を行う部分がここです

dialog.PrintDocument(doc.DocumentPaginator, "foo");

doc.DocumentPaginator で FixedDocument の標準の DocumentPaginator を使っています
カスタムするためには この DocumentPaginator を自作のものにする必要があります

そこで DocumentPaginator を継承するクラスを作って

var doc_paginator = new DocPaginator(doc);
dialog.PrintDocument(doc_paginator, "foo");

のようにしたのですが PrintDocument 関数を呼び出したときに

System.Windows.Xps.XpsSerializationException: 'ReachSerialization_FixedPageInPage'

という例外が出てしまいます

これは .NET ランタイムバージョン 5.0.6 のエラーメッセージで .NET Framework だと

FixedPage に別の FixedPage を含めることはできません。

というメッセージが出ました

クラス名によっては動く

最初は普通に DocumentPaginator を継承した DocPaginator の実装になにかの問題があったんだろうと思ってました
しかし 何度見直してもこれといった問題は見当たりません
ググると一応同じ現象が起きるようなのですが これといった解決策まででてきません

標準の DocumentPaginator は doc.DocumentPaginator で取得できるように関連づいていて このプロパティは getter のみなので変更もできません
内部的に doc.DocumentPaginator にアクセスする部分があって 標準以外の DocumentPaginator だとこの問題が起きるのかなと思いました

標準のと同じ実装にしてみる

諦める前の確認として doc.DocumentPaginator で取得できる DocumentPaginator と全く同じ実装にしてみました
Framework 内部のソースコードはここにあります
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Documents/FixedDocumentPaginator.cs
doc.DocumentPaginator で取得できる値の型は FixedDocumentPaginator という型でした

クラス名なども同じにして同じ実装にします
ただし internal でアクセスできないメソッドやプロパティがあるので それらはリフレクションに置き換えます

すると……なんと動きました!

となると やっぱり実装が問題なのでしょうか?

違い

自作の DocumentPaginator は DocumentPaginator の最小限の実装です
FixedDocumentPaginator は継承してるクラスやインターフェースにも違いがあります

動いてる実装から 継承クラスを DocumentPaginator にしてインターフェースも除外します
これによって不要になったメソッド等も削除します

これでもう一度動かしてみると……これでもまだ動きます

この時点で自作のものと実装を比べてみると 書き方や順序や private プロパティの名前の違い等はありますが 同じ実装と言って良い内容でした

クラス名の違いだけ

クラス定義の中身は同じと言っても良いものだったので もうコピペで完全に一緒のものにしました
それでも FixedDocumentPaginator の方は動いています

もう一度 元々の自作の DocPaginator にしてみると 例外が出ます

DocPaginator のクラス名を FixedDocumentPaginator1 にしてみると これでも動きました
FixedDocumentPaginator2 にしても動きます
FooBar にしたら動きません

わけがわからないです

内部処理で FixedDocumentPaginator という文字列がクラス名に含まれているかをチェックしてるんでしょうか……?

PrintDocument メソッドの内部実装を読んでみることにしたのですが XpsDocumentWriter の Write メソッドを呼び出してるというところまでしかわかりませんでした
https://referencesource.microsoft.com/#System.Printing/System/Windows/Xps/XpsDocumentWriter.cs,50

セキュリティ的な理由なのか C# ではないネイティブコードで書かれている部分なのか Write メソッドの実装は公開されてませんでした

ソースコード

試すように最小限にした FixedDocumentPaginator のソースコードです

public class FixedDocumentPaginator1 : DocumentPaginator
{
private FixedDocument _doc { get; }

public FixedDocumentPaginator1(FixedDocument document)
{
this._doc = document;
}

public override DocumentPage GetPage(int pageNumber)
{
return (DocumentPage)typeof(FixedDocument)
.GetMethod("GetPage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.Invoke(this._doc, new object[] { pageNumber });
}

public override bool IsPageCountValid
{
get
{
return (bool)typeof(FixedDocument)
.GetProperty("IsPageCountValid", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(this._doc);
}
}

public override int PageCount
{
get
{
return (int)typeof(FixedDocument)
.GetProperty("PageCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(this._doc);
}
}

public override Size PageSize
{
get
{
return (Size)typeof(FixedDocument)
.GetProperty("PageSize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.GetValue(this._doc);
}
set
{
typeof(FixedDocument)
.GetProperty("PageSize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.SetValue(this._doc, value);
}
}

public override IDocumentPaginatorSource Source
{
get { return this._doc; }
}
}

リフレクションするよりも doc.DocumentPaginator のメソッドやプロパティをプロキシするような作りのほうがよかったかもしれません