Pregunta

My requirements:

  • a persistent UserControl that handles logic for a custom image, such as a map or drawing
  • a set of containers to implement caching on the image during zoom or pan movements
  • VisualBrush copies of the UserControl that I can add to the containers for use with Effects

I currently implement image caching with a RenderTargetBitmap, but that seems to have trouble with the VisualBrush-covered Rectangle objects I'm using.

My question: What can I add/change in this code to get the VisualBrush objects to render correctly after RenderTargetBitmap uses them? What strange thing is RenderTargetBitmap doing that makes the VisualBrush invisible?

This is a problem that I have been unable to reproduce without a decent amount of code.

In my xaml file I have:

<Window x:Class="ElementRender.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="350" Width="525">
  <Grid>
    <Grid Name="_contentContainer">
      <Rectangle Fill="White"/>
      <Grid Name="_content">
        <Grid Name="_back"/>
        <Grid Name="_body"/>
      </Grid>
    </Grid>
    <StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
      <Button Content="New" Name="New"/>
      <Button Content="Move" Name="Move"/>
      <Button Content="Update" Name="Update"/>
    </StackPanel>
  </Grid>
</Window>

and the .xaml.cs:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

public partial class MainWindow : Window
{

  private const int imageWidth = 150;
  private const int imageHeight = 150;
  private readonly UserControl Control;

  public MainWindow()
  {
     InitializeComponent();

     // User Control setup
     Control = new UserControl() {
        Width = imageWidth, Height = imageHeight,
        Content = BuildImage()
     };
     _body.Children.Add(SoftCopy(Control));

     // event setup
     Move.Click += (sender, e) => _content.RenderTransform = new TranslateTransform(50, 50);
     New.Click += (sender, e) => {
        HardCopy();
        _content.RenderTransform = null;
        Control.Content = BuildImage();
     };
  }

  private FrameworkElement BuildImage()
  {
     return new Rectangle{Fill=Brushes.Blue};
  }
  private void HardCopy()
  {
     int width = (int) _contentContainer.ActualWidth;
     int height = (int) _contentContainer.ActualHeight;

     // render the current image
     var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
     DrawingVisual dv = new DrawingVisual();
     using (var context = dv.RenderOpen())
     {
        var brush = new VisualBrush(_contentContainer) { Opacity = .5 };
        context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
     }
     rtb.Render(dv);
     var lastRender = new Image
     {
        Source = rtb,
        Stretch = Stretch.None,
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center,
        Width = width,
        Height = height
     };
     _back.Children.Clear();
     _back.Children.Add(lastRender);
  }
  private FrameworkElement SoftCopy(FrameworkElement element)
  {
     return new Rectangle{Fill= new VisualBrush(element), Width=element.Width, Height=element.Height};
  }
}

A few helping notes about the code:

  • the xaml's _contentContainer works with HardCopy() to copy the current images into the image cache, _back.
  • SoftCopy returns a FrameworkElement that looks exactly like the one past in, but without any transforms, effects, or visual parents. This is very important.
  • BuildImage simulates building a new image to be pasted over the cache after the initial image has been transformed somehow.

If you build and run the application removing the SoftCopy() from the _body.Children.Add(SoftCopy(Control));, you see the effect that I want to get: the new element is pasted above the old element, and the old element seems to retain its transform.

Alternatively, if you cut out the line var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); from HardCopy, the caching function is broken, but the SoftCopy is displayed correctly.

However, if you run the application as-is, you notice that the new BlueRectangle (as rendered through a VisualBrush) doesn't display at all, until you hit the "New" button again, pushing the image to the cache, and still not showing you the new created image.

¿Fue útil?

Solución

I'm going to be pompous enough to call this a bug in WPF. I eventually found out how to fix the strange behavior I was getting:

var visual = visualBrush.Visual;
visualBrush.Visual = null;
visualBrush.Visual = visual;

This should essentially be a null operation: by the end, the visual brush has the same visual as when it started. However, adding this code segment after rendering the VisualBrush into the RenderTargetBitmap fixed the issue I was having.

Otros consejos

I didn't quite understand the post but there are few important things:

If you apply RenderTransform/Margins to element and take picture of it(RenderTargetBItmap), you're gonna have bad time. It will be offseted and you will get only sub-picture.

The idea is to take picture without any rendertransforms, and then later copy RenderTransform over from the old one. If needed.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top