人気ブログランキング | 話題のタグを見る

自動改ページ帳票クラスを作ってみました

自動改ページ帳票クラスを作ってみました_a0080437_22155109.png
WPFでコレクションを印刷したいときに、キリの良いところで自動で改ページして、勝手に複数ページに分けてくれるようなものが欲しいなぁと思って、そういうものを作ってみました。
原理的には、こちらの方とほぼ同じ方法ですが、(自分が)使い勝手の良い感じに仕上げています。


PrintPaginatorクラスの概要
機能
レポート形式と単票形式での印刷が可能です。
レポート形式とは、1ページに複数のコレクションを表示するモードです。上の画像のような感じです。
単票形式とは、1ページに1アイテムを表示するモードです。すなわち、コレクション数がそのままページ数になります。
レポート形式の既定の動作は、ページ生成時に1ページに表示できるコレクション数を求めてそれを動的に割り当てます。アイテムの内容によっては高さが変わることもあるので、はみ出した分は次のページから表示されます。
ただ、この動作は逐一レイアウト更新を行って高さを計算しているので、ページが増えるとクッソ重いです。
それの回避策として、1ページに表示する最大コレクション数を固定するようにもできます。固定するには、下のプロパティ一覧にある FixedCollectionCount に1以上の値をセットします。

※2018.10.13 追記
レイアウト更新を必要最小限の回数に抑えたので、ほんのちょっとだけ軽くなりました。
ただ、一度すべてのコレクションを読ませるという処理をやっているので、これがとにかく重いです。1万件とか読ませたらもうヤバイです。死にます。
そういう場合は、コレクション数を決め打ちしたほうがいいです。
そこまで件数が多いと、どちらにしろページ生成するのに時間を食いますけど。

※2018.10.16 追記
DataGridのようにScrollViewerを持っているコントロールの場合は、ViewportHeightから表示行数を読み取るように変更しました。
大量データでもこれで速くなる! と、思ったんですが、まったくもって速くなりませんでした。
というのも、ページの生成自体はすぐに終わるんですが、プレビュー画面に表示されるまでが重たいです。
レンダリング能力の問題なので、もはや、自分の力ではどうにもできません。

帳票の作り方
帳票テンプレートはXAMLでFixedPageを定義してください。
PrintPaginatorオブジェクトはXAMLを外部ファイル、又は、リソースから読み込みます。
リソースから読み込ませたい場合は、帳票XAMLのビルドアクションを「埋め込みリソース」にしてください。
サンプルアプリケーションには、両方の帳票サンプルが含まれていますので参考にしてください。
そして、高さ計算用にページ全体を包含しているUI要素(Gridとか)には「PageConstrol」、コレクションのUI要素(ItemsControlとか)には「CollectionControl」という名前を付けてください。(Nameプロパティです)
ただ、単票、又は、コレクション数を固定にした場合にはこれらの設定は不要です。(高さ計算しないので)

ページに表示できる印刷要素
  • ヘッダー
  • フッター
  • コレクションアイテム
  • 印刷日時
  • ページ番号
  • 総ページ数
もっと増やしたい場合は、ソースコードに好きに書き加えてください。

プロパティ
  • Header ヘッダー要素
  • Footer フッター要素
  • Items コレクション要素
  • ReportXamlPath XAMLのパスかリソース名
  • IsSingleMode 単票モードに切り替え
  • FixedCollectionCount 固定コレクション数(レポート形式で1以上の値を指定することで有効)
  • PageOrientation 印刷向き(既定は縦)
  • PageMediaSize 用紙サイズ(既定はA4)
  • OutputColor カラーモード(既定はモノクロ)
  • CopyCount 印刷部数(既定は1)

メソッド
  • コンストラクタ XAMLファイル、ヘッダー、フッター、コレクションが指定できます
  • Preview 印刷プレビューウィンドウを表示します
  • Print ただちに印刷します
  • GenerateReport FixedDocumentを生成して返します

ご注意
所詮、FixedPageなので用紙サイズを変えてもほとんど意味ありません。
A3で印刷したいなら、結局、A3用のXAMLを作る必要があります。
FlowDocumentとかDocumentPaginatorとかをうまく使えばこんなことしなくていいのかもしれませんが、使い方がさっぱり分かりません。誰か教えてください。
WPFで印刷したいときみんなどうしてるの?

実際の使い方
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
        // レポート形式の表示
private void ReportExecute(object parameter)
{
// サンプルデータの生成
List<ReportSampleClass> items = ReportSampleClassGenerate.Generate(50);
// ヘッダーに表示するもの
decimal totalOvertime = items.Sum(a => a.Overtime);
// ファイル名からPrintPaginatorを作る
PrintPaginator print = new PrintPaginator("帳票/サンプルレポート.xaml", totalOvertime, null, items)
{
// 印刷の向きは横(既定は縦)
PageOrientation = PageOrientation.Landscape,
// 用紙サイズ(既定はA4)
//PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4),
// カラーモード(既定はモノクロ)
//OutputColor = OutputColor.Monochrome,
// 印刷部数(既定は1)
//CopyCount = 1,
// 1ページに収めるコレクション数を固定したいときはFixedCollectionCountプロパティに1以上の値をセットしてください(既定は0)
//FixedCollectionCount = 30,
// 単票形式モード(既定はfalse=レポート形式)
//IsSingleMode = false,
};
// プレビュー
print.Preview();
// 印刷はPrintメソッドにPrintQueueを渡す
//LocalPrintServer printer = new LocalPrintServer();
//print.Print(printer.DefaultPrintQueue);
}



サンプル帳票
  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<FixedPage   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Width="29.7cm" Height="21cm" Language="ja-JP">

<!--ページサイズに合わせる(高さは固定させない)-->
<Grid x:Name="PageControl"
Width="{Binding Width, RelativeSource={RelativeSource AncestorType={x:Type FixedPage}}}"
MinHeight="{Binding Height, RelativeSource={RelativeSource AncestorType={x:Type FixedPage}}}">

<!--ページ余白 左,上,右,下-->
<Grid Margin="0.5cm,1.5cm,0.5cm,0.5cm">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!--伝票名称-->
<TextBlock Grid.Row="0" Text="時間外勤務実績レポート" FontSize="16" FontWeight="Bold" />

<!--発行日-->
<TextBlock Grid.Row="1" Height="0.7cm">
<Run Text="発行日:"/>
<Run Text="{Binding PrintDateTime, StringFormat={}{0:F}}"/>
</TextBlock>

<!--総申請時間-->
<TextBlock Grid.Row="1" Height="0.7cm" Margin="6cm,0,0,0">
<Run Text="総申請時間:"/>
<Run Text="{Binding Header, StringFormat={}{0:N2} h}" FontWeight="Bold"/>
</TextBlock>

<!--コレクションヘッダ-->
<StackPanel Grid.Row="2">
<WrapPanel>
<TextBlock Text="従業員名" Width="110"/>
<TextBlock Text="所属部署" Width="120"/>
<TextBlock Text="申請日時" Width="140"/>
<TextBlock Text="区分" Width="50"/>
<TextBlock Text="勤務日" Width="110"/>
<TextBlock Text="勤務時間" Width="90"/>
<TextBlock Text="休憩時間(h)" Width="70"/>
<TextBlock Text="申請時間(h)" Width="70"/>
<TextBlock Text="業務内容" Width="170"/>
<TextBlock Text="承認日時" Width="140"/>
</WrapPanel>
<Border BorderThickness="0,0,0,1" BorderBrush="Black"/>
</StackPanel>

<!--コレクション-->
<ItemsControl x:Name="CollectionControl" Grid.Row="3" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel Margin="0,1">
<!--従業員名-->
<Viewbox Width="110" StretchDirection="DownOnly" Stretch="Fill" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Text="{Binding Name}"/>
</Viewbox>
<!--所属部署-->
<Viewbox Width="120" StretchDirection="DownOnly" Stretch="Fill" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Text="{Binding Dept}"/>
</Viewbox>
<!--申請日時-->
<TextBlock Text="{Binding RegDate, StringFormat={}{0:F}, ConverterCulture=ja-JP}" Width="140"/>
<!--時間外区分-->
<TextBlock Text="{Binding Category}" Width="50"/>
<!--勤務日-->
<TextBlock Text="{Binding WorkDay, StringFormat={}{0:D}{0:(ddd)}, ConverterCulture=ja-JP}" Width="110"/>
<!--勤務時間-->
<TextBlock Width="90">
<Run Text="{Binding StartTime, StringFormat={}{0:hh}:{0:mm}}"/>
<Run Text="~"/>
<Run Text="{Binding EndTime, StringFormat={}{0:hh}:{0:mm}}"/>
</TextBlock>
<!--休憩時間-->
<TextBlock Text="{Binding BreakTime, StringFormat={}{0:N2}}" Width="70"/>
<!--申請時間-->
<TextBlock Text="{Binding Overtime, StringFormat={}{0:N2}}" Width="70"/>
<!--業務内容-->
<TextBlock Text="{Binding Notes}" Width="170" HorizontalAlignment="Left" TextWrapping="Wrap"/>
<!--承認日時-->
<TextBlock Text="{Binding AppDate, StringFormat={}{0:F}, ConverterCulture=ja-JP}" Width="140"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<!--ページ番号-->
<TextBlock Grid.Row="4" HorizontalAlignment="Center">
<Run Text="{Binding PageNumber, StringFormat={}{0:N0}}"/>
<Run Text=" / "/>
<Run Text="{Binding PageCount, StringFormat={}{0:N0}}"/>
</TextBlock>
</Grid>
</Grid>
</FixedPage>



PrintPaginator.cs
  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Printing;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Resources;

namespace PrintPaginateSample
{
/// <summary>
/// 自動改ページ帳票クラス
///
/// FixedPage直下のUI要素(Gridなど)に「PageControl」という名前を付ける(全体の高さを調べるのに使う)
/// ItemsControlなどのUI要素に「CollectionControl」という名前を付ける(コレクションの高さを調べるのに使う)
/// 単票モードとコレクション数を固定した場合は、これらは不要です。
///
/// 2018.10.15 K_Yaguchi
/// </summary>
class PrintPaginator
{
/// <summary>
/// レポート用ViewModel
/// </summary>
class ReportViewModel
: INotifyPropertyChanged
{
private DateTime _printDateTime;
private int _pageNumber;
private int _pageCount;
private object _header;
private object _footer;
private object _items;

// 印刷日時
public DateTime PrintDateTime { get => _printDateTime; set => SetProperty(ref _printDateTime, value); }
// ページ番号
public int PageNumber { get => _pageNumber; set => SetProperty(ref _pageNumber, value); }
// 総ページ数
public int PageCount { get => _pageCount; set => SetProperty(ref _pageCount, value); }
// ヘッダー
public object Header { get => _header; set => SetProperty(ref _header, value); }
// フッター
public object Footer { get => _footer; set => SetProperty(ref _footer, value); }
// コレクション
public object Items { get => _items; set => SetProperty(ref _items, value); }

// プロパティ変更通知
public event PropertyChangedEventHandler PropertyChanged;
private bool SetProperty<T>(ref T value, T newValue, [CallerMemberName]string propertyName = null)
{
if (!Equals(value, newValue))
{
value = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}

/// <summary>
/// ヘッダー要素
/// </summary>
public object Header { get; set; }
/// <summary>
/// フッター要素
/// </summary>
public object Footer { get; set; }
/// <summary>
/// コレクション要素
/// </summary>
public IReadOnlyCollection<object> Items { get; set; }
/// <summary>
/// 帳票XAMLファイル(FixedPage)のパスまたはリソース名
/// </summary>
public string ReportXamlPath { get; set; }
/// <summary>
/// 1ページに1要素を割り当てる単票スタイルとして印刷する
/// </summary>
public bool IsSingleMode { get; set; }
/// <summary>
/// 1ページに収める固定コレクション数(レポート形式モードの時のみ)
/// </summary>
public int FixedCollectionCount { get; set; }
/// <summary>
/// 印刷方向
/// </summary>
public PageOrientation? PageOrientation { get; set; } = System.Printing.PageOrientation.Portrait;
/// <summary>
/// 用紙サイズ
/// </summary>
public PageMediaSize PageMediaSize { get; set; } = new PageMediaSize(PageMediaSizeName.ISOA4);
/// <summary>
/// カラーモード
/// </summary>
public OutputColor? OutputColor { get; set; } = System.Printing.OutputColor.Monochrome;
/// <summary>
/// 印刷部数
/// </summary>
public int? CopyCount { get; set; } = 1;

/// <summary>
/// 初期化
/// </summary>
public PrintPaginator()
{
}

/// <summary>
/// 初期化
/// </summary>
/// <param name="reportXamlPath">帳票XAMLファイルのパスまたはリソース名</param>
public PrintPaginator(string reportXamlPath)
: this()
{
ReportXamlPath = reportXamlPath;
}

/// <summary>
/// 初期化
/// </summary>
/// <param name="reportXamlPath">帳票XAMLファイル(FixedPage)のパスまたはリソース名</param>
/// <param name="header">ヘッダー要素</param>
/// <param name="footer">フッター要素</param>
/// <param name="items">コレクション要素</param>
public PrintPaginator(string reportXamlPath, object header, object footer, IReadOnlyCollection<object> items)
: this(reportXamlPath)
{
Header = header;
Footer = footer;
Items = items;
}

/// <summary>
/// 印刷プレビュー
/// </summary>
public bool? Preview(string title = "印刷プレビュー")
{
DocumentViewer viewer = new DocumentViewer { Document = GenerateReport() };
viewer.CommandBindings.Add(new CommandBinding(ApplicationCommands.Print, PrintExecute));
return new Window
{
Title = title,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
Content = viewer,
//Content = new DocumentViewer { Document = GenerateReport() }, // これをやるとGenerateReport()内で例外が発生したときに参照残りする
}.ShowDialog();
}

// 印刷プレビューで印刷が押された時の処理
private void PrintExecute(object sender, ExecutedRoutedEventArgs e)
{
e.Handled = true;
PrintDialog dlg = new PrintDialog();

// 用紙向き
dlg.PrintTicket.PageOrientation = PageOrientation;
// 用紙サイズ
dlg.PrintTicket.PageMediaSize = PageMediaSize;
// カラーモード
dlg.PrintTicket.OutputColor = OutputColor;
// 印刷部数
dlg.PrintTicket.CopyCount = CopyCount;

if (dlg.ShowDialog() == true)
{
FixedDocument doc = (FixedDocument)((DocumentViewer)sender).Document;
doc.PrintTicket = dlg.PrintTicket;
PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue).Write(doc);
}
}

/// <summary>
/// 印刷実行
/// </summary>
public void Print(PrintQueue printer)
{
PrintQueue.CreateXpsDocumentWriter(printer).Write(GenerateReport());
}

/// <summary>
/// FixedDocumentを生成する
/// </summary>
public FixedDocument GenerateReport()
{
// 帳票XAMLを読み込む
string xamlData;
if (File.Exists(ReportXamlPath))
{
// パスがファイルだった場合はファイルから読み込む
xamlData = File.ReadAllText(ReportXamlPath);
}
else
{
// パスがファイルではなかった場合はリソースから読み込む
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ReportXamlPath))
{
using (StreamReader sr = new StreamReader(stream))
{
xamlData = sr.ReadToEnd();
}
}
}
// ページを生成して
IList<PageContent> pageContents = IsSingleMode ? GetSinglePageContents(xamlData, DateTime.Now) : GetReportPageContents(xamlData, DateTime.Now);
// 印刷設定を適用したドキュメントに
FixedDocument doc = new FixedDocument()
{
PrintTicket = new PrintTicket
{
PageOrientation = PageOrientation,
PageMediaSize = PageMediaSize,
OutputColor = OutputColor,
CopyCount = CopyCount,
}
};
// ページを追加して
foreach (PageContent p in pageContents)
{
doc.Pages.Add(p);
}
// 返す
return doc;
}

// レポート形式のページをすべて返す
// レポート形式とは、コレクション要素を1ページにたくさん収める形式のこと
// 1ページに収まらない要素は、次ページ以降に印刷される
private List<PageContent> GetReportPageContents(string xamlData, DateTime printDateTime)
{
if (Items == null || Items.Count <= 1)
{
// コレクションが1個以下ならば全部1ページに収める
FixedPage reportPage = GetFixedPage(xamlData);
reportPage.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = 1,
PageCount = 1,
Header = Header,
Footer = Footer,
Items = Items
};
return new List<PageContent> { new PageContent { Child = reportPage } };
}
else if (FixedCollectionCount > 0)
{
// コレクション数が固定されていれば総ページ数を求めて
int pageCount = Items.Count / FixedCollectionCount + (Items.Count % FixedCollectionCount == 0 ? 0 : 1);
// ページごとにコレクションを生成して
List<PageContent> results = new List<PageContent>();
for (int pageNumber = 1, startIndex = 0; pageNumber <= pageCount; pageNumber++)
{
FixedPage reportPage = GetFixedPage(xamlData);
reportPage.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = pageNumber,
PageCount = pageCount,
Header = Header,
Footer = Footer,
Items = Items.Skip(startIndex).Take(FixedCollectionCount),
};
startIndex += FixedCollectionCount;
// PageContentに追加したものを
results.Add(new PageContent { Child = reportPage });
}
// 返す
return results;
}
else
{
// 動的にコレクション数を求めたページを作って
IList<FixedPage> pages = GetCollectionPages(xamlData, printDateTime);
// 総ページ数を取得して
int pageCount = pages.Count;
// ページ番号と総ページ数を書き換えたものを
List<PageContent> results = new List<PageContent>();
for (int i = 0, pageNumber = 1; pageNumber <= pageCount; i++, pageNumber++)
{
((ReportViewModel)pages[i].DataContext).PageNumber = pageNumber;
((ReportViewModel)pages[i].DataContext).PageCount = pageCount;
// PageContentに追加して
results.Add(new PageContent { Child = pages[i] });
}
// 返す
return results;
}
}

// 単票形式のページをすべて返す
// 単票形式とは、コレクションの要素を1ページに1要素のみ収める形式のこと
// コレクションの数がそのままページ数となる
private List<PageContent> GetSinglePageContents(string xamlData, DateTime printDateTime)
{
int pageNumber = 1;
int pageCount = Items.Count;
List<PageContent> results = new List<PageContent>();
// ページごとにコレクションを生成してPageContentに追加したものを
foreach (object item in Items)
{
// 帳票ページの生成
FixedPage page = GetFixedPage(xamlData);
// ページごとのViewModel
page.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = pageNumber,
PageCount = pageCount,
Header = Header,
Footer = Footer,
Items = item,
};
results.Add(new PageContent { Child = page });
}
// 返す
return results;
}

// ページごとに表示できるコレクション数を計算
private List<FixedPage> GetCollectionPages(string xamlData, DateTime printDateTime)
{
// 帳票ページの生成
FixedPage page = GetFixedPage(xamlData);
// 一度すべてのコレクションを読み込ませてみて
page.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = 1,
PageCount = 1,
Header = Header,
Footer = Footer,
Items = Items,
};
// UI要素を再描画し
FixedPageUpdateLayout(page);
// コレクションUI要素と
FrameworkElement collectionControl = (FrameworkElement)page.FindName("CollectionControl");
if (collectionControl == null)
{
throw new Exception("FixedPageにCollectionControlの名前が付けられたUI要素が見つかりません");
}
// ScrollViewerを取得し
ScrollViewer scrollViewer = GetScrollViewer(collectionControl);
if (scrollViewer != null && scrollViewer.CanContentScroll)
{
// ViewportHeightが行数を格納しているならば、その値でコレクション数を決定する
List<FixedPage> results = new List<FixedPage>();
for (int i = 0; i < Items.Count;)
{
int count = (int)scrollViewer.ViewportHeight;
FixedPage result = GetFixedPage(xamlData);
result.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = results.Count,
PageCount = results.Count,
Header = Header,
Footer = Footer,
Items = Items.Skip(i).Take(count).ToList(),
};
results.Add(result);
i += count;
scrollViewer.ScrollToVerticalOffset(i);
FixedPageUpdateLayout(page);
}
// 返す
return results;
}
else
{
// ページUI要素と
FrameworkElement pageControl = (FrameworkElement)page.FindName("PageControl");
if (pageControl == null)
{
throw new Exception("FixedPageにPageControlの名前が付けられたUI要素が見つかりません");
}
// 全体の大きさを取得することで
double pageHeight = page.Height;
double pageControlHeight = pageControl.ActualHeight;
// ページごとに表示できるコレクション数を求める
if (pageHeight >= pageControlHeight)
{
// すべての要素が1ページに収まっている
return new List<FixedPage> { page };
}
else
{
// コレクション全体の高さ
double collectionHeight = collectionControl.ActualHeight;
// コレクションを除いたページ高さ
double headerAndFooter = pageControlHeight - collectionHeight;
// コレクションの印刷領域の高さ
double collectionViewHeight = pageHeight - headerAndFooter;
// コレクション1要素の平均高さ
double itemHeightAvg = collectionHeight / Items.Count;
// 1ページに表示できる平均コレクション数
int itemCountAvg = (int)(collectionViewHeight / itemHeightAvg);
// ページごとにコレクション数を求めながら
List<FixedPage> results = new List<FixedPage>();
for (int i = 0; i < Items.Count;)
{
// どんどんページを作っていって
page = GetFixedPage(xamlData);
page.DataContext = new ReportViewModel
{
PrintDateTime = printDateTime,
PageNumber = results.Count + 1,
PageCount = results.Count + 1,
Header = Header,
Footer = Footer,
};
pageControl = (FrameworkElement)page.FindName("PageControl");
i += GetPageCollectionCount(page, pageControl, itemHeightAvg, itemCountAvg, i);
results.Add(page);
}
// 返す
return results;
}
}
}

// コレクションを増減させて、1ページに収まるコレクション数を求める(ScrollViewerなし)
private int GetPageCollectionCount(FixedPage page, FrameworkElement pageControl, double itemHeightAvg, int itemCountAvg, int startCollectionIndex)
{
// 残りのすべてのコレクション
IReadOnlyList<object> totalItems = Items.Skip(startCollectionIndex).ToList();
// 平均コレクション数をセットし
ObservableCollection<object> items = new ObservableCollection<object>(totalItems.Take(itemCountAvg));
((ReportViewModel)page.DataContext).Items = items;
FixedPageUpdateLayout(page);
if (pageControl.ActualHeight > page.Height)
{
// 全体が大きければ、ひとつずつ要素を減らして1ページに収まるコレクション数を求める(0個のコレクションにはしないこと)
for (int i = items.Count - 1; i > 1; i--)
{
items.RemoveAt(items.Count - 1);
FixedPageUpdateLayout(page);
if (pageControl.ActualHeight <= page.Height)
{
break;
}
}
}
else if (page.Height - pageControl.ActualHeight >= itemHeightAvg)
{
// 全体が小さく平均アイテム高さよりも余裕があれば、ひとつずつ要素を増やして1ページに収まるコレクション数を求める
for (int i = 0; i < totalItems.Count; i++)
{
items.Add(totalItems[i]);
FixedPageUpdateLayout(page);
if (pageControl.ActualHeight > page.Height)
{
// ページ高さを超えたら1つ削る
items.RemoveAt(items.Count - 1);
break;
}
else if (page.Height - pageControl.ActualHeight < itemHeightAvg)
{
// 余白がほぼなくなったら諦める
break;
}
}
}
return items.Count;
}

// ScrollViewerを取得する
private ScrollViewer GetScrollViewer(DependencyObject o)
{
if (o is ScrollViewer) return (ScrollViewer)o;
int count = VisualTreeHelper.GetChildrenCount(o);
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(o, i);
ScrollViewer scrollViewer = GetScrollViewer(child);
if (scrollViewer != null)
{
return scrollViewer;
}
}
return null;
}

// FixedPageを取得する
private FixedPage GetFixedPage(string xamlData)
{
return (FixedPage)XamlReader.Parse(xamlData);
}

// FixedPageのUI要素を再描画する
private void FixedPageUpdateLayout(FixedPage page)
{
Size size = new Size(page.Width, page.Height);
page.Measure(size);
page.Arrange(new Rect(size));
page.UpdateLayout();
}
}
}


by mikaka-flyff | 2018-10-16 19:39