UI doesn't update when opacity property changed and then followed by an await method call

0

When my app launches, it calls a method to show a progress indicator. This method sets the opacity of the content area to 0, and the opacity of the progress bar to 1 (it's a Popup control).

Progress indicator's popup xaml:

        <Popup 
        x:Name="loadingOverlay" 
        IsOpen="{Binding IsProgressBarOpen}"
        Margin="10,-100,0,0" 
        Opacity="{Binding OpacityProgressBar}"
        VerticalAlignment="Center" HorizontalAlignment="Center">
        <Border 
            x:Name="loadingOverlayPanel"   
            BorderBrush="{StaticResource PhoneAccentBrush}" >
            <StackPanel 
                VerticalAlignment="Center">
                <telerik:RadBusyIndicator 
                    x:Name="LoadingVisualAngleTransform" 
                    Content=" "
                    Foreground="{Binding ActiveTheme.ForegroundAlt1, Source={StaticResource ThemeController}}"
                    FontSize="18"
                    FontWeight="Bold"
                    IndicatorAnimationStyle="{StaticResource ColorWheelIndicatorAnimation}"
                    IsRunning="True"
                    Opacity="{Binding OpacityProgressBar}"
                    VerticalAlignment="Center">
                </telerik:RadBusyIndicator>
            </StackPanel>
        </Border>
    </Popup>

Method making the above control's opacity = 1.

    public void showProgressBar()
    {
        this.IsProgressBarOpen = true;
        this.OpacityContent = 0;
        this.OpacityProgressBar = 1;
    }

The problem I'm having is that the progress indicator is not showing if the showProgressBar() method call is followed by an await method call.

In the below code, I first call the progress bar to show, and then make a request to get the phone's location coordinates. The code following that should only be run once the coordinates have been received which is why I've used the await keyword. I'm assuming the short time between the showProgressBar() and setLocationCoordinates() calls haven't given enough time for the UI to update? What's weird is when debugging once the location coordinates have been fetched, I noticed the progress indicator still doesn't appear.

                // Progress bar should be shown as weather is being fetched.
                App.ViewModel.showProgressBar();

                // Must retrieve GPS location details with the status of
                // the requested recorded into a variable.
                // Have 3 attemps cause sometimes first attempt fails.
                string status = "";
                int attempts = 1;
                while( status != "success" )
                {
                    status = await App.ViewModel.GPS.setLocationCoordinates();

                    if (status == "success")
                    {
                        App.ViewModel.GPS.setLocationDetails();
                        break;
                    }

                    attempts++;
                    if (attempts > 3)
                        break;
                }

I should add that this problem doesn't occur if the showProgressBar() is not followed by an await method.

UPDATE: added xaml of my page to show Pantelis that I've actually been using his recommendation (I think). I have a Grid with no Opacity binded, with Popup and Pivot controls as siblings, each with an opacity binded. Let me know if this isn't what you were suggesting.

    <!-- RESOURCES -->
<phone:PhoneApplicationPage.Resources>

    <Style x:Key="ColorWheelIndicatorAnimation" TargetType="telerikBusy:BusyIndicatorAnimation">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel
                        x:Name="PART_LayoutRoot">
                        <StackPanel.Resources>
                            <Storyboard 
                                x:Name="PART_Animation" 
                                RepeatBehavior="Forever">
                                <DoubleAnimation
                                    Storyboard.TargetName="progressBarIcon1"
                                    Storyboard.TargetProperty="Opacity"
                                    From="1.0" To="0.0" Duration="0:0:1"
                                    AutoReverse="True"/>
                                <DoubleAnimation
                                    Storyboard.TargetName="progressBarIcon2"
                                    Storyboard.TargetProperty="Opacity"
                                    From="0.0" To="1.0" Duration="0:0:1"
                                    AutoReverse="True"/>
                            </Storyboard>
                        </StackPanel.Resources>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <TextBlock 
                                Grid.Row="0"
                                x:Name="progressBarIcon1" 
                                FontSize="48"
                                FontFamily="/Assets/Fonts/WeatherIcons-Regular.otf#Weather Icons" 
                                Foreground="{Binding ActiveTheme.Foreground, Source={StaticResource ThemeController}}"
                                Text="&#xf055;" 
                                HorizontalAlignment="Center" 
                                VerticalAlignment="Center"/>
                            <TextBlock Grid.Row="0"
                                x:Name="progressBarIcon2" 
                                FontSize="48"
                                FontFamily="/Assets/Fonts/WeatherIcons-Regular.otf#Weather Icons" 
                                Foreground="{Binding ActiveTheme.Foreground, Source={StaticResource ThemeController}}"
                                Text="&#xf053;" 
                                HorizontalAlignment="Center" 
                                VerticalAlignment="Center"/>
                        </Grid>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</phone:PhoneApplicationPage.Resources>



<!--LAYOUT CONTAINER -->
<Grid 
    x:Name="LayoutRoot" 
    Background="{Binding ActiveTheme.Background, Source={StaticResource ThemeController}}"
    CacheMode="BitmapCache">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" MinHeight="800"/>
    </Grid.RowDefinitions>


    <!-- LOADING OVERLAY -->
    <Popup 
        x:Name="loadingOverlay" 
        IsOpen="{Binding IsProgressBarOpen}"
        Margin="10,-100,0,0" 
        Opacity="{Binding OpacityProgressBar}"
        VerticalAlignment="Center" HorizontalAlignment="Center">
        <Border 
            x:Name="loadingOverlayPanel"   
            BorderBrush="{StaticResource PhoneAccentBrush}" >
            <StackPanel 
                VerticalAlignment="Center">
                <telerik:RadBusyIndicator 
                    x:Name="LoadingVisualAngleTransform" 
                    Content=" "
                    Foreground="{Binding ActiveTheme.ForegroundAlt1, Source={StaticResource ThemeController}}"
                    FontSize="18"
                    FontWeight="Bold"
                    IndicatorAnimationStyle="{StaticResource ColorWheelIndicatorAnimation}"
                    IsRunning="True"
                    Opacity="{Binding OpacityProgressBar}"
                    VerticalAlignment="Center">
                </telerik:RadBusyIndicator>
            </StackPanel>
        </Border>
    </Popup>


    <!-- TITLE BAR START -->
    <StackPanel 
        Grid.Row="0" 
        Margin="0,17,0,0"
        Opacity="{Binding OpacityContent}"
        x:Name="titleBar">

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="50"/>
            </Grid.ColumnDefinitions>


            <TextBlock 
                FontWeight="Bold"
                FontSize="18"
                Foreground="{Binding ActiveTheme.ForegroundAlt1, Source={StaticResource ThemeController}}" 
                Grid.Row="0"
                Grid.Column="1"
                Margin="0,0,0,0"
                Opacity="0.5"
                Tap="Title_Tap" 
                Text="title"/>
        </Grid>
    </StackPanel>
    <!-- TITLE BAR END -->

    <!-- PIVOTS -->
    <!-- Taken from: http://social.msdn.microsoft.com/Forums/wpapps/en-us/1baf74fa-0ddd-4226-a02d-a7fc9f80374d/pivot-static-header-like-twitter-app?forum=wpdevelop-->
    <controls:Pivot
        x:Name="MainPivot"
        Background="{Binding ActiveTheme.Background, Source={StaticResource ThemeController}}"
        Foreground="{Binding ActiveTheme.Foreground, Source={StaticResource ThemeController}}"
        Margin="0,50,0,0" 
        Opacity="{Binding OpacityContent}"
        Padding="0,0,0,0"
        SelectionChanged="Pivot_SelectionChanged">

        // Pivots defined here, but none of them use OpacityContent
    </controls:Pivot>

</Grid>

Update 2: here are the async methods called after the showProgressBar() method is called:

    public async Task<string> setLocationCoordinates()
    {
        // Get the latitude and longitude.
        status = await this.setCoordinates();
        if (status == "success")
        {
            this.CoordinatesFetched = true;
        }
        this.CoordinatesFetched = false;

        return status;
    }


    public async Task<string> setCoordinates()
    {
        // Need to initialise the tracking mechanism. 
        Geolocator geolocator = new Geolocator();

        // Must determine if GPS is on/off.
        if (geolocator.LocationStatus == PositionStatus.Disabled)
        {
            Status = false;
            // If off, get out.
            return "gps off";
        }
        else
            Status = true;

        // Setup the desired accuracy in meters for data returned from the location service.
        geolocator.DesiredAccuracyInMeters = 50;

        try
        {
            // Taken from: http://bernhardelbl.wordpress.com/2013/11/26/geolocator-getgeopositionasync-with-correct-timeout/
            // Because sometimes GetGeopositionAsync does not return. So you need to add a timeout procedure by your self.

            // get the async task
            var asyncResult = geolocator.GetGeopositionAsync();
            var task = asyncResult.AsTask();

            // add a race condition - task vs timeout task
            var readyTask = await Task.WhenAny(task, Task.Delay(10000));
            if (readyTask != task) // timeout wins
            {
                return "error";
                //throw new TimeoutException();
            }

            // position found within timeout
            Geoposition geoposition = await task;

            // Retrieve latitude and longitude.
            Latitude = Convert.ToDouble(geoposition.Coordinate.Latitude.ToString("0.0000000000000"));
            Longitude = Convert.ToDouble(geoposition.Coordinate.Longitude.ToString("0.0000000000000"));

            // Update the 'Current Location' db record.
            var query = from LocationDbRecord location in App.ViewModel.LocationDb.Locations
                        where location.LocationId == 1
                        select location;

            foreach (LocationDbRecord l in query)
            {
                l.Latitude = Latitude.ToString();
                l.Longitude = Longitude.ToString();
            }

            // Submit the changes to the database. 
            try
            {
                App.ViewModel.LocationDb.SubmitChanges();
            }
            catch (Exception ee)
            {
                Console.WriteLine(ee);
            }

            return "success";
        }
        // If there's an error, may be because the ID_CAP_LOCATION in the app manifest wasn't include. 
        // Alternatively, may be because the user hasn't turned on the Location Services.
        catch (Exception ex)
        {
            if ((uint)ex.HResult == 0x80004004)
            {
                return "gps off";
            }
            else
            {
                // Something else happened during the acquisition of the location.
                // Return generic error message.
                return "error";
            }
        }
    }

And this is the method called after the coordinates are fetched to get the name of the location:

    public void setLocationDetails()
    {
        // Must perform reverse geocoding i.e. get location from latitude/longitude.
        ReverseGeocodeQuery query = new ReverseGeocodeQuery()
            {
                GeoCoordinate = new GeoCoordinate(Latitude, Longitude)
            };
        query.QueryCompleted += query_QueryCompleted;
        query.QueryAsync();
    }

    /**
     * Event called when the reverse geocode call returns a location result.
     **/
    void query_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
    {
        foreach (var item in e.Result)
        {
            Location = item.Information.Address.City;

            // Update the 'Current Location' db record.
            var query = from LocationDbRecord location in App.ViewModel.LocationDb.Locations
                        where location.LocationId == 1
                        select location;

            foreach (LocationDbRecord l in query)
            {
                l.Country = "Somewhere in " + Location;
            }

            // Submit the changes to the database. 
            try
            {
                App.ViewModel.LocationDb.SubmitChanges();
            }
            catch (Exception ee)
            {
                Console.WriteLine(ee);
            }

            // Need to passively update the current location's general location in the locations Db.
            this.updateCurrentLocationLiveTileDbRecord(item.Information.Address.City, item.Information.Address.Country, item.GeoCoordinate.Latitude, item.GeoCoordinate.Longitude);
            return;
        }
    }
c#
windows-phone-8
windows-phone
async-await
asked on Stack Overflow Jun 9, 2014 by Barrrdi • edited Jun 9, 2014 by Barrrdi

3 Answers

0

Well, this happens because Opacity is inherited for any nested elements by those they contain them. You'll have to get the popup and your stackpanel where your busy indicator lies, in a single cell grid to stop the opacity inheritance.

<Grid>
    <Popup Opacity="{Binding ...}"/>
    <ProgressBar Opacity="{Binding ...}"/>
</Grid>
answered on Stack Overflow Jun 9, 2014 by Pantelis
0

Try changing your setLocationCoordinates method to this:

public Task<string> setLocationCoordinates() {
    return Task.Run(async delegate() {
        // Get the latitude and longitude.
        status = await this.setCoordinates();
        this.CoordinatesFetched = status == "success";
        return status;
    });
}

While I was editing this code I noticed that you always set CoordinatesFetched to false. Is that intended? Or did I miss something? (In my code it depends on the value of status.)

Also, like @Pantelis said - you should use Visibility instead of Opacity for showing/hiding controls whenever possible (which is about 99% of the cases).

answered on Stack Overflow Jun 9, 2014 by yasen
0

Okay, so I got this to work by adding:

this.loadingOverlay.IsOpen = true;

before setLocationCoordinates() was called, and then manually setting it to false after it was done, because the "this.IsProgressBarOpen = false" line in showProgressBar() didn't seem to update this.loadingOverlay's IsOpen property for some reason that I'm looking into right now. If anyone has any idea why, then please let me know as I'm curious.

Thanks all for the advice and recommendations. I will take it on board.

answered on Stack Overflow Jun 9, 2014 by Barrrdi

User contributions licensed under CC BY-SA 3.0