Home > database >  Custom collectionView implementation
Custom collectionView implementation

Time:01-24

I am trying to implement a CollectionView as in the picture below. As you can see, if an element is selected, it has a corresponding check mark in the upper right corner

enter image description here

First I tried to find the AbstractLayout category ID in the SelectionChanged event, and already inside it I was looking for an element named = "showIfSelected", but when searching inside collectionView I always got null. I have read several articles about why this is the case, but I have not found a solution to the problem. Maybe someone can tell me how to achieve the result I need in the end?

.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:viewmodels="clr-namespace:Paraglider.MobileApp.ViewModels"
    xmlns:models="clr-namespace:Paraglider.MobileApp.Models"
    x:DataType="viewmodels:CatalogPageViewModel"
    x:Class="Paraglider.MobileApp.Pages.CatalogPage">

    <Grid BackgroundColor="#FFFEF6">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

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

        ...

        <ScrollView Grid.Row="1" Grid.ColumnSpan="2" Margin="20, 0" VerticalScrollBarVisibility="Never">

            <CollectionView
                x:Name="collectionView"
                Grid.Row="2" Grid.ColumnSpan="2"
                ItemsSource="{Binding Categories}"
                SelectionMode="Multiple"
                SelectionChanged="CollectionView_SelectionChanged">

                <CollectionView.Header>...</CollectionView.Header>

                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Vertical" Span="{OnIdiom Default=2}" />
                </CollectionView.ItemsLayout>

                <CollectionView.ItemTemplate>
                    <DataTemplate x:DataType="models:Category">
                        <AbsoluteLayout x:Name="{Binding Id}">
                            
                            <Image 
                                Aspect="AspectFit" 
                                WidthRequest="165" 
                                Source="pzv.svg" 
                                Margin="0, 10" />
                            
                            <Image
                                x:Name="showIfSelected"
                                Aspect="AspectFit" 
                                AbsoluteLayout.LayoutBounds="130, 0, autoSize, autoSize"  
                                Source="selected_category.svg" 
                                IsVisible="True" >
                                <Image.Shadow>
                                    <Shadow Brush="Black" Offset="0, 10" Opacity="0.1" Radius="15" />
                                </Image.Shadow>
                            </Image>
                            
                        </AbsoluteLayout>
                    </DataTemplate>
                </CollectionView.ItemTemplate>

            </CollectionView>

        </ScrollView>

    </Grid>
</ContentPage>

.xaml.cs:

public partial class CatalogPage : ContentPage
{
    public CatalogPage(CatalogPageViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }

    private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var prev = e.PreviousSelection.Select(x => (Category)x).ToList();
        var current = e.CurrentSelection.Select(x => (Category)x).ToList();

        var unselected = prev.ExceptBy(current.Select(x => x.Id), x => x.Id).ToList();

        foreach (var item in unselected)
        {
            var layout = this.FindByName<AbsoluteLayout>($"{item.Id}");
            var image = layout.FindByName<Image>("showIfSelected");
            image.IsVisible = false;
        }

        var selected = current.ExceptBy(prev.Select(x => x.Id), x => x.Id).ToList();

        foreach (var item in selected)
        {
            var layout = this.FindByName<AbsoluteLayout>($"{item.Id}");
            var image = layout.FindByName<Image>("showIfSelected");
            image.IsVisible = true;
        }
    }
}

CodePudding user response:

As suggested by Jason, you can directly bind IsVisible to a property of your Model like below:

XAML:

 <Image
     Aspect="AspectFit"          
     Source="selected_category.svg" 
     IsVisible="{Binding IsSelected, Mode=TwoWay}" >
                  
 </Image>

Model:

public partial class Category : ObservableObject
{
    [ObservableProperty]
    private bool isSelected;
}

CodePudding user response:

Combine styling: https://learn.microsoft.com/en-us/dotnet/maui/user-interface/styles/xaml?view=net-maui-7.0

And visual states: https://learn.microsoft.com/en-us/dotnet/maui/user-interface/visual-states?view=net-maui-7.0

You can use the VisualStates, to determine what style to be used. CollectionView has a VisualState Selected.

You can name your visual element, lets say that picture, and use styling to change properties of exactly that visual element.

No need of this wall of text, everything can be done in the XAML, with zero code behind it.

Edit: Other users suggested, you may need example how to do it. Here is a working code, from one of my test projects (on MAUI NET 7.0)

<Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal" />
                <VisualState x:Name="Selected">
                    <VisualState.Setters>
                        <Setter Property="Stroke".../>
                        <Setter Property="BackgroundColor" ..."/>
                        <Setter TargetName="line1"
                            Property="Label.TextColor"
                            Value="Black" />...

The important part you can notice, is that I have a Label VisualElement, that has the Name "line1", and I access the property TextColor, and apply the value Black, when the item gets selected.

This throws terrible runtime exceptions, if you apply this style, and fail to provide child VisualElement of type Label, and the specified Name.

On the plus side, the alternative to this is more time consuming (in magnitudes) and also prone to errors.

I hope this helps.

  • Related