渲染(重绘)不可见的canvas

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

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); } 

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

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

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

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

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

您应该可以使用此function并将图像基于为每个TabItem存储的ContentPresenter

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

 // 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; } ///  /// if containers are done, generate the selected item ///  ///  ///  void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } ///  /// get the ItemsHolder and generate any children ///  public override void OnApplyTemplate() { base.OnApplyTemplate(); _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } ///  /// when the items change we remove any generated panel children and add any new ones as necessary ///  ///  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 (ie 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"); } } ///  /// update the visible child in the ItemsHolder ///  ///  protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); UpdateSelectedItem(); } ///  /// generate a ContentPresenter for the selected item ///  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; } } ///  /// create the child ContentPresenter for the given item (could be data or a TabItem) ///  ///  ///  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; } ///  /// Find the CP for the given object. data could be a TabItem or a piece of data ///  ///  ///  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; } ///  /// copied from TabControl; wish it were protected in that class instead of private ///  ///  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; } } 

编辑以下评论

如果这对您不起作用,您可以尝试像普通一样渲染Canvas,然后在比DispatcherPriority.Render更低的DispatcherPriority上打印它,以便在所有渲染发生后打印。

像这样的东西:

 public static void ExportToImage(Canvas canvas, System.Drawing.Bitmap bmp) { // Save old background Brush background = canvas.Background; // Clear background to make images free of it canvas.Background = null; // 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); Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate() { MemoryStream picStream = new MemoryStream(); PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(renderBitmap)); encoder.Save(picStream); canvas.Background = background; // I don't think you can simply return your value here, // so you'll probably need to setup something else to // return your bitmap to your calling code bmp = new System.Drawing.Bitmap(picStream); })); }