渲染(重绘)不可见的画布

时间:2012-07-12 10:25:31

标签: c# wpf

我在每个页面like this上都有一个带有单独画布(带有不同UserControls)的选项卡式应用程序。现在我需要将所有页面(画布)保存到图像中。代码是这样的:

public static System.Drawing.Bitmap ExportToImage(Canvas canvas)
{
    // Save old background
    Brush background = canvas.Background;
    // Clear background to make images free of it
    canvas.Background = null;

    //canvas.UpdateLayout();
    //canvas.InvalidateVisual();

    // Create a render bitmap and push the surface to it
    RenderTargetBitmap renderBitmap =
        new RenderTargetBitmap(
            (int)canvas.Width,
            (int)canvas.Height,
            96d,
            96d,
            PixelFormats.Pbgra32);
    renderBitmap.Render(canvas);

    MemoryStream picStream = new MemoryStream();
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
    encoder.Save(picStream);

    canvas.Background = background;

    return new System.Drawing.Bitmap(picStream);
}

我进行了额外的转换,例如更改边距和大小,但这并不重要。

对于活动页面上的画布(当前在屏幕上),我获得了正常的渲染图像:没有背景,排列位置和大小等。)

但对于非活动页面上的画布,我会获得具有原始画布外观的图像(带有背景但未排列)。 如何强制画布应用我的修改并使用它们进行渲染? 我尝试在画布上使用UpdateLayout和InvalidateVisual,但是没有效果。

1 个答案:

答案 0 :(得分:2)

WPF卸载不可见的对象,这意味着当您离开Tab时,它将卸载其上的UI控件并加载一组新的控件。

为避免出现这种情况,我通常会使用TabControl的扩展版本,当您远离它时,会存储每个ContentPresenter的{​​{1}},当您返回该版本时选项卡重新加载TabItem而不是重绘所有内容。它占用了更多的内存,但我发现它在性能上更好,因为每当你切换标签时,TabItem不再需要重新创建它上面的所有控件。

您应该可以使用此功能,并根据为ContentPresenter

存储的ContentPresenter创建图像

原始代码来自here,虽然该网站已经停止了几个月,我不知道它搬到了哪里。我稍微修改了一下因为我需要允许拖放标签项来重新排列它们而不重新绘制它们,但这不应该影响任何东西。

TabItem

根据以下评论进行修改

如果这对您不起作用,您可以尝试像普通一样渲染Canvas,然后将其打印在比// Extended TabControl which saves the displayed item so you don't get the performance hit of // unloading and reloading the VisualTree when switching tabs // Obtained from http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx // and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class TabControlEx : System.Windows.Controls.TabControl { // Holds all items, but only marks the current tab's item as visible private Panel _itemsHolder = null; // Temporaily holds deleted item in case this was a drag/drop operation private object _deletedObject = null; public TabControlEx() : base() { // this is necessary so that we get the initial databound selected item this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } /// <summary> /// if containers are done, generate the selected item /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } /// <summary> /// get the ItemsHolder and generate any children /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } /// <summary> /// when the items change we remove any generated panel children and add any new ones as necessary /// </summary> /// <param name="e"></param> protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (_itemsHolder == null) { return; } switch (e.Action) { case NotifyCollectionChangedAction.Reset: _itemsHolder.Children.Clear(); if (base.Items.Count > 0) { base.SelectedItem = base.Items[0]; UpdateSelectedItem(); } break; case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: // Search for recently deleted items caused by a Drag/Drop operation if (e.NewItems != null && _deletedObject != null) { foreach (var item in e.NewItems) { if (_deletedObject == item) { // If the new item is the same as the recently deleted one (i.e. a drag/drop event) // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be // redrawn. We do need to link the presenter to the new item though (using the Tag) ContentPresenter cp = FindChildContentPresenter(_deletedObject); if (cp != null) { int index = _itemsHolder.Children.IndexOf(cp); (_itemsHolder.Children[index] as ContentPresenter).Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); } _deletedObject = null; } } } if (e.OldItems != null) { foreach (var item in e.OldItems) { _deletedObject = item; // We want to run this at a slightly later priority in case this // is a drag/drop operation so that we can reuse the template this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, new Action(delegate() { if (_deletedObject != null) { ContentPresenter cp = FindChildContentPresenter(_deletedObject); if (cp != null) { this._itemsHolder.Children.Remove(cp); } } } )); } } UpdateSelectedItem(); break; case NotifyCollectionChangedAction.Replace: throw new NotImplementedException("Replace not implemented yet"); } } /// <summary> /// update the visible child in the ItemsHolder /// </summary> /// <param name="e"></param> protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); UpdateSelectedItem(); } /// <summary> /// generate a ContentPresenter for the selected item /// </summary> void UpdateSelectedItem() { if (_itemsHolder == null) { return; } // generate a ContentPresenter if necessary TabItem item = GetSelectedTabItem(); if (item != null) { CreateChildContentPresenter(item); } // show the right child foreach (ContentPresenter child in _itemsHolder.Children) { child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; } } /// <summary> /// create the child ContentPresenter for the given item (could be data or a TabItem) /// </summary> /// <param name="item"></param> /// <returns></returns> ContentPresenter CreateChildContentPresenter(object item) { if (item == null) { return null; } ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) { return cp; } // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); cp.Content = (item is TabItem) ? (item as TabItem).Content : item; cp.ContentTemplate = this.SelectedContentTemplate; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; cp.ContentStringFormat = this.SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); _itemsHolder.Children.Add(cp); return cp; } /// <summary> /// Find the CP for the given object. data could be a TabItem or a piece of data /// </summary> /// <param name="data"></param> /// <returns></returns> ContentPresenter FindChildContentPresenter(object data) { if (data is TabItem) { data = (data as TabItem).Content; } if (data == null) { return null; } if (_itemsHolder == null) { return null; } foreach (ContentPresenter cp in _itemsHolder.Children) { if (cp.Content == data) { return cp; } } return null; } /// <summary> /// copied from TabControl; wish it were protected in that class instead of private /// </summary> /// <returns></returns> protected TabItem GetSelectedTabItem() { object selectedItem = base.SelectedItem; if (selectedItem == null) { return null; } if (_deletedObject == selectedItem) { } TabItem item = selectedItem as TabItem; if (item == null) { item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; } return item; } } 更低的DispatcherPriority上,以便在所有渲染完成后打印。

这样的事情:

DispatcherPriority.Render