0

I have a usercontrol with some buttons, textblocks and a ContentControl that will load other (3) different usercontrols within the same usercontrol. The buttons from the parent usercontrol will stay so I can keep iterating through the other (3) usercontrols. Those (3) usercontrols contain 1, 2, or 3 DataGrids with several row, and this may take a second or two to load (at least while debugging). I want to display an animation to indicate this. However, I cannot load those user controls async in order to display the animation, which waits until the usercontrol is loaded.

I don't see why my code wouldn't work. The parent usercontrol looks like this:

enter image description here

And when I click one of the three buttons at the bottom, a different usercontrol loads within the parent usercontrol, using the ContentControl.

enter image description here enter image description here

The parent user control is something like this:

<UserControl>
    <DockPanel LastChildFill="True">
        <Grid DockPanel.Dock="Top" >
            <Control Visibility="{Binding LoadingVisilibity}" Style="{StaticResource BusyAnimationStyle}" Panel.ZIndex="10"/>
            <ContentControl Content="{Binding ContentPanel}"></ContentControl>
        </Grid>
    </DockPanel>
</UserControl>

The BusyAnimationStyle just displays circles turning around.

   <Style x:Key="BusyAnimationStyle" TargetType="{x:Type Control}">
        <Setter Property="Background" Value="#9F000000"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Control">
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse0" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:0.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation1" BeginTime="00:00:00.1" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation2" BeginTime="00:00:00.2" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse2" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation3" BeginTime="00:00:00.3" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse3" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation4" BeginTime="00:00:00.4" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse4" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation5" BeginTime="00:00:00.5" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse5" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation6" BeginTime="00:00:00.6" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse6" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Animation7" BeginTime="00:00:00.7" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipse7" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:00.8" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </ControlTemplate.Resources>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsVisible" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
                                <BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard0"/>
                                <StopStoryboard BeginStoryboardName="Storyboard1"/>
                                <StopStoryboard BeginStoryboardName="Storyboard2"/>
                                <StopStoryboard BeginStoryboardName="Storyboard3"/>
                                <StopStoryboard BeginStoryboardName="Storyboard4"/>
                                <StopStoryboard BeginStoryboardName="Storyboard5"/>
                                <StopStoryboard BeginStoryboardName="Storyboard6"/>
                                <StopStoryboard BeginStoryboardName="Storyboard7"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <Grid>
                            <Canvas Height="60" Width="60">
                                <Canvas.Resources>
                                    <Style TargetType="Ellipse">
                                        <Setter Property="Width" Value="15"/>
                                        <Setter Property="Height" Value="15" />
                                        <Setter Property="Fill" Value="#009B9B9B" />
                                    </Style>
                                </Canvas.Resources>
                                <Ellipse x:Name="ellipse0" Canvas.Left="1.75" Canvas.Top="21"/>
                                <Ellipse x:Name="ellipse1" Canvas.Top="7" Canvas.Left="6.5"/>
                                <Ellipse x:Name="ellipse2" Canvas.Left="20.5" Canvas.Top="0.75"/>
                                <Ellipse x:Name="ellipse3" Canvas.Left="34.75" Canvas.Top="6.75"/>
                                <Ellipse x:Name="ellipse4" Canvas.Left="40.5" Canvas.Top="20.75" />
                                <Ellipse x:Name="ellipse5" Canvas.Left="34.75" Canvas.Top="34.5"/>
                                <Ellipse x:Name="ellipse6" Canvas.Left="20.75" Canvas.Top="39.75"/>
                                <Ellipse x:Name="ellipse7" Canvas.Top="34.25" Canvas.Left="7" />
                                <Ellipse Width="39.5" Height="39.5" Canvas.Left="8.75" Canvas.Top="8" Visibility="Hidden"/>
                            </Canvas>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


private Visibility loadingVisilibity;
public Visibility LoadingVisilibity
{
    get => loadingVisilibity;
    set
    {
        loadingVisilibity = value;
        OnPropertyChanged(nameof(LoadingVisilibity));
    }
}

To change the Content of the ContentControl, I have three buttons each like:

private ICommand firstBtnCommand;
public ICommand FirstViewBtnCommand
{
    get
    {
        return firstBtnCommand ?? (firstBtnCommand = new CommandHandler((param) => ChangeView("first"), true));
    }
}

And the function:

private async void ChangeView(string view)
{
    LoadingVisilibity = Visibility.Visible;
        await Application.Current.Dispatcher.InvokeAsync(()=>
        {
            if (view == "first") { ContentPanel.Content = this.firstViewModel; }
            else if (view == "second") { ContentPanel.Content = this.secondViewModel; }
            else { ContentPanel.Content = this.thirdViewModel; }
        });
    LoadingVisilibity = Visibility.Collapsed;
}

I also tried:

    await Application.Current.Dispatcher.BeginInvoke(
        new ThreadStart(() =>
        {
            if (view == "first") { ContentPanel.Content = this.firstViewModel; }
            else if (view == "second") { ContentPanel.Content = this.secondViewModel; }
            else { ContentPanel.Content = this.thirdViewModel; }
        }));

However, the animation doesn't happen. Sometimes I can see a bit just after the elements are loaded, but not while the ContentControl is loading.

I have done this before and usually goes like:

    await Task.Run(() =>
    {
        if (view == "first") { ContentPanel.Content = this.firstViewModel; }
        else if (view == "second") { ContentPanel.Content = this.secondViewModel; }
        else { ContentPanel.Content = this.thirdViewModel; }
    });

However, in this case tells me

The calling thread cannot access this object because a different thread owns it

I am lost now after trying so many different things, and I don't see anything wrong with my code.

How can I load the animation while the ContentControl content is loading? Is there a better way of doing this? Surely this looks like the right approach.

Let me know if there is more information that I can provide.

ehtio
  • 179
  • 3
  • 11
  • 2
    `Content="{Binding ContentPanel}"` seems to imply that ContentPanel, despite the name, is a view model property. It should then be possible to update the ContentPanel property in an async Task. The associated view element, i.e. your UserControl, would be created by the ContentTemplate of the ContentControl. – Clemens Nov 17 '22 at 08:22
  • 1
    It is, indeed. When I click a button I set the ContentPanel.Content to one of the viewmodels associated with the view I want to display. That works. Could you expand on the second part "The associated view element would be created by the ContentTemplate of the ContentControl"? I don't quite get that. – ehtio Nov 17 '22 at 08:34
  • 2
    See [Data Templating Overview](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview?view=netframeworkdesktop-4.8). Note that your ContentPanel should not be a view element. It should be a property which directly returns the Content that should be visualized by the ContentControl. – Clemens Nov 17 '22 at 08:36
  • 2
    [Navigation between pages](https://stackoverflow.com/a/61323201/3141792) shows how to use data models to load a UserControl via DataTemplate . This pattern allows you to display a busy indicator while you can focus on the data models (e.g. fetch data asynchronously). This way you Audi don't need to use the Dispatcher. – BionicCode Nov 17 '22 at 09:57
  • 1
    @BionicCode, that was very similar to what I was doing, however a lot simpler and it seems to work better. It lets me set the Control visiblity before and after the load of the ContentControl.Content. However, it's doesnt animate. Any idea why this is? – ehtio Nov 17 '22 at 16:48
  • 2
    Sorry,but what do you mean: *"it's doesnt animate"*? Your busy indicator? – BionicCode Nov 17 '22 at 20:01
  • 1
    @BionicCode I used the style from this comment https://stackoverflow.com/a/6369137/13181058 and use it as Control. When it's visible it spins. However, when I use it while changing the usercontrol (described on the main post), it just appears but without animating, just a still image. – ehtio Nov 17 '22 at 20:10
  • 2
    It's not clear to me where the spinner is displayed in your layout. I suggest to follow and implement my previously provided link. Then create a dedicated page (UserControl/DataTemplate) and an associated data model for the spinner. You then show the spinner (as DataTemplate) while you prepare the actual selected content. Then once completed switch to the selected content. This would be more efficient than toggling the Visibility. I hope it's clear what I mean. Otherwise you need to update the question to show the related and relevant details in order to allow a proper review. – BionicCode Nov 17 '22 at 22:07
  • 1
    @BionicCode, I updated the question to include the code for the spinner. The spinner is a Control type, and it shares the same Grid as the ContentControl. I will, however, try to put the spinner in a dedicated page and see how that goes. – ehtio Nov 18 '22 at 07:53

0 Answers0