【问题标题】:Use of ItemsControl ActualHeight to paginate a printed XAML form?使用 ItemsControl ActualHeight 对打印的 XAML 表单进行分页?
【发布时间】:2013-01-09 16:23:41
【问题描述】:

我需要使用 XAML 生成一个打印表单,该表单具有一个标题网格和可变数量的行,随着行数的增加可能会产生多个页面。标题必须出现在每一页上,并且由于行内的文本换行,每一行的高度可能会有所不同。我目前正在尝试使用 ItemsControl(行容器)的 ActualHeight 来确定何时生成新页面,但 ActualHeight 的值始终为零强>。

我的“XAML_Form”具有以下结构。在 ItemTemplate 中使用了一个网格,以允许将行中的列与标题网格中的列对齐。

<Grid Width="980" Height="757">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Name="_headerControl" Grid.Row="0"/>
        <ItemsControl Name=_rowsControl ItemsSource={Binding Rows} Grid.Row="1">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Grid>

我们的架构有一个报告类,用于处理添加页脚、页码以及将页面聚合到 PDF 中。此报表类对每个 XAML 视图页面都有一个嵌套的 ViewModel。每个页面的 ViewModel 使用行对象的后备列表:

   List<RowClass> RowsList;

ViewModel 还有一个 ICollectionView 用于绑定为 ItemsSource:

   ICollectionView Rows = new ListCollectionView(RowsList);

我的报表类有一个 CreatePages 方法,其中包含如下代码:

IList<XAML_Form> pages = new List<XAML_Form>();
var vm = new PageViewModelClass();
var page = new XAML_Form { DataContext = vm };
page.InitializeComponent();
page.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity);
page.Arrange(new Rect(new Point(0,0), page.DesiredSize));
var maxRowsHeight = page.DesiredSize.Height - page._headerControl.ActualHeight;
pages.Add(page);
var rowsOnPage = 0;
foreach (var row in sourceRowsObjectList)
{
    rowsOnPage++;
    vm.RowsList.Add(row);
    vm.Rows.Refresh();
    page.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity);
    page.Arrange(new Rect(new Point(0,0), page.DesiredSize));
    if (page._rowsControl.ActualHeight <= maxRowsHeight)
        continue;

    // The rows exceed the available space; the row needs to go on the next page.
    vm.RowsList.RemoveAt(--rowsOnPage);
    vm = new PageViewModelClass();
    vm.RowsList.Add(row);
    rowsOnPage = 1;
    page = new XAML_Form { DataContext = vm };
    page.InitializeComponent();
    pages.Add(page);
}
return pages;

初始测量/排列确实为我提供了 maxRowsHeight 的预期值。生成的表单对于有几行的单个页面看起来很好。我的具体问题是:为什么 page._rowsControl.ActualHeight 总是为零?一般来说,有没有更好的方法来解决这个问题?

【问题讨论】:

  • 我通过 WPF 博士的系列深入研究了 ItemsControl。我看到“网格”不是项目控制类的项目宿主之一。这和这个有关系吗?
  • 终于在这方面取得了一点进展。可以调用“非常不鼓励”的 UpdateLayout 方法。由于这太贵了,我不想在添加每一行后调用 UpdateLayout。相反,我将添加应该适合页面而不换行的行数,然后调用 UpdateLayout。如果它们不适合,则继续删除一行并调用 UpdateLayout,直到行适合。仍然没有对这种方法感到欣喜若狂,但看起来它会奏效。

标签: wpf xaml itemscontrol measure actualheight


【解决方案1】:

这里有一个解决方案。我们正在尝试将视图关注点与视图模型关注点分开,因此仍有待改进。

报表类中的 CreatePages 方法现在是:

private static IEnumerable<XAML_Form> CreatePages()
{
    IList<XAML_Form> pages = new List<XAML_Form>();
    int rowCount = sourceRowsObjectList.Count;
    int remainingRowCount = rowCount;
    do
    {
        var pageVm = new PageViewModelClass();
        var page = new XAML_Form(pageVm);
        pages.Add(page);

        int numberOfRowsToAdd = Math.Min(remainingRowCount, XAML_Form.MaxNumberOfRows);
        pageVm.AddRows(sourceRowsObjectList.Skip(rowCount - remainingRowCount).Take(numberOfRowsToAdd));
        remainingRowCount -= numberOfRowsToAdd;
        while (page.AreRowsOverflowing())
        {
            pageVm.RemoveLastRow();
            remainingRowCount++;
        }
    } while (remainingRowCount > 0);
    return pages;
}

后面相关的XAML_Form代码如下:

private static int _maxNumberOfRows = -1;

public XAML_Form(PageViewModelClass viewModel)
{
    InitializeComponent();
    Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
    Arrange(new Rect(new Point(0, 0), DesiredSize);
    ViewModel = viewModel;
}

public PageViewModelClass ViewModel
{
    get { return (PageViewModelClass)DataContext; }
    private set { DataContext = value; }
}

public static int MaxNumberOfRows
{
    get     // Compute this only once, the first time it is called.
    {
        if (_maxNumberOfRows < 0) return _maxNumberOfRows;
        var page = new XAML_Form();
        var singleRowCollection = new object[] { null; }
        page._rowsControl.ItemsSource = singleItemCollection;
        page._rowsControl.UpdateLayout();
        var rowHeight = page._rowsControl.ActualHeight;
        _maxNumberOfRows = (int)((page.DesiredSize.Height - page._headerControl.ActualHeight) / rowHeight);
        page._rowsControl.ItemsSource = null;
        return _maxNumberOfRows;
    }
}

// Call this method as rarely as possible. UpdateLayout is EXPENSIVE!
public bool AreRowsOverflowing()
{
    _rowsControl.UpdateLayout();
    return _rowsControl.ActualHeight > DesiredSize.Height - _headerControl.ActualHeight;
}

【讨论】:

  • 我们已经对此进行了进一步优化,以尽量避免调用 UpdateLayout。也许我会在某个时候发布。
  • 我们还通过在后面的代码中创建一个“虚拟面板”来优化这一点,它只包含可以换行的文本框控件。然后我们可以只在这个虚拟面板上调用 UpdateLayout,它永远不会显示,也不是表单的父级,所以它更便宜。
猜你喜欢
  • 2011-06-30
  • 1970-01-01
  • 2014-04-15
  • 1970-01-01
  • 2015-10-03
  • 1970-01-01
  • 2015-04-27
  • 1970-01-01
  • 2011-07-05
相关资源
最近更新 更多