views:

762

answers:

6

Hi all,

I have a WPF application that is a fullscreen kiosk app. It's actually a pretty complicated app at this point, but here's some code that shows the basic idea. Essentially, whenever the user goes from one screen to the next, there's some serious flicker going on bringing up the new window. In severe cases, the desktop is displayed for a few seconds before the new screen shows up. That doesn't happen in this sample code, because it's so simple, but add a few more buttons and styles and you'll see it.

App.xaml.cs:

public partial class App : Application {
    Manager mManager;
    public App() {
        mManager = new Manager();
        Window1 screen1 = new Window1(mManager);
        mManager.Screen1 = screen1;
        try {
            this.Run(screen1);
        } catch (Exception e) {
            System.Console.WriteLine(e.ToString());                
        } finally {
            Application.Current.Shutdown();
        }
    }
}

Window1.xaml.cs:

public partial class Window1 : Window {
    Manager Manager{get; set;}
    public Window1(Manager inManager) {
        InitializeComponent();
        Manager = inManager;
    }

    private void OnChangeScreen(object sender, RoutedEventArgs e) {
        Manager.OpenScreen2();
    }
}

Window2.xaml.cs:

public partial class Window2 : Window {
    Manager Manager{get; set;}
    public Window2(Manager inManager) {
        InitializeComponent();
        Manager = inManager;
    }

    private void OnChangeScreen(object sender, RoutedEventArgs e) {
        Manager.OpenScreen1();
    }
}

Manager.cs:

public class Manager {
    public Window1 Screen1{ get; set;}
    public Window2 Screen2{ get; set;}

    public Manager(){
        Screen1 = new Window1(this);
    }

    public void OpenScreen2() {
        Screen2 = new Window2(this);
        Screen2.Show();
        if (Screen1 != null) {
            Screen1.Hide();
        }
    }

    public void OpenScreen1() {
        Screen1 = new Window1(this);
        Screen1.Show();
        if (Screen2 != null) {
            Screen2.Hide();
        }
    }
}

Window1.xaml (essentially mimicked by window2.xaml):

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" 
        WindowStyle="None"
        WindowState="Maximized"
        Width="1280"
        Height="1024"
        FontFamily="Global User Interface"
        ResizeMode="NoResize">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Name="ChangeScreenButton" Click="OnChangeScreen" Grid.Row="2" Grid.Column="2" Content="Toggle Screen 2"></Button>
    </Grid>
</Window>

Interleaving the displays of the two windows (ie, showing window 1 before deleting window 2, etc) does not change the flickering behavior. In this simple app, it would be possible to just hide the other screens that aren't shown, but in the more complicated app, there's just too much state information to manage screen information properly and easily.

Is there some magic codeword or technique to avoid flicker that would work in this simple app that also scales to the more complex app? I'm worried that I'll be forced to rewrite the entire UI at this point to support hiding and showing, and that's just not feasible in my timeframe.

EDIT: I've tried the hide/show thing on some dialogs, and it just doesn't seem to matter. Maybe it's because the main kiosk app is style heavy?

+1  A: 

I'm curious as to why you are using multiple windows for the same application in a Kiosk. You could easily put all of the controls on the same "Window" and simply change visibility on Panels to display different "screens". This would certainly prevent the desktop from ever being shown, and would allow you to do neat things like fade transitions or sliding animations, etc.

NebuSoft
That is a very, very interesting idea. I'm not sure how long it would take to switch things to this approach (again, state problems; this used to be a WinForms app switched over to WPF), but it might be worth an experiment or two.
mmr
Another idea that may prevent this...If it is taking a long time to display subsequent windows, it may be due to the computer being bogged down trying render everything. You might want to "show" all the windows at startup, and switch between them via activating the window you want. This way it's simply windows switching the active (on top) window as opposed to having to actually render all of the components on each transition.
NebuSoft
+1  A: 

WPF has built in navigation functionality.

Just look at the Frame and the Page classes which you can easily design using VS or Blend.

winSharp93
and the NavigationWindow.
Will
+1  A: 

Agreed with the comments about using the built-in navigation functionality, but if you're locked in to your design at this point, perhaps consider animating the opacity of your windows? A short 100 or 200 ms animation of Opacity from 1 -> 0 for the outgoing window and 0 -> 1 for the incoming window might resolve the issue. Handle the actual cleanup of the outgoing window in the Completed event on the storyboard.

Ben Von Handorf
Neat idea, but I've been told I'm not allowed to be creative with anything like that, just to make things show faster.
mmr
If your concern is the whole designer/developer separation, you could create the storyboard in code to avoid issues with existing animations. If it's just a policy issue, well... I guess they like their flickering. :)
Ben Von Handorf
It's more of a "That looks like art to me! You're a coder, you're not allowed to be an artist! That's not what we pay you for! Get back to the mines!" kind of mentality.
mmr
Ah... always helpful. Hopefully it's something that WPF can help you get past... those little transitions really do help usability and user impressions and they're pretty easy to do.
Ben Von Handorf
A: 

Seeing as how WPF uses DirectX and the Graphics Processor to offload processing of screen elements, are the computer's DirectX and drivers up to date?

Cory

OffApps Cory
This is happening on every machine the app is tested on, and they all should be fully updated. If .NET requires more directx drivers be installed, I'd think it'd be part of the installer... but my dev machine certainly has the most recent directx installed and it's flickering here.
mmr
A: 

If you have any initialization in the constructor that takes a long time that could cause a delay and flicker. You could try using an asynchronous method or put that initialization on a background thread so that it does not block the showing of the window.

An example of something which would cause a delay would be a database query or a request for data over a network.

A quick experiment would be to disable parts of the constructor in a slow Window to find out what is causing delay in showing the Window.

Doug Ferguson
+4  A: 

The underlying cause of the flicker is that whenever you .Hide() a window its PresentationSource is disconnected, causing Unloaded events to be fired on everything and everything cached in the MILCore layer of WPF to be discarded. Then when you .Show() it again later, everything is rebuilt.

To prevent flicker, make sure you keep your UI connected to a PresentationSource at all times. This can be done in several ways:

Single window with a disguised TabControl

Use a single window containing a TabControl styled so you can't see the tabs. Switch tabs in code when you would normally show or hide windows. You can simply search-and-replace "Window" in your existing code with "Page", then replace "Show()" calls to your custom "Show()" which does the following:

  1. Check for previously created TabItem for this Page (using a Dictionary)
  2. If no TabItem found, wrap the Page inside a new TabItem and add it to the TabControl
  3. Switch the TabControl to the new TabItem

The ContentTemplate you would use for your TabControl is extremely simple:

<ContentTemplate TargetType="TabControl">
  <ContentPresenter x:Name="PART_SelectedContentHost"
                    ContentSource="SelectedContent" />
</ContentTemplate>

Using a Frame with Navigation

Using Frame with Navigation is a very good solution for a kiosk because it implements a lot of the page switching and other functionality. However it may be more work to update an existing application this way than to use a TabControl. In either case you need to convert from Window to Page, but with Frame you also need to deal with navigation.

Multiple windows with opacity

You can make a window almost completely invisible using a low opacity and yet WPF will still keep the visual tree around. This would be a trivial change: Just replace all calls to Window.Show() and Window.Hide() with calls to "MyHide()" and "MyShow()" which updates the opacity. Note that you can improve this further by having these routines trigger animations of very short duration (eg 0.2 second) that animate the opacity. Since both animations will be set at the same time the animation will proceed smoothly and it will be a neat effect.

Ray Burns
Thanks for the explanation.
mmr