Muckle_ewe
Muckle_ewe

Reputation: 1123

Clearing canvas has a delay

Below is the code for a simple app that draws a rectangle on a canvas in a window and then takes a screen shot of the app using the CopyFromScreen function when any key is pressed. Just before this is called however, I call canvas.Children.Clear(). I would then expect the resultant image to not have the rectangle in it, but it does. It seems that the actual rectangle image isn't removed from the canvas when the function is called but some time after.

I tried putting in a System.Threading.Thread.Sleep(1000); after the Clear() call but the rectangle stays on screen for that full second as well. Clearly it's getting removed after the key press function finishes, is there any way to remove it before the CopyFromScreen call?

To run this you will need to add a reference to System.Drawing.

XAML code

<Window x:Class="CanvasTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="210" Height="240"
        KeyDown="keyPressed">
    <Window.Background>
        <SolidColorBrush Color="White"/>
    </Window.Background>
    <Grid>
        <Canvas Name="canvas"
            HorizontalAlignment="Left" VerticalAlignment="Top">
        </Canvas>
    </Grid>
</Window>

.cs code

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;

namespace CanvasTest {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();

            Left = 0;
            Top = 0;

            Rectangle rect = new Rectangle {
                Stroke = System.Windows.Media.Brushes.Black,
                StrokeThickness = 1,
                Width = 100,
                Height = 100
            };

            canvas.Children.Add(rect);
            Canvas.SetLeft(rect, 50);
            Canvas.SetTop(rect, 50);
        }

        private void keyPressed(object sender, System.Windows.Input.KeyEventArgs e) {
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap((int)Width, (int)Height);

            System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap);

            canvas.Children.Clear();
            graphics.CopyFromScreen(0, 0, 0, 0,
                                    new System.Drawing.Size(bitmap.Width, bitmap.Height),
                                    System.Drawing.CopyPixelOperation.SourceCopy);

            String path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            path += "\\MuckleEwesPic.png";

            bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
}

How can I clear the canvas and then take a screen shot without this behaviour happening? And no 'don't add the rect' isn't a solution ha, this is just a minimal example of a larger app in which the problem is occurring.

Thanks.

Upvotes: 1

Views: 712

Answers (1)

stijn
stijn

Reputation: 35901

Using something like in ColinSmith's link:

static void WaitForRenderPass()
{
  Application.Current.Dispatcher
    .BeginInvoke( DispatcherPriority.ApplicationIdle, new Action( () => {} ) )
    .Wait();
}

sort of works for waiting on a render pass but the problem is it's not possible (afaik) to tell if that render pass contains everything you want.

In practice depending on system load/video card drivers/... it has to be called multiple times. I've had one machine where it had to be called in a loop up to 3 times, and each of those calls took about 2 frames @60Hz to complete. Only then a call to the above would return immediately indicating the render thread is really idle, which likely means all changes you want have been rendered, which in turn means screen captures contain everything they should have. So we ended up using

for( int i = 0 ; i < 5 ; ++i )
{
  WaitForRenderPass();
  Thread.Sleep( 10 );
}

It's ugly and it's a hack but it hasn't failed (yet).

Regarding HighCore's comment: you can capture the screen with Wpf-only classes as well, however:

  • it has the same problem the OP started this questiomn for in the first place so it won't immediately solve anything (except from not using System.Drawing)
  • you won't be able to get a main window's chrome in the capture
  • it's markedly slower than CopyScreen
  • it does not render the exact same pixels as rendered to the screen

Upvotes: 2

Related Questions