views:

305

answers:

1

I have a WPF application with a Frame and a TreeView. The TreeView displays the menu for my application, and when an item is clicked, the Frame displays the content for that menu item via a call to Navigate. That call sometimes takes several seconds, and UI is blocked while navigation completes.

I have a control with animated spinning circles (I call it a WaitIcon) that I would like to display while the frame loads, but it doesn't appear (and would not spin even if it did appear) because the UI thread is blocked loading the Frame's content. I can't call Navigate on a background thread because the frame is owned by the UI thread. Is there a way to show the WaitIcon while the frame loads? Possibly by creating a second thread that owns the WaitIcon?

UPDATE -- Sample code showing the problem below:

Here is the main window. I've just used an animated Ellipse to simulate the WaitIcon:

<Window x:Class="FrameLoadingTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <Storyboard x:Key="Animation" RepeatBehavior="Forever">
            <ColorAnimation From="Red" To="Black" Duration="0:0:2" Storyboard.TargetName="Ellipse1" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" />
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource Animation}" />
        </EventTrigger>
    </Window.Triggers>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="5*" />
        </Grid.ColumnDefinitions>
        <Button Click="Button_Click">Load Frame</Button>
        <Frame Name="ContentFrame" Grid.Column="1"></Frame>
        <Ellipse Name="Ellipse1" Height="100" Width="100" Visibility="Hidden" Fill="Red">
        </Ellipse>
    </Grid>
</Window>

The click event for the button:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Ellipse1.Visibility = Visibility.Visible;
    ContentFrame.Source = new Uri("TestPage.xaml", UriKind.Relative);
}

The test page:

<Page x:Class="FrameLoadingTest.TestPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="TestPage">
    <Grid>
        <TextBlock>Hello, world</TextBlock>
    </Grid>
</Page>

And I put a sleep in the constructor to simulate a long running initialization:

public partial class TestPage : Page
{
    public TestPage()
    {
        InitializeComponent();
        Thread.Sleep(3000);
    }
}

The ellipse does not show up until after the page loads.

+1  A: 

WPF animation, when started, cannot be blocked by the main thread. The storyboard runs in its own thread, and the rendering happens in the WPF rendering thread, which is a different one from the main. Also, I believe the Frame loading is also non-blocking.

So there is something else going on with your code, why don't you post it here so we can understand.

EDIT: I was wrong about storyboard. Since the resource is created on the main thread, it runs in the same main thread. And even if you create a storyboard dynamically, it will not be able to animate objects from the windows created on the main thread.

And I was wrong about Frame - it is blocking the main thread when loading an URL. I always used WebBrowser control, which is non blocking.

So, you have two options (maybe more, but I'll list two):

  • Create a windows with the animation in a different thread as described here.
  • Use WebBrowser instead of Frame.
Sergey Aldoukhov
I've updated the question with a sample
Eddie Deyo