views:

212

answers:

3

I want to capture an image of the screen, however there are some wpf controls in my application that I do not want to appear in the screenshot. Using the code below, the hidden controls will sometimes still appear in the screenshot. How do I ensure that this doesn't happen?

Window window = new Window();
Button button = new Button();

void ShowWindow()
{
    button.Content = "button";
    button.ToolTip = "tooltip";
    window.Content = button;
    window.Show();

    button.Click += new RoutedEventHandler(button_Click);
}

void button_Click(object sender, RoutedEventArgs e)
{
    //Hide some controls and all tooltips in the window
    ToolTipService.SetIsEnabled(window, false);
    button.Visibility = Visibility.Hidden;

    //Block until wpf renders
    Application.Current.Dispatcher.Invoke(
        System.Windows.Threading.DispatcherPriority.Background, 
        new System.Threading.ThreadStart(delegate { }));

    //Take screenshot
    Bitmap bmpScreenshot = new Bitmap(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
                               System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height,
                               System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot);
    gfxScreenshot.CopyFromScreen(System.Windows.Forms.Screen.PrimaryScreen.Bounds.X,
                                System.Windows.Forms.Screen.PrimaryScreen.Bounds.Y,
                                0, 0,
                                System.Windows.Forms.Screen.PrimaryScreen.Bounds.Size,
                                CopyPixelOperation.SourceCopy);
    bmpScreenshot.Save("Screenshot.png", ImageFormat.Png);

    //Restore controls and tooltips
    button.Visibility = Visibility.Visible;
    ToolTipService.SetIsEnabled(window, true);
}
A: 

Make the application wait about 300 milliseconds after the button is clicked before taking the screenshot. Or hide the controls when the button is clicked, take the screenshot and then make the controls appear again after the button is released.

Time Machine
This might work some of the time, but if your machine is busy performing other tasks, 300ms might not be long enough. It depends upon how deterministic you need this to be.
Drew Noakes
A: 

Have you tried setting the DispatcherPriority to Normal rather than Background?

Drew Noakes
Or Render, instead of Normal/Background
Will Eddins
Changing DispacherPriority does not appear to help. I believe that this line works by forcing all actions of higher priority to be processed. So in order to force a render, it needs to have a lower priority than render. However, even changing it to lowest priority does not fix the problem.
kova
Interesting, and a fair point. IIRC, the actual rendering in WPF is done in another thread (not the dispatcher thread), so just because the dispatcher is finished doesn't mean the pixels are on the screen yet. I'm not sure if that information is available upstream where you are... sorry :)
Drew Noakes
A: 

Try using a low priority on the dispatcher, it still isn't a guarantee though (source).

Also, from my understanding WPF doesn't start the render process until the UI thread is idle (or lengthily blocked), so you may want to place the dispatch blocking & snapshot code in an actual background thread (or have the dispatcher asynchronously invoke the snapshot code). Then restore the button visibility once that snapshot thread is complete.

DanStory
Thanks for the link. It looks like it is impossible to tell whether a change to a wpf control has been rendered.
kova
This answer was the most helpful. What I want to do is impossible (to do deterministically). The best bet is to try some tricks that will hopefully cause the render thread to run and finish rendering before the capture, though so far is I can tell there is no way to guarantee this.
kova