0
votes

I have a WPF application that takes a screenshot of the contents of a WindowsFormsHost control and applies it to the background of a Grid control. The Grid acts as a transition screen from the WindowsFormsHost to other WPF controls. This is done to make a smooth transition effect and to avoid air space issues. I first capture the WinFormsHost control image as a bitmap and apply it to the Grid background. Then programmatically change the visibility of the transition Grid to visible. Then do an opacity animation to smoothly show another control. It works perfectly about 70 to 90 percent of the time depending on what computer I test the application on. The problem is that the transition Grid background is not being rendered fast enough or at the correct time. So that occasionally I am seeing a screenshot from a previous transition which does not match the current screen image at transition time.

If I could somehow ensure that the transition image was drawn before the Grid is made visible, it would work everytime. I just cannot see how to do this. The application seems to always wait until the last minute to do any rendering.

I have tried to force rendering with Dispatcher.Invoke() and Dispatcher.BeginInvoke() methods. I have also tried to delay the time when the Grid is made visible with a Dispatcher Timer, but no matter how much time there is between the call setting the background image and setting the visibility, the Grid does not always update. I have also tried things like InvalidateVisual() with no luck.

After looking at many examples into problems involving WPF's rending and UI threads, I made several attempts to correct the problem...

Attempt 1 - Works poorly, but attempt works better with a line of code that first sets background to nothing or black:

                TransitionScreen.Visibility = Visibility.Visible
                TransitionScreen.Background = Nothing
                TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))

                State = "WPFControlMode"
                SwitchState()

Attempt 2 - Best results so far. Works fair to well depending on computer. Increasing the counter value does not improve results. The appearance of the TransitionScreen will be delayed with a larger counter value, but will have the same chance of showing a previous screenshot (about 1 in 5 times on my laptop):

Case "Transition" 
            'Grab Screen Shot and apply to transition screen...'

            TransitionScreen.Visibility = Visibility.Visible
            TransitionScreen.Background = Nothing
            TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))


End Select


'The following code is inside a loop...'
If State = "Transition" Then
TransitionCounter += 1

        If TransitionCounter = 25 Then
            TransitionCounter = 0

            TransitionScreen.Visibility = Visibility.Visible
            TransitionScreen.Background = Nothing
            TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))

            State = "WPFControlMode"
            SwitchState()         

        End If

End If

Attempt 3, 4, 5, ... - Attempts try to force rendering of control by working through Dispatcher. These attempts do not appear to help and often make the problem worse. They have been used in combination with both attempts 1 and 2.

Case "Transition" 
            'Grab Screen Shot and apply to transition screen...'

            TransitionScreen.Visibility = Visibility.Visible
            TransitionScreen.Background = Nothing
            TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))

            FlushWindowsMessageQueue()


End Select

'Tried many variations of the following procedure.  Every dispatcher priority with both BeginInvoke and Invoke methods, as well as calling the methods through Application and TransitionScreen objects.' 

Private Sub FlushWindowsMessageQueue()
    Application.Current.Dispatcher.Invoke( _
    New Action(AddressOf DummySub), _
    DispatcherPriority.Background, _
    New Object() {})
End Sub

Private Sub DummySub()
End Sub

There were more attempts also, but this should give you an idea of what I have tried. This is a real puzzle. This is the last major problem with a fairly involved project, and I am pretty much invested in the transition screen at this point. If you can think of anything, I will greatly appreciate it. Thanks.

1

1 Answers

0
votes

I did come up with a work around for this problem. I do a transition animation before I take the screenshot. The transition animation results in a lower resolution image than the control would normally show. Then I take the screenshot of the lower resolution image which can be applied to the transition grid control, after it is made visible, fast enough that there is never a flicker. Then when I want to return to the WinFormsHost control, I run through this process in reverse. My application now works without flicker and run smoothly on most computers I have tried. On some computers with large monitors, there is sometimes a slight delay when rendering the transition screen, but still no flicker. The delay is about half a second, and I assume it has to do with the larger area that transition image must be rendered to.

Also, the above was done in combination with method/attempt #2. The flicker still occurs when in combination with attempt #1.