Forum Xamarin.Forms

Change Content of ContentView.Content by binding to VM does only work on UWP

Hello community,
I got a XAML ContentPage:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:progressBar="clr-namespace:Syncfusion.XForms.ProgressBar;assembly=Syncfusion.SfProgressBar.XForms"
xmlns:cardView="clr-namespace:Syncfusion.XForms.Cards;assembly=Syncfusion.Cards.XForms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:converters="clr-namespace:baufeo.Tools.Converters" 
             xmlns:converters2="clr-namespace:baufeo.Views.Converters"
             xmlns:xforms="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
             x:Class="baufeo.Views.BudgetRechner.AddPosition"
             xmlns:extensions="clr-namespace:baufeo.Extensions"
             xmlns:UIDesign="clr-namespace:baufeo.Helpers.Data" xmlns:graphics="clr-namespace:Syncfusion.XForms.Graphics;assembly=Syncfusion.Core.XForms"
             Title="Position hinzufügen">
    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:TrueToFalseConverter x:Key="TrueToFalseConverter"/>
            <converters2:DurchHundertConverter x:Key="DurchHundertConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>
    <ContentPage.Content>
        <Grid BackgroundColor="{Static UIDesign:UIDesignData.PrimaryColor}">
            <graphics:SfGradientView>
                <graphics:SfGradientView.BackgroundBrush>
                    <graphics:SfLinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                        <graphics:SfLinearGradientBrush.GradientStops>
                            <graphics:SfGradientStop Color="{DynamicResource PrimaryColor}" Offset="0"/>
                            <graphics:SfGradientStop Color="{DynamicResource AccentPrimaryColor}" Offset="{Binding Progress, Converter={StaticResource DurchHundertConverter}, Mode=OneWay}"/>
                        </graphics:SfLinearGradientBrush.GradientStops>
                    </graphics:SfLinearGradientBrush>
                </graphics:SfGradientView.BackgroundBrush>
            </graphics:SfGradientView>
            <StackLayout IsVisible="{Binding StartInputMode, Converter={StaticResource Key=TrueToFalseConverter}, Mode=OneWay}">
                <xforms:SfListView ItemsSource="{Binding Items}" ItemSize="150" ItemSpacing="5" SelectedItem="{Binding SelectedItem}">
                    <xforms:SfListView.LayoutManager>
                        <xforms:GridLayout SpanCount="3"/>
                    </xforms:SfListView.LayoutManager>
                    <xforms:SfListView.ItemTemplate>
                        <DataTemplate>
                            <Grid HeightRequest="150" WidthRequest="150" HorizontalOptions="CenterAndExpand" VerticalOptions="FillAndExpand"
                                  BackgroundColor="{Static UIDesign:UIDesignData.AccentPrimaryColor}">
                                <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
                                    <Label FontFamily="{StaticResource FontAwesomeLight}"
                                           HorizontalTextAlignment="Center" 
                                           VerticalTextAlignment="Center" 
                                           Text="{Binding IconId}" 
                                           FontSize="Large"
                                           TextColor="{Static UIDesign:UIDesignData.SecondaryColor}"/>
                                    <Label FontSize="Subtitle" VerticalOptions="End" VerticalTextAlignment="End" HorizontalTextAlignment="Center" Text="{Binding Name}" TextColor="{Static UIDesign:UIDesignData.SecondaryColor}"/>
                                </StackLayout>
                            </Grid>
                        </DataTemplate>
                    </xforms:SfListView.ItemTemplate>
                </xforms:SfListView>
            </StackLayout>
            <Grid IsVisible="{Binding StartInputMode}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="1"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <progressBar:SfLinearProgressBar Grid.Row="0" Progress="{Binding Progress}" TrackColor="{DynamicResource SecondaryColor}" ProgressColor="{DynamicResource AccentPrimaryColor}"/>
                <ContentView Grid.Row="1" Content="{Binding CurrentView}"
                          IsEnabled="True"/>
            </Grid>

        </Grid>


    </ContentPage.Content>
</ContentPage>

The second Grid contains a ContentView Element I've bound to:

private View _currentView;
        public View CurrentView
        {
            get => _currentView;
            set
            {
                _currentView = value;
                OnPropertyChanged(nameof(CurrentView));
            }
        }

Everytime the user clicks a button CurrentView gets updated by a different View of my private ObservableCollection<View> AbfrageContentViews { get; set; }

The Collection is dynamicaly seeded with Grid Elements that hosts different View Elements like Labels and stuff.

On UWP following code pushes the new View to CurrentView and does something like an interview:

if (currentListPosition < AbfrageContentViews.Count())
                    {
                       var griditem = (Grid)CurrentView;
               var oldView = griditem;
                        var newView = (Grid)AbfrageContentViews[currentListPosition];
                        newView.Opacity = 0;
                        await oldView.FadeTo(0, 190, Easing.SpringOut);
                        CurrentView = AbfrageContentViews[currentListPosition];
                        await newView.FadeTo(1, 190, Easing.SpringIn);
                        OnPropertyChanged(nameof(Progress));
                    }

Looks like this:

On Android: Nothing happens but the App crashes at the line OnPropertyChanged(nameof(CurrentView));

Could anyone tell me what's going on there?

I'm not sure if ContentView.Content maybe expects something else as Grid for source. But in the documentation I've found that Xamarin.Forms.Layout inherits from View. And why does it work correctly on UWP and not on Android?

Thank you very much!

Answers

  • YelinzhYelinzh Member, Xamarin Team Xamurai

    I created a basic demo to test the function, it works fine on Android. Here is the related code, you could refer to it.

    <ContentView ... Content="{Binding ViewContent}"/>
    

    ViewModel class

    public class ViewModel_2 : INotifyPropertyChanged
    {
        public ObservableCollection<View> ContentCollection { get; set; }
    
        private View viewContent;
        public View ViewContent
        {
            get
            {
                return viewContent;
            }
            set
            {
                if (viewContent != value)
                {
                    viewContent = value;
                    NotifyPropertyChanged(nameof(ViewContent));
                }
            }
        }
    
        public ViewModel_2()
        {
    
            ContentCollection = new ObservableCollection<View>();
            //add views
    
            ViewContent = ContentCollection[0];
        }
        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    Xamarin forums are migrating to a new home on Microsoft Q&A!
    We invite you to post new questions in the Xamarin forums’ new home on Microsoft Q&A!
    For more information, please refer to this sticky post.

  • EightballEightball Member ✭✭
    edited February 24

    Thank you very much @Yelinzh
    I tried your code and added some views and my 'NextViewCommand' like this:

    public class BattlefieldViewModel : BaseViewModel
        {
            public BattlefieldViewModel()
            {
                ObservableCollection<View> ContentCollection = new ObservableCollection<View>();
    
                var grid = new Grid()
                {
                    RowDefinitions = new RowDefinitionCollection
                    {
                        new RowDefinition()
                        {
                            Height = new GridLength(1,GridUnitType.Star),
    
                        },
                        new RowDefinition()
                        {
                            Height = new GridLength(1,GridUnitType.Star)
                        }
                        }
                };
    
                var label = new Label() { Text = "Grid1" };
                var button = new Button() { Text = "Weiter", Command=this.NextViewCommand};
    
                grid.Children.Add(label, 0, 0);
                grid.Children.Add(button, 0, 1);
    
                ContentCollection.Add(grid);
    
                var grid2 = new Grid()
                {
                    RowDefinitions = new RowDefinitionCollection
                    {
                        new RowDefinition()
                        {
                            Height = new GridLength(1,GridUnitType.Star),
    
                        },
                        new RowDefinition()
                        {
                            Height = new GridLength(1,GridUnitType.Star)
                        }
                        }
                };
                var label2 = new Label() { Text = "Grid2" };
                var button2 = new Button() { Text = "Weiter", Command = this.NextViewCommand };
    
    
                grid2.Children.Add(label2, 0, 0);
                grid2.Children.Add(button2, 0, 1);
    
                ContentCollection.Add(grid2);
    
                CurrentView = ContentCollection[0];
    
    
            }
    
            public Command NextViewCommand => new Command(async () => await ExecuteNextView());
    
            private async Task ExecuteNextView()
            {
                CurrentView = ContentCollection[1];
            }
    
            private View _currentView;
            public View CurrentView
            {
                get => _currentView;
                set
                {
                    if(_currentView != value)
                    {
                        _currentView = value;
                        OnPropertyChanged(nameof(CurrentView));
                    }
                }
            }
    
    
            public ObservableCollection<View> ContentCollection { get; set; }
    
        }
    }
    

    First view got presented as expected. After clicking my button ContentCollection gets null do you have any idea?

  • YelinzhYelinzh Member, Xamarin Team Xamurai
    edited 7:31AM

    First view got presented as expected. After clicking my button ContentCollection gets null

    This is because you created two 'ContentCollection' parameters in the mode class. But the 'ExecuteNextView' method can only the 'ContentCollection' property of the 'BattlefieldViewModel' class which is not initialized.

    To fix the issue, try to change the code like below:

    public class BattlefieldViewModel : BaseViewModel
    {
        public BattlefieldViewModel()
        {
            //ObservableCollection<View> ContentCollection = new ObservableCollection<View>();
            ContentCollection = new ObservableCollection<View>();
            ...
            CurrentView = ContentCollection[0];
    
        }
        public Command NextViewCommand => new Command(async () => await ExecuteNextView());
        private async Task ExecuteNextView()
        {
            CurrentView = ContentCollection[1];
        }
        ...
        public ObservableCollection<View> ContentCollection { get; set; }
    }
    
  • EightballEightball Member ✭✭

    OMG I was blind, thank you for your advice.

    Unfortunately my problem already exists in my current build (real case app). I will try to source it out to an extra page and will write a clean structure without awaiting any SourceItem Tasks or hiding something in my XAML-Page to show my Views.

    I guess there are some threadsafety-problems in my coding which are only occuring on Android. Maybe Windows or the fast PC could better handle this dirty coding.

    I will update my question with my solution.

Sign In or Register to comment.