none
複数ページにわたるリストの印刷 RRS feed

  • 質問

  • お世話になります。

    以下のような形式(例:CDリスト)の一覧表を、複数ページにわたって印刷をしようと試みてます。

    --------------------------------------------
        鈴木陽一の所持リスト    2015/12/14        ←ヘッダー
        ----------------------------------
        Bach, J.S.: Partitas & Sonatas            ←ボディ
        Jascha Heifetz: the Complete Album Collection
        Spectre 007
        Amor & Pasion
        Karajan, Beethoven: The Symphonies
        Murray Perahia-the First 40 Years
        The Christmas Album
            (以下略)
        --------------------------------------
                    Page.1                        ←フッター
    ----------------------------------------

    書籍を参考に ViewをXAML(UserControl)で作成し、Canvasに要素を並べてViewModelにバインディングしました。

    ViewModelには、それぞれ印刷要素に対応するプロパティがあり、ボディ対応部分は CD型を定義してそのコレクション(Items)として持っています。印刷する用紙はA4決め打ちで固定行数になるので XAML側ではTextBlockを必要なだけ並べて Text="{Binding Path=Items[0].Title} のようなバインディングにしています。

    以下、その参考にしたコードそのままなのですが(Messanger & Behaviorはまだ使ええていません)

    [CDDocumentViewModel.cs]

    public Action PrintView { get; set; }
    static public void ShowDialog(CDList source)
    {
        CDDocumentViewModelvm = new CDDocumentViewModel(source);
        CDDocument v = new CDDocument();
        v.DataContext = vm;
        vm.PrintView();
    }

    Public CDDocumentViewModel(CDList source)
    {
        this.OwnerName = source.OwnerName;
        this.Items = source.Titles;
    }

    上記 vm.PrintPreviewの中身は XAMLのコードビハインドで
    [CDDocument.xaml]

    public partial class CDDocument : UserControl
    {
        private const double _printMargin = 96;
    
        public CDDocument()
        {
            DataContextChanged += (object sender, DependencyPropertyChangedEventArgs e) =>
            {
                CDDocumentViewModel vm = DataContext as CDDocumentViewModel;
                if (vm == null) throw new InvalidCastException();
    
                vm.PrintView = ExecutePrintThis;
            };
    
            InitializeComponent();
        }
    
        private void ExecutePrintThis()
        {
            PrintDialog dialog = new PrintDialog();
            dialog.MinPage = 1;
            dialog.MaxPage = 1;
            dialog.UserPageRangeEnabled = true;
            dialog.PrintTicket.PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4);
            dialog.PrintTicket.PageOrientation = PageOrientation.Portrait;
    
            bool r = dialog.ShowDialog() ?? false;
            if (!r) return;
    
            double w = dialog.PrintableAreaWidth;
            double h = dialog.PrintableAreaHeight;
            Width = w - (_printMargin * 2);
            Height = h - (_printMargin * 2);
            FixedDocument document = CreateDocument(this, w, h);
            dialog.PrintDocument(document.DocumentPaginator, "ミュージックリスト");
        }
    
        private static FixedDocument CreateDocument(UIElement uiElement, double width, double height)
        {
            FixedPage fp = new FixedPage();
            fp.Width = width;
            fp.Height = height;
            fp.Children.Add(uiElement);
            FixedPage.SetLeft(uiElement, _printMargin);
            FixedPage.SetTop(uiElement, _printMargin);
    
            PageContent page = new PageContent();
            IAddChild p = page as IAddChild;
            p.AddChild(fp);
    
            FixedDocument fd = new FixedDocument();
            fd.Pages.Add(page);
            return fd;
    	}
    }

    これで1ページだけの印刷はできます。
    ここから複数ページの印刷につなげられないかと思い、
    まず、ViewModelはCDのコレクションを持っていてその総数を知っているので、固定行数で分割した総ページ数を

    [CDDocumentViewMode.cs]

    public Action<uint> PrintView { get; set; }
    static public void ShowDialog(Slip source)
    {
        CDDocumenttViewModel vm = new CDDocumenttViewModel(source);
        CDDocumentt v = new CDDocumentt();
        v.DataContext = vm;
        vm.PrintView(vm.MaxPage);
    }

    という感じで、PrintViewに渡してやりました。
    続いてxamlのコードビハインド側も

    private void ExecutePrintThis(uint maxPage)
    {
    	(省略)
    	FixedDocument document = CreateDocument(this, w, h, maxPage);
            dialog.PrintDocument(document.DocumentPaginator, "ミュージックリスト");
    }
    
    private static FixedDocument CreateDocument(UIElement uiElement, double width, double height, uint maxPage)
    {
        FixedDocument fd = new FixedDocument();
        for (int i=1; i<=maxPage; i++)
        {
            FixedPage fp = new FixedPage();
            fp.Width = width;
            fp.Height = height;
            fp.Children.Add(uiElement);
            FixedPage.SetLeft(uiElement, _printMargin);
            FixedPage.SetTop(uiElement, _printMargin);
    
            PageContent page = new PageContent();
            IAddChild p = page as IAddChild;
            p.AddChild(fp);
    
            fd.Pages.Add(page);
        }
        return fd;
    }
    中身はともかく、せめて、複数ページくらいは印刷できるかと思ったのですが、
    CreateDocument()内の

    fp.Children.Add(uiElement);

    で、「指定された要素は、既に別の要素の論理子です。まず接続を切断してください。」のエラーが発生します。もともとはコードビハインド内で this を渡しているので当然といえば当然ですが、ここで詰まってしまいました。

     うまくページが複数印刷に回せれば、

    fd.Pages.Add(page);

    の次に ViewModel側のItemsの中身を1ページ分ずらせば(バインディングは Text="{Binding Path=Items[0].ProductName}" のような形にしてるので)次のデータが印刷できるんじゃないかと都合よく考えていたのですが、そもそも上記エラーが出るし、for を回す中で勝手にItemsの中身をずらしても、そのまま印刷に出てこない気がして…

     どうもこの方向自体ゴールに着かないんじゃないかと思い始めてきました。
    ヘッダー、ボディ、フッターを備えた一覧表のようなものを複数ページに印刷する適切な方法をご存知の方がおられれば具体的にお教えいただけないでしょうか。






    • 編集済み 雷持 2015年12月14日 6:14 コード修正
    2015年12月14日 5:57

回答

  • 単純にページ毎に印刷させたい部分の項目だけを保持させる「印刷ページのモデル」を作るだけでいいです。
    あと、ページ内で繰り返す部分はItemsControlで生成させてしまえば楽です。

    <Page x:Class="WpfApplication1.PrintPage"
          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" 
          xmlns:app="clr-namespace:WpfApplication1"
          Title="ISO A4" Width="790" Height="1122" >
        <Page.Resources>
            <DataTemplate DataType="{x:Type app:CDListPrintBody}">
                <StackPanel>
                    <Grid TextElement.FontSize="20" TextElement.FontWeight="Bold">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="500" />
                            <ColumnDefinition Width="200" />
                        </Grid.ColumnDefinitions>
                        <Border Grid.Column="0" BorderBrush="Black" BorderThickness="1,1,1,1" >
                            <TextBlock Text="ProductName" HorizontalAlignment="Center"/>
                        </Border>
                        <Border Grid.Column="1" BorderBrush="Black" BorderThickness="0,1,1,1" >
                            <TextBlock Text="Price" HorizontalAlignment="Center"/>
                        </Border>
                    </Grid>
                    <ItemsControl ItemsSource="{Binding Path=Items}" TextElement.FontSize="12">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate >
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="500" />
                                        <ColumnDefinition Width="200" />
                                    </Grid.ColumnDefinitions>
                                    <Border Grid.Column="0" BorderBrush="Black" BorderThickness="1,0,1,1" >
                                        <TextBlock Text="{Binding Path=ProductName}" Margin="1" />
                                    </Border>
                                    <Border Grid.Column="1" BorderBrush="Black" BorderThickness="0,0,1,1" >
                                        <TextBlock Text="{Binding Path=Price,StringFormat={}\\{0:N}}" Margin="1" HorizontalAlignment="Right"/>
                                    </Border>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
            </DataTemplate>
    
        </Page.Resources>
    
        <Grid Margin="30">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
    
            <Grid Grid.Row="0" x:Name="headerGrid">
                <Border BorderBrush="Black" BorderThickness="0,1" Margin="0,0,0,10">
                    <ContentPresenter Content="{Binding  Path=Header}" HorizontalAlignment="Center" TextElement.FontSize="20"/>
                </Border>
            </Grid>
    
            <Grid Grid.Row="1" x:Name="body">
                <ContentPresenter Content="{Binding Path=Body}" HorizontalAlignment="Center"/>
            </Grid>
    
            <Grid Grid.Row="2" x:Name="footerGrid">
                <Border BorderBrush="Black" BorderThickness="0,1">
                    <StackPanel Margin="0,10,0,0">
                        <ContentPresenter Content="{Binding  Path=Footer}" HorizontalAlignment="Center"/>
                        <TextBlock HorizontalAlignment="Center" Text="{Binding Path=PageNumber,StringFormat={}Page.{0}}"/>
                    </StackPanel>
                </Border>
            </Grid>
        </Grid>
    </Page>
    namespace WpfApplication1
    {
        /// <summary>app.xaml.cs</summary>
        public partial class App : System.Windows.Application
        {
            protected override void OnStartup(System.Windows.StartupEventArgs e)
            {
                base.OnStartup(e);
    
                var cdmodel = CDViewModel.MakeDummy();
                var pages = CDListPrintPageModel.CreatePages
                    (cdmodel.Items
                    , "所持リスト\t" + System.DateTime.Now.ToString("yyyy/MM/dd")
                    , null);
    
                PrintPageModelBase.Print(pages);
            }
        }
    
        /// <summary>CD</summary>
        class CD
        {
            public string ProductName { get; set; }
            public int Price { get; set; }
        }
    
        /// <summary>通常のViewModelのようなもの</summary>
        class CDViewModel
        {
            public CDViewModel()
            {
                Items = new System.Collections.ObjectModel.ObservableCollection<CD>();
            }
    
            public System.Collections.ObjectModel.ObservableCollection<CD> Items { get; private set; }
    
            /// <summary>ダミーデータを作る</summary>
            public static CDViewModel MakeDummy()
            {
                CDViewModel model = new CDViewModel();
                System.Random rnd = new System.Random();
                for (int i = 0; i < 256; i++)
                {
                    CD cd = new CD();
                    cd.ProductName = "なまえ" + i.ToString();
                    cd.Price = rnd.Next(1000, 5000);
                    model.Items.Add(cd);
                }
                return model;
            }
    
        }
    }
    
    
    namespace WpfApplication1
    {
        using System;
        using System.Collections.Generic;
        using System.Windows.Controls;
        using System.Windows.Documents;
        using System.Printing;
        using System.Windows;
        using System.Linq;
    
        /// <summary>印刷用のページ</summary>
        public partial class PrintPage : Page
        {
            public PrintPage()
            {
                InitializeComponent();
            }
        }
    
        /// <summary>CD一覧印刷の1ページ分のモデル</summary>
        class CDListPrintPageModel : PrintPageModelBase
        {
            /// <summary>1ページの項目数</summary>
            public const int CountPerPage = 50;
    
            public CDListPrintPageModel(string header, string footer, int pageNumber, IEnumerable<CD> list)
            {
                this.Header = header;
                this.Footer = footer;
                this.PageNumber = pageNumber;
                this.Body = new CDListPrintBody() { Items = list };
            }
    
    
            private static IEnumerable<CDListPrintPageModel> CreatePageModels(IEnumerable<CD> cds, string header = null, string footer = null)
            {
                int count = (cds.Count() + CountPerPage - 1) / CountPerPage;
    
                for (int i = 0; i < count; i++)
                {
                    //1ページ分を取り出し
                    var items = cds.Skip(CountPerPage * i).Take(CountPerPage);
                    //印刷用モデルを作る
                    CDListPrintPageModel model = new CDListPrintPageModel(header, footer, i+1, items);
                    yield return model;
                }
            }
    
            /// <summary>一覧から印刷用モデルを順番に生成</summary>
            public static IEnumerable<System.Windows.Controls.Page> CreatePages(IEnumerable<CD> cds, string header = null, string footer = null)
            {
                foreach (CDListPrintPageModel model in CreatePageModels(cds, header, footer))
                {
                    PrintPage pp = new PrintPage();
                    pp.DataContext = model;
                    yield return pp;
                }
            }
        }
    
        /// <summary>CD一覧印刷の本文部分のモデル</summary>
        class CDListPrintBody : ListPrintBodybase<CD>
        {
        }
    
        /// <summary>印刷ページの基本モデル</summary>
        class PrintPageModelBase
        {
            public object Header { get; set; }
            public object Body { get; set; }
            public object Footer { get; set; }
            public int PageNumber { get; set; }
    
            public static void Print(IEnumerable<System.Windows.Controls.Page> pages)
            {
                FixedDocument doc = new FixedDocument();
                foreach (System.Windows.Controls.Page p in pages)
                {
                    FixedPage fp = CreateFixedPage(p);
                    PageContent pc = new PageContent();
                    pc.Child = fp;
                    doc.Pages.Add(pc);
                }
    
                PrintDialog dlg = new System.Windows.Controls.PrintDialog();
    
                if (dlg.ShowDialog() == true)
                {
                    System.Windows.Xps.XpsDocumentWriter xpsdw
                        = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);
    
                    doc.PrintTicket = dlg.PrintTicket;
                    xpsdw.Write(doc, dlg.PrintTicket);
                }
            }
    
            private static FixedPage CreateFixedPage(Page page)
            {
                Action dummy = () => { };
                var disp = System.Windows.Threading.Dispatcher.CurrentDispatcher;
                FixedPage fixpage = new FixedPage();
                {
                    Frame fm = new Frame();
    
                    fm.Content = page;
                    //disp.Invoke(dummy, System.Windows.Threading.DispatcherPriority.Loaded);//バインディングとかいろいろさせる必要がある場合
    
                    FixedPage.SetLeft(fm, 0);
                    FixedPage.SetTop(fm, 0);
    
                    fixpage.Children.Add(fm);
    
                    Size sz = new Size(page.Width, page.Height);
    
                    fixpage.Width = page.Width;
                    fixpage.Height = page.Height;
    
                    fixpage.Measure(sz);
                    fixpage.Arrange(new Rect(new Point(), sz));
                    fixpage.UpdateLayout();
    
    
                }
                return fixpage;
            }
        }
    
        /// <summary>複数項目印刷する印刷ページの本文部分</summary>
        class ListPrintBodybase<T>
        {
            public IEnumerable<T> Items { get; set; }
        }
    }
    

    #最近はPageで帳票つくって印刷しかしてない

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク 雷持 2015年12月16日 5:08
    2015年12月14日 10:59

すべての返信

  • 帳票は帳票ツールに任せた方が楽でしょう。以下を参考にしてみて下さい。

    どの帳票がよい?
    https://social.msdn.microsoft.com/Forums/ja-JP/ead8139f-0ef0-4a76-afc3-ce4cbf51cf4d


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年12月14日 6:32
    モデレータ
  • こんにちは。

    ヘッダーフッターは関係なく、
    1つのエレメントを複数のFixedPageに設定したいということですよね?

    何らかの方法でインスタンスをコピーするしかないんじゃないでしょうか。
    なんとか上記エラーを回避できたとしても同じ参照のエレメントを印刷しているので
    最後に設定したページ内容で指定したページ数印刷される気がします。

    エレメントのコピーは色々方法があるのかもしれませんが、
    XamlWriterを使う方法を試してみました。
    ※簡略化して、エレメントはTextBlockで試してます。
     バインドしたViewModelを変更したいのであればViewModelインスタンスもコピーする必要があるかもしれません。

    private static FixedDocument CreateDocument(UIElement uiElement, double width, double height, uint maxPage)
    {
        FixedDocument fd = new FixedDocument();
        for (int i = 1; i <= maxPage; i++)
        {
            FixedPage fp = new FixedPage();
            fp.Width = width;
            fp.Height = height;
    
            //インスタンスをコピーする
            string childXaml = XamlWriter.Save(uiElement);
            StringReader stringReader = new StringReader(childXaml);
            XmlReader xmlReader = XmlReader.Create(stringReader);
            UIElement clonedChild = (UIElement)XamlReader.Load(xmlReader);
    
            //プロパティなど変更
            ((TextBlock)clonedChild).Text = i.ToString();
    
            fp.Children.Add(clonedChild);
    
            PageContent page = new PageContent();
            IAddChild p = page as IAddChild;
            p.AddChild(fp);
    
            fd.Pages.Add(page);
        }
        return fd;
    }
    

    2015年12月14日 6:48
    モデレータ
  • 単純にページ毎に印刷させたい部分の項目だけを保持させる「印刷ページのモデル」を作るだけでいいです。
    あと、ページ内で繰り返す部分はItemsControlで生成させてしまえば楽です。

    <Page x:Class="WpfApplication1.PrintPage"
          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" 
          xmlns:app="clr-namespace:WpfApplication1"
          Title="ISO A4" Width="790" Height="1122" >
        <Page.Resources>
            <DataTemplate DataType="{x:Type app:CDListPrintBody}">
                <StackPanel>
                    <Grid TextElement.FontSize="20" TextElement.FontWeight="Bold">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="500" />
                            <ColumnDefinition Width="200" />
                        </Grid.ColumnDefinitions>
                        <Border Grid.Column="0" BorderBrush="Black" BorderThickness="1,1,1,1" >
                            <TextBlock Text="ProductName" HorizontalAlignment="Center"/>
                        </Border>
                        <Border Grid.Column="1" BorderBrush="Black" BorderThickness="0,1,1,1" >
                            <TextBlock Text="Price" HorizontalAlignment="Center"/>
                        </Border>
                    </Grid>
                    <ItemsControl ItemsSource="{Binding Path=Items}" TextElement.FontSize="12">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate >
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="500" />
                                        <ColumnDefinition Width="200" />
                                    </Grid.ColumnDefinitions>
                                    <Border Grid.Column="0" BorderBrush="Black" BorderThickness="1,0,1,1" >
                                        <TextBlock Text="{Binding Path=ProductName}" Margin="1" />
                                    </Border>
                                    <Border Grid.Column="1" BorderBrush="Black" BorderThickness="0,0,1,1" >
                                        <TextBlock Text="{Binding Path=Price,StringFormat={}\\{0:N}}" Margin="1" HorizontalAlignment="Right"/>
                                    </Border>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
            </DataTemplate>
    
        </Page.Resources>
    
        <Grid Margin="30">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
    
            <Grid Grid.Row="0" x:Name="headerGrid">
                <Border BorderBrush="Black" BorderThickness="0,1" Margin="0,0,0,10">
                    <ContentPresenter Content="{Binding  Path=Header}" HorizontalAlignment="Center" TextElement.FontSize="20"/>
                </Border>
            </Grid>
    
            <Grid Grid.Row="1" x:Name="body">
                <ContentPresenter Content="{Binding Path=Body}" HorizontalAlignment="Center"/>
            </Grid>
    
            <Grid Grid.Row="2" x:Name="footerGrid">
                <Border BorderBrush="Black" BorderThickness="0,1">
                    <StackPanel Margin="0,10,0,0">
                        <ContentPresenter Content="{Binding  Path=Footer}" HorizontalAlignment="Center"/>
                        <TextBlock HorizontalAlignment="Center" Text="{Binding Path=PageNumber,StringFormat={}Page.{0}}"/>
                    </StackPanel>
                </Border>
            </Grid>
        </Grid>
    </Page>
    namespace WpfApplication1
    {
        /// <summary>app.xaml.cs</summary>
        public partial class App : System.Windows.Application
        {
            protected override void OnStartup(System.Windows.StartupEventArgs e)
            {
                base.OnStartup(e);
    
                var cdmodel = CDViewModel.MakeDummy();
                var pages = CDListPrintPageModel.CreatePages
                    (cdmodel.Items
                    , "所持リスト\t" + System.DateTime.Now.ToString("yyyy/MM/dd")
                    , null);
    
                PrintPageModelBase.Print(pages);
            }
        }
    
        /// <summary>CD</summary>
        class CD
        {
            public string ProductName { get; set; }
            public int Price { get; set; }
        }
    
        /// <summary>通常のViewModelのようなもの</summary>
        class CDViewModel
        {
            public CDViewModel()
            {
                Items = new System.Collections.ObjectModel.ObservableCollection<CD>();
            }
    
            public System.Collections.ObjectModel.ObservableCollection<CD> Items { get; private set; }
    
            /// <summary>ダミーデータを作る</summary>
            public static CDViewModel MakeDummy()
            {
                CDViewModel model = new CDViewModel();
                System.Random rnd = new System.Random();
                for (int i = 0; i < 256; i++)
                {
                    CD cd = new CD();
                    cd.ProductName = "なまえ" + i.ToString();
                    cd.Price = rnd.Next(1000, 5000);
                    model.Items.Add(cd);
                }
                return model;
            }
    
        }
    }
    
    
    namespace WpfApplication1
    {
        using System;
        using System.Collections.Generic;
        using System.Windows.Controls;
        using System.Windows.Documents;
        using System.Printing;
        using System.Windows;
        using System.Linq;
    
        /// <summary>印刷用のページ</summary>
        public partial class PrintPage : Page
        {
            public PrintPage()
            {
                InitializeComponent();
            }
        }
    
        /// <summary>CD一覧印刷の1ページ分のモデル</summary>
        class CDListPrintPageModel : PrintPageModelBase
        {
            /// <summary>1ページの項目数</summary>
            public const int CountPerPage = 50;
    
            public CDListPrintPageModel(string header, string footer, int pageNumber, IEnumerable<CD> list)
            {
                this.Header = header;
                this.Footer = footer;
                this.PageNumber = pageNumber;
                this.Body = new CDListPrintBody() { Items = list };
            }
    
    
            private static IEnumerable<CDListPrintPageModel> CreatePageModels(IEnumerable<CD> cds, string header = null, string footer = null)
            {
                int count = (cds.Count() + CountPerPage - 1) / CountPerPage;
    
                for (int i = 0; i < count; i++)
                {
                    //1ページ分を取り出し
                    var items = cds.Skip(CountPerPage * i).Take(CountPerPage);
                    //印刷用モデルを作る
                    CDListPrintPageModel model = new CDListPrintPageModel(header, footer, i+1, items);
                    yield return model;
                }
            }
    
            /// <summary>一覧から印刷用モデルを順番に生成</summary>
            public static IEnumerable<System.Windows.Controls.Page> CreatePages(IEnumerable<CD> cds, string header = null, string footer = null)
            {
                foreach (CDListPrintPageModel model in CreatePageModels(cds, header, footer))
                {
                    PrintPage pp = new PrintPage();
                    pp.DataContext = model;
                    yield return pp;
                }
            }
        }
    
        /// <summary>CD一覧印刷の本文部分のモデル</summary>
        class CDListPrintBody : ListPrintBodybase<CD>
        {
        }
    
        /// <summary>印刷ページの基本モデル</summary>
        class PrintPageModelBase
        {
            public object Header { get; set; }
            public object Body { get; set; }
            public object Footer { get; set; }
            public int PageNumber { get; set; }
    
            public static void Print(IEnumerable<System.Windows.Controls.Page> pages)
            {
                FixedDocument doc = new FixedDocument();
                foreach (System.Windows.Controls.Page p in pages)
                {
                    FixedPage fp = CreateFixedPage(p);
                    PageContent pc = new PageContent();
                    pc.Child = fp;
                    doc.Pages.Add(pc);
                }
    
                PrintDialog dlg = new System.Windows.Controls.PrintDialog();
    
                if (dlg.ShowDialog() == true)
                {
                    System.Windows.Xps.XpsDocumentWriter xpsdw
                        = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);
    
                    doc.PrintTicket = dlg.PrintTicket;
                    xpsdw.Write(doc, dlg.PrintTicket);
                }
            }
    
            private static FixedPage CreateFixedPage(Page page)
            {
                Action dummy = () => { };
                var disp = System.Windows.Threading.Dispatcher.CurrentDispatcher;
                FixedPage fixpage = new FixedPage();
                {
                    Frame fm = new Frame();
    
                    fm.Content = page;
                    //disp.Invoke(dummy, System.Windows.Threading.DispatcherPriority.Loaded);//バインディングとかいろいろさせる必要がある場合
    
                    FixedPage.SetLeft(fm, 0);
                    FixedPage.SetTop(fm, 0);
    
                    fixpage.Children.Add(fm);
    
                    Size sz = new Size(page.Width, page.Height);
    
                    fixpage.Width = page.Width;
                    fixpage.Height = page.Height;
    
                    fixpage.Measure(sz);
                    fixpage.Arrange(new Rect(new Point(), sz));
                    fixpage.UpdateLayout();
    
    
                }
                return fixpage;
            }
        }
    
        /// <summary>複数項目印刷する印刷ページの本文部分</summary>
        class ListPrintBodybase<T>
        {
            public IEnumerable<T> Items { get; set; }
        }
    }
    

    #最近はPageで帳票つくって印刷しかしてない

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク 雷持 2015年12月16日 5:08
    2015年12月14日 10:59
  • 皆さん、コメントをありがとうございます。

    >Tak1wa さん

     試してみましたが、やはりViewModelとバインドした状態では同じ内容が複数ページで出てくるだけでした。

    ループの中でViewModelの状態を変化させてClonedChildのDataContextに再設定とかしたのですが同じでした。この方向でいくなら仰るようにViewModelインスタンスもコピーする必要があるかもしれません。

    >trapemiya さん

     と、なると帳票ツールになるのかと紹介いただいたリンクを読ませていただきました。無料でかつ標準的に使えるという点では Microsoft Reportがいいのかも知れません。 情報がないか探してみたいと思います。

    >gekka さん

    「#最近はPageで帳票つくって印刷しかしてない」 なんか心強い!

    シンプルな一覧表ならxamlとコードでなんとかできるのではないかと思っていたので、確認は明日以降になりますが、動かして動作を追ってみたいと思います。 


    • 編集済み 雷持 2015年12月14日 14:00
    2015年12月14日 13:59
  • gekka さん>

    >#最近はPageで帳票つくって印刷しかしてない

    この発想は私にはなかったなー。確かにWPFですからね。Excelで帳票を作っていた頃を思い出しましたが、私にとってはそれの後継的な感じがしました。確かにありだなーと思いました。今後の参考にさせていただきます。情報ありがとうございます。

    雷持 さん>

    >無料でかつ標準的に使えるという点では Microsoft Reportがいいのかも知れません。 情報がないか探してみたいと思います。

    一応補足ですが、Crystal Reportsも無料です。ただ、最近は最初からVisual Studioにバンドルされていないので、わかりにくいかもしれません。その関係なのか、Visual Studio 2010の頃はインストール時にすんなりいかないことがありましたが、最近はそうでもなかったと記憶しています。私は10年以上Crystal Reportsで開発していますし、特に困っていませんので、今のところ他に目を向けようという感じはないです。良いツールだと思います。
    そのため、Microsoft Reportは実運用で使用したことはありませんが、掲示板等で回答する際に調査したり資料を読む限り、こちらも良いツールだと感じました。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年12月15日 1:47
    モデレータ
  • >gekka さん

    自作のサンプルに組み込んで印刷することができました。ありがとうございます。

        class ListPrintBodybase<T>
        {
            public IEnumerable<T> Items { get; set; }
        }
    

    は、どこからも使われてないようで、代わりに(?) class CDListPrintBody の中身が空だったのでそちらに Itemsを配置すると印刷できました。

        class CDListPrintBody
        {
            public IEnumerable<CD> Items { get; set; }
        }

    質問なのですが
    印刷領域は、PrintPage.xaml のHeight や Width で設定することになるようですが、96/inchで算出しているのでしょうか

    あと、PrintPageModelBase の中に

    //disp.Invoke(dummy, System.Windows.Threading.DispatcherPriority.Loaded);//バインディングとかいろいろさせる必要がある場合

    という部分で、PrintPage.xaml には CDListPrintPageModel をDataContextに設定してバインディングしてあると思うのですがこの状態で「いろいろさせる」というのはどんなケースが想定されているのでしょうか。(とりあえず空っぽのActionがダミーとして渡されてますが)

    参考にしていろいろいじってみたいと思います。


    2015年12月16日 5:22
  •     class ListPrintBodybase<T>
        {
            public IEnumerable<T> Items { get; set; }
        }

    は、どこからも使われてないようで、代わりに(?) class CDListPrintBody の中身が空だったのでそちらに Itemsを配置すると印刷できました。


    class ListPrintBodybase<T>はジェネリッククラスで、CDListBodyはこれをCD型に特化した継承しているので使われていますよ。
    ジェネリック型のまま使ってないのはDataTemplateで識別できるようにするためです。


    印刷領域は、PrintPage.xaml のHeight や Width で設定することになるようですが、96/inchで算出しているのでしょうか

    WPFで使われている長さはDIP(デバイス非依存ピクセル)で、96が1インチになります。
    #私は計算が面倒なのでmmから変換するMarkupExtention作って使ってますが

    //disp.Invoke(dummy, System.Windows.Threading.DispatcherPriority.Loaded);//バインディングとかいろいろさせる必要がある場合

    という部分で、PrintPage.xaml には CDListPrintPageModel をDataContextに設定してバインディングしてあると思うのですがこの状態で「いろいろさせる」というのはどんなケースが想定されているのでしょうか。(とりあえず空っぽのActionがダミーとして渡されてますが)

    単純なコントロールしか配置していないのであればとくに問題は出ないのですが、複雑なコントロールを使っていたりすると最終的な出力結果にならない場合があります。
    たとえばコントロールの配置がすんでから、さらに結果を修正するような処理がある場合は、処理が動く前に印刷に回されてしまうことがあるので。1ページの項目数が可変の場合にレンダリングされた結果から、1ページに収まる項目数を逆算するような処理をしたりとか、スクロールさせて先頭位置を合わせたりとか。
    そういった場合は、Dispatcherの優先度を低いダミー処理をいれておいて必要な済んでから印刷へ進むようにしてます。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2015年12月16日 12:20

  • //disp.Invoke(dummy, System.Windows.Threading.DispatcherPriority.Loaded);//バインディングとかいろいろさせる必要がある場合

    という部分で、PrintPage.xaml には CDListPrintPageModel をDataContextに設定してバインディングしてあると思うのですがこの状態で「いろいろさせる」というのはどんなケースが想定されているのでしょうか。(とりあえず空っぽのActionがダミーとして渡されてますが)

    単純なコントロールしか配置していないのであればとくに問題は出ないのですが、複雑なコントロールを使っていたりすると最終的な出力結果にならない場合があります。
    たとえばコントロールの配置がすんでから、さらに結果を修正するような処理がある場合は、処理が動く前に印刷に回されてしまうことがあるので。1ページの項目数が可変の場合にレンダリングされた結果から、1ページに収まる項目数を逆算するような処理をしたりとか、スクロールさせて先頭位置を合わせたりとか。
    そういった場合は、Dispatcherの優先度を低いダミー処理をいれておいて必要な済んでから印刷へ進むようにしてます。

    そんなケースがあるんですね。 とりあえず現状は単純なコントロール配置がせいいっぱいなので「そういう問題が起こることがある」ことを覚えておきたいと思います。ありがとうございました。


    • 編集済み 雷持 2015年12月18日 4:40
    2015年12月18日 4:39