Печать динамического Usercontrol с помощью страницы документа в WPF: Datagrid печатается пустым

Я пытаюсь распечатать документ wpf. Я использую documentpaginator для разбивки на страницы и пользовательский элемент управления для создания фактического визуального для печати. Мой пользовательский элемент управления имеет несколько текстовых полей и привязку данных к объектам данных, которые передаются пользовательскому элементу управления во время выполнения.

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

Ниже приведен код класса GetPage класса paginator:

  SaleOrderPage page = new SaleOrderPage(order, pageNumber, controlSize);
        page.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        page.Arrange(new Rect(new Point(0, 0), PageSize));
        page.InitPrint();
        page.UpdateLayout();

        return new DocumentPage(page);

Метод InitPrint инициализирует мой datagrid и привязывает его к источнику данных. Вот код для метода InitPrint.

 public void InitPrint() {
        var sales = ctx.SalesOrders.Where(so => so.Id.Equals(order.Id)).First().Sales.Skip(pageNumber * PageRows).Take(PageRows);
        var printData = sales.Select(x => new {
            Particulars = string.Format("{0} - {1}", x.Item.Brand.Name, x.Item.Shade.Name),
            Quantity = string.Format("{0} {1}", x.Quantity, x.Item.Brand.Unit.Symbol)
        }).ToList();

        dgSales.ItemsSource = printData;
        dgSalesCopy.ItemsSource = printData;
    }

Я считаю, что я пропускаю какой-то шаг при печати на фактический принтер, потому что он работает, как ожидается, на XPS-принтере, но не на физическом принтере.

Спасибо за чтение вопроса,

Нэвин

Ответ 1

Взгляните на tutorial. Похороненный в комментариях - это DocumentPaginator, который был явно создан для печати DataGrid.

public class DocPaginator : DocumentPaginator
{
    #region Fields

    private int _rows;
    private int _columns;
    private int _rowsPerPage;
    private int _columnsPerPage;
    private int _firstColumnWidth;
    private int _horizontalPageCount;
    private int _verticalPageCount;
    private System.Windows.Size _pageSize;        
    private DataGrid _dataGrid;
    private List<string> _columnsList;
    private bool _isFirstColumnLarger;
    private List<Dictionary<int, Pair<int, int>>> _columnSlotsList;
    private string _strTitle;
    private string _strComment;

    #endregion Fields

    #region Constructor

    public DocPaginator(DataGrid dataGrid, System.Windows.Size pageSize, List<string> columnsList, 
                            bool isFirstColumnLarger = false, string strTitle = null, string strComment = null)
    {
        _rows = dataGrid.Items.Count;
        _columns = dataGrid.Columns.Count;            
        _dataGrid = dataGrid;
        _columnsList = columnsList;
        _isFirstColumnLarger = isFirstColumnLarger;
        _strTitle = strTitle;
        _strComment = strComment;

        CalculateFirstColumnWidth();

        PageSize = pageSize;

        _horizontalPageCount = HorizontalPageCount;
        _verticalPageCount = VerticalPageCount;

        GenerateColumnSlots();
    }

    #endregion Constructor

    #region Public Methods

    public override DocumentPage GetPage(int pageNumber)
    {
        double pgNumber = Math.IEEERemainder(pageNumber, _verticalPageCount) >= 0 ?
                            Math.IEEERemainder(pageNumber, _verticalPageCount) :
                                Math.IEEERemainder(pageNumber, _verticalPageCount) + _verticalPageCount;

        int currentRow = Convert.ToInt32(_rowsPerPage * pgNumber);

        var page = new PageElement(currentRow, Math.Min(_rowsPerPage, _rows - currentRow), _dataGrid,
                                        _columnsList, _firstColumnWidth, _isFirstColumnLarger,
                                            GetColumnSlot(pageNumber), _strTitle, _strComment, GetPageNumber(pageNumber))
        {
            Width = PageSize.Width,
            Height = PageSize.Height,
        };

        page.Measure(PageSize);
        page.Arrange(new Rect(new System.Windows.Point(0, 0), PageSize));

        return new DocumentPage(page);
    }



    #endregion Public Methods

    #region Public Properties

    public override bool IsPageCountValid
    { get { return true; } }

    public override int PageCount
    { get { return _horizontalPageCount * _verticalPageCount; } }

    public override System.Windows.Size PageSize
    {
        get { return _pageSize; }
        set
        {
            _pageSize = value;
            _rowsPerPage = PageElement.RowsPerPage(PageSize.Height);
            _columnsPerPage = PageElement.ColumnsPerPage(PageSize.Width, _firstColumnWidth);

            //Can't print anything if you can't fit a row on a page
            Debug.Assert(_rowsPerPage > 0);
        }
    }

    public override IDocumentPaginatorSource Source
    { get { return null; } }

    public int HorizontalPageCount
    {
        get { return (int)Math.Ceiling((_columns - 1) / (double)_columnsPerPage); }
    }

    public int VerticalPageCount
    { 
        get { return (int)Math.Ceiling(_rows / (double)_rowsPerPage); } 
    }

    #endregion Public Properties

    #region Private Methods

    private void CalculateFirstColumnWidth()
    {
        int maxDataLen = 0;

        for (int i = 0; i < _dataGrid.Items.Count; i++)
        {
            List<Object> icol = (List<Object>)_dataGrid.Items[i];
            var largestDataItem = (from d in icol 
                                    select d != null ? d.ToString().Length : 0).Max();
            maxDataLen = maxDataLen < largestDataItem ? largestDataItem : maxDataLen;
        }

        string strDataLen = string.Join("a", new string[maxDataLen + 1]);

        _firstColumnWidth = PageElement.CalculateBitLength(strDataLen, 
                                            new Font("Tahoma", 8, System.Drawing.FontStyle.Regular, GraphicsUnit.Point));
    }

    private void GenerateColumnSlots()
    {
        _columnSlotsList = new List<Dictionary<int, Pair<int, int>>>();

        for (int i = 0; i < _horizontalPageCount; i++)
        {
            Dictionary<int, Pair<int, int>> columnSlot = new Dictionary<int, Pair<int, int>>();
            columnSlot.Add(1, new Pair<int, int>((_columnsPerPage * i) + 1,
                                                    Math.Min(_columnsPerPage * (i + 1), _columns - 1)));

            _columnSlotsList.Add(columnSlot);
        }
    }

    private Dictionary<int, Pair<int, int>> GetColumnSlot(int pageNumber)
    {
        for (int i = 0; i <= _columnSlotsList.Count; i++)
        {
            if (i == Math.Ceiling(Convert.ToDouble(pageNumber / _verticalPageCount)))
                return _columnSlotsList[i];
        }
        return new Dictionary<int, Pair<int, int>>();
    }

    private string GetPageNumber(int intPageNumber)
    {
        string strPageNumber = String.Empty;

        if (_horizontalPageCount == 1)
            strPageNumber = (intPageNumber + 1).ToString();
        else
        { }

        return strPageNumber;
    }

    #endregion Private Methods
}

#region Pair Class

public class Pair<TStart, TEnd>
{
    public Pair(TStart start, TEnd end)
    {
        Start = start;
        End = end;
    }

    public TStart Start { get; set; }
    public TEnd End { get; set; }
}

#endregion
And this is the enhanced version of PageElement:

public class PageElement : UserControl
{
    #region Constants

    private const int PAGE_MARGIN = 40;
    private const int HEADER_HEIGHT = 50;
    private const int LINE_HEIGHT = 20;
    private const int COLUMN_WIDTH = 60;
    private const int HEADER_CHR_WIDTH = 9;
    private const int HEADER_LINE_HEIGHT = 12;
    private const string EXCAPE_CHAR = "\r\n";
    private const string NOT_APPLICAPLE = "N/A";

    #endregion Constants

    #region Fields

    private int _currentRow;
    private int _rows;
    private DataGrid _dataGrid;
    private List<string> _columns;
    private int _firstColumnWidth;        
    private bool _isFirstColumnLarger;
    private static int _columnsPerPage;
    private Dictionary<int, Pair<int, int>> _columnSlot;
    private string _strTitle;
    private string _strComment;
    private string _strPageNumber;        

    #endregion Fields

    #region Constructor

    public PageElement(int currentRow, int rows, DataGrid dataGrid, List<string> columns,
                        int firstColumnWidth, bool isFirstColumnLarger, Dictionary<int, 
                            Pair<int, int>> columnSlot, string strTitle, string strComment, 
                            string strPageNumber)
    {
        Margin = new Thickness(PAGE_MARGIN);
        _currentRow = currentRow;
        _rows = rows;
        _dataGrid = dataGrid;
        _columns = columns;
        _firstColumnWidth = firstColumnWidth;
        _isFirstColumnLarger = isFirstColumnLarger;
        _columnSlot = columnSlot;
        _strTitle = strTitle;
        _strComment = strComment;
        _strPageNumber = strPageNumber;
    }

    #endregion Constructor

    #region Public Static Functions

    public static int RowsPerPage(double height)
    {
        //5 times Line Height deducted for: 1 for Title and Comments each; 2 for Page Number and 1 for Date
        return (int)Math.Floor((height - (2 * PAGE_MARGIN) - HEADER_HEIGHT - (5 * LINE_HEIGHT)) / LINE_HEIGHT);
    }

    public static int ColumnsPerPage(double width, int firstColumnWidth)
    {
        _columnsPerPage = (int)Math.Floor((width - (2 * PAGE_MARGIN) - firstColumnWidth) / COLUMN_WIDTH);
        return _columnsPerPage;
    }

    public static int CalculateBitLength(string strData, d.Font font)
    {
        using (d.Graphics graphics = d.Graphics.FromImage(new d.Bitmap(1, 1)))
        {
            d.SizeF dtsize = graphics.MeasureString(strData, font);

            return Convert.ToInt32(dtsize.Width);
        }  
    }

    #endregion Public Static Functions

    #region Private Functions

    private static FormattedText MakeText(string text, int fontSize)
    {
        return new FormattedText(text, CultureInfo.CurrentCulture,
            FlowDirection.LeftToRight, new Typeface("Tahoma"), fontSize, Brushes.Black);
    }

    #endregion Private Functions

    #region Protected Functions

    protected override void OnRender(DrawingContext dc)
    {
        Point curPoint = new Point(0, 0);
        int beginCounter = 0;
        double intYAxisTracker = 0;
        double TitleHeight = 0;

        //Print Title.
        if (_strTitle != null)
        {
            int intTitleLength = CalculateBitLength(_strTitle, new d.Font("Tahoma", 9, d.FontStyle.Regular));
            curPoint.X = ((Width - (2 * PAGE_MARGIN)) / 2) - (intTitleLength / 2);
            dc.DrawText(MakeText(_strTitle, 9), curPoint);
            curPoint.Y += LINE_HEIGHT;
            curPoint.X = 0;
        }

        //Print Comment.
        if (_strTitle != null)
        {
            int intCommentLength = CalculateBitLength(_strComment, new d.Font("Tahoma", 9, d.FontStyle.Regular));
            curPoint.X = ((Width - (2 * PAGE_MARGIN)) / 2) - (intCommentLength / 2);
            dc.DrawText(MakeText(_strComment, 9), curPoint);
            curPoint.Y += LINE_HEIGHT;
            curPoint.X = 0;
        }

        //Print current Date.
        int intDatLength = CalculateBitLength(String.Format("{0:MMMM dd, yyyy}", DateTime.Now), new d.Font("Tahoma", 9, d.FontStyle.Regular));
        curPoint.X = ((Width - (2 * PAGE_MARGIN)) / 2) - (intDatLength / 2);
        dc.DrawText(MakeText(String.Format("{0:MMMM dd, yyyy}", DateTime.Now), 9), curPoint);
        curPoint.Y += LINE_HEIGHT;
        curPoint.X = 0;

        TitleHeight = curPoint.Y;

        //Print First column of header row.
        dc.DrawText(MakeText(_columns[0], 9), curPoint);
        curPoint.X += _firstColumnWidth;
        beginCounter = _columnSlot[1].Start;

        //Print other columns of header row
        for (int i = beginCounter; i <= _columnSlot[1].End; i++)
        {
            //Remove unwanted characters
            _columns[i] = _columns[i].Replace(EXCAPE_CHAR, " ");

            if (_columns[i].Length > HEADER_CHR_WIDTH)
            {
                //Loop through to wrap the header text
                for (int k = 0; k < _columns[i].Length; k += HEADER_CHR_WIDTH)
                {
                    int subsLength = k > _columns[i].Length - HEADER_CHR_WIDTH ? _columns[i].Length - k : HEADER_CHR_WIDTH;
                    dc.DrawText(MakeText(_columns[i].Substring(k, subsLength), 9), curPoint);
                    curPoint.Y += HEADER_LINE_HEIGHT;
                }
            }
            else
                dc.DrawText(MakeText(_columns[i], 9), curPoint);

            //YAxisTracker keeps track of maximum lines used to print the headers.
            intYAxisTracker = intYAxisTracker < curPoint.Y ? curPoint.Y : intYAxisTracker;
            curPoint.X += COLUMN_WIDTH;
            curPoint.Y = TitleHeight;
        }

        //Reset X and Y pointers
        curPoint.X = 0;
        curPoint.Y += intYAxisTracker - TitleHeight;

        //Draw a solid line
        dc.DrawRectangle(Brushes.Black, null, new Rect(curPoint, new Size(Width, 2)));
        curPoint.Y += HEADER_HEIGHT - (2 * LINE_HEIGHT);

        //Loop through each collection in dataGrid to print the data
        for (int i = _currentRow; i < _currentRow + _rows; i++)
        {
            List<Object> icol = (List<Object>)_dataGrid.Items[i];

            //Print first column data
            dc.DrawText(MakeText(icol[0].ToString(), 10), curPoint);
            curPoint.X += _firstColumnWidth;
            beginCounter = _columnSlot[1].Start;

            //Loop through items in the collection; Loop only the items for currect column slot.
            for (int j = beginCounter; j <= _columnSlot[1].End; j++)
            {
                dc.DrawText(MakeText(icol[j] == null ? NOT_APPLICAPLE : icol[j].ToString(), 10), curPoint);
                curPoint.X += COLUMN_WIDTH;
            }
            curPoint.Y += LINE_HEIGHT;
            curPoint.X = 0;
        }

        //Print Page numbers
        curPoint.Y = Height - (2 * PAGE_MARGIN) - LINE_HEIGHT;
        curPoint.X = Width - (2 * PAGE_MARGIN) - COLUMN_WIDTH;

        dc.DrawText(MakeText(_strPageNumber, 9), curPoint);

    }

    #endregion Protected Functions
}