SelectedItem from CollectionView doesn't refresh automatically when Selection is changed

88 Views Asked by At

I can't find out why my ToolbarItem Text isn't refreshing automatically when I change the SelectedItem on my CollectionView. I don't have much experience with xamarin, c# or coding in general but I can't imagine thats a big thing to implement.

I tried to debug the problem with some stops at the "AktuellesProjekt" Property but it didn't help that much. I just don't know what to do to fix this.

<ToolbarItem Text="{Binding AktuellesProjekt.Name}" Order="Primary"/>

Example

ViewModel:

public class DashViewModel : BaseViewModel
{
    //Name
    public const string ViewName = "DashPage";
    
    //Konstruktur
    public DashViewModel()
    {
        //Titel
        Title = "Dash";

        //Commands intialisieren
        LadeProjekte();
    }

    //Commands


    //Fields
    private ObservableCollection<ProjektModel> _projekte = new ObservableCollection<ProjektModel>();
    private ProjektModel _aktuellesProjekt = new ProjektModel();

    //Properties
    public ObservableCollection<ProjektModel> Projekte
    {
        get { return _projekte; }
        set
        {
            if (value == _projekte)
                return;

            OnPropertyChanged(nameof(Projekte));
        }
    }

    public ProjektModel AktuellesProjekt
    {
        get { return _aktuellesProjekt; }
        set
        {
            {
                if (value == _aktuellesProjekt)
                    return;

                OnPropertyChanged(nameof(AktuellesProjekt));
            }
        }
    }

    //Methoden
    public void LadeProjekte()
    {
        for (int i = 2020; i <= 2040; i++)
        {
            var projekt = new ProjektModel
            {
                Name = i,
                BestandGesamt = 12000.00 + (i - 2020) * 1000,
                LetzteAktualisierung = "31.12." + i.ToString(),
                AnzahlEintraege = 76 - (i - 2020) * 10
            };

            Projekte.Add(projekt);
            AktuellesProjekt = Projekte[0];
        }
    }
}

View:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title="{Binding Title}"
             BackgroundColor="{StaticResource NormalBackgroundColor}"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" 
             
             xmlns:fa="clr-namespace:DXApp3.Resources"
             xmlns:dxcv="http://schemas.devexpress.com/xamarin/2014/forms/collectionview"
             xmlns:viewmodels="clr-namespace:DXApp3.ViewModels"
             xmlns:models="clr-namespace:DXApp3.Models"
             
             ios:Page.UseSafeArea="true"
             x:Class="DXApp3.Views.DashPage">

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="{Binding AktuellesProjekt.Name}" Order="Primary"/>
    </ContentPage.ToolbarItems>
    
    <ContentPage.BindingContext>
        <viewmodels:DashViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Content>

        <Grid>

            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>


            <ScrollView Grid.Row="0">
                <dxcv:DXCollectionView ItemsSource="{Binding Projekte}"
                                       SelectedItem="{Binding AktuellesProjekt}"
                                       SelectionMode="Single">
                    
                    <dxcv:DXCollectionView.ItemTemplate>
                        <DataTemplate>
                            <Grid Padding="10,10,10,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>

                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>

                                <Label Grid.Row="0" Grid.Column="0" 
                                       Text="{Binding Name, StringFormat='Finanzen {0}'}"
                                       TextColor="Black"
                                       HorizontalTextAlignment="Start"
                                       VerticalTextAlignment="Center"
                                       FontSize="18"/>
                            </Grid>
                        </DataTemplate>                        
                    </dxcv:DXCollectionView.ItemTemplate>
                    
                </dxcv:DXCollectionView>
            </ScrollView>

            <Button Grid.Row="0" BorderColor="Transparent" BorderWidth="1" FontAttributes="Bold" BackgroundColor="#1968B3" CornerRadius="25" TextColor="White" 
                    HorizontalOptions="End" VerticalOptions="End" WidthRequest="50" HeightRequest="50" Margin="0,0,20,22">
                <Button.ImageSource>
                    <FontImageSource FontFamily="fa-solid" Size="Medium" Glyph="{x:Static fa:FaSolid.Plus}" />
                </Button.ImageSource>
            </Button>
        </Grid>

    </ContentPage.Content>

</ContentPage>
2

There are 2 best solutions below

4
Julian On

Issue #1: Missing update of backing field

First of all, you're not actually updating the backing fields here:

public ObservableCollection<ProjektModel> Projekte
{
    get { return _projekte; }
    set
    {
        if (value == _projekte)
            return;

        OnPropertyChanged(nameof(Projekte));
    }
}

public ProjektModel AktuellesProjekt
{
    get { return _aktuellesProjekt; }
    set
    {
        {
            if (value == _aktuellesProjekt)
                return;

            OnPropertyChanged(nameof(AktuellesProjekt));
        }
    }
}

You need to set the backing fields to value:

public ObservableCollection<ProjektModel> Projekte
{
    get { return _projekte; }
    set
    {
        if (value == _projekte)
            return;

        _projekte = value;

        OnPropertyChanged(nameof(Projekte));
    }
}

public ProjektModel AktuellesProjekt
{
    get { return _aktuellesProjekt; }
    set
    {
        if (value == _aktuellesProjekt)
            return;

        _aktuellesProjekt = value;

        OnPropertyChanged(nameof(AktuellesProjekt));
    }
}

Issue #2: ToolbarItem not updating

Now that the AktuellesProjekt gets updated properly (or at least it should), based on your comment, I've done a quick search and came across issue #14886. It seems that bindings on ToolbarItems don't work properly.

Workaround

A workaround would be to subscribe to the PropertyChanged event of the ViewModel in the code behind and then manually update the Text property of the ToolbarItem:

public class DashPage : ContentPage
{
    private DashViewModel _viewModel;

    public DashPage()
    {
        InitializeComponent();
    }

    protected override void OnBindingContextChanged()
    {
        if(BindingContext is DashViewModel viewModel)
        {
            if(_viewModel != null)
            {
                _viewModel.PropertyChanged -= OnViewModelPropertyChanged;
            }

            _viewModel = viewModel;

            _viewModel.PropertyChanged += OnViewModelPropertyChanged;
        }
    }

    private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == nameof(_viewModel.AktuellesProjekt))
        {
            TitleItem.Text = _viewModel.AktuellesProjekt.Name;
        }
    }
}

For this to work, you'll also need to provide the x:Name attribute to the toolbar item:

<ContentPage.ToolbarItems>
    <ToolbarItem x:Name="TitleItem" Text="{Binding AktuellesProjekt.Name}" Order="Primary"/>
</ContentPage.ToolbarItems>

Simplification

This can be simplified a little, if you don't set the BindingContext in the XAML, but rather in the code-behind.

Remove this from your XAML:

<ContentPage.BindingContext>
    <viewmodels:DashViewModel/>
</ContentPage.BindingContext>

And then your code-behind could look as follows:

public class DashPage : ContentPage
{
    private readonly DashViewModel _viewModel;

    public DashPage()
    {
        InitializeComponent();
        BindingContext = _viewModel = new DashViewModel();
        _viewModel.PropertyChanged += OnViewModelPropertyChanged;
    }

    private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == nameof(_viewModel.AktuellesProjekt))
        {
            TitleItem.Text = _viewModel.AktuellesProjekt.Name;
        }
    }
}
0
Guangyu Bai - MSFT On

Here is an easy way to achieve this.

First, you need to create the CollectionView with the method SelectionChanged.

    <ContentPage.ToolbarItems>
        <ToolbarItem x:Name="Toolbaritem"/>
    </ContentPage.ToolbarItems>

    <CollectionView ItemsSource="{Binding Data}"
                    SelectionMode="Single"
                    SelectionChanged="OnCollectionViewSelectionChanged">
        ...
    </CollectionView>

Second, you can realize the method SelectionChanged by using the following code. Then, You need to get the data from the CollectionView and pass the data to the ToolbarItem.

private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
       var entity = (Entity)e.Item;
       Toolbaritem.Text = entity.Name;
    }

This method will pass the data to the ToolbarItem everytime after you press the CollectionviewItem.