GuidoG
GuidoG

Reputation: 12014

xamarin form does not knows controls (inside ListView.ItemTemplate's DataTemplate) by its x:Name

I have this in my xaml

 <StackLayout VerticalOptions="CenterAndExpand">
                <ListView x:Name="listViewItems" Margin="1,1" BackgroundColor="White" HasUnevenRows="True" >
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <ContentView Padding="3" VerticalOptions="FillAndExpand" >
                                    <!-- BorderColor="Gray" BackgroundColor="{StaticResource ElementBackgroundColor}"> -->
                                    <Grid BackgroundColor="White" ColumnSpacing="0">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="35"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>

                                        <Label x:Name="labelCode" Grid.Column="0" Grid.RowSpan="2" Text="{Binding Code}" VerticalOptions="Center" HorizontalOptions="FillAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
                                        <StackLayout Grid.Column="1" Spacing="0" Orientation="Vertical" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand">
                                            <Label x:Name="labelEnglish" Text="{Binding NameEnglish}" VerticalOptions="Start" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
                                            <Label x:Name="labelRussian" Text="{Binding NameRussian}" VerticalOptions="StartAndExpand" TextColor="{StaticResource TextColor}" BackgroundColor="White" FontAttributes="Bold" FontSize="{StaticResource FontSizeLabelLittle}" />
                                        </StackLayout>
                                    </Grid>
                                </ContentView>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>

And I want to do this in code

labelCode.IsVisible = false;

But I get compile error

Severity Code Description Project File Line Suppression State Error CS0103 The name 'labelCode' does not exist in the current context gttCompound C:\Development\Guido\Xamarin\gttCompound\gttCompound\Pages\PageViewOrSelectItem.xaml.cs 33 Active

I found a question/answer here with the same problem, but I checked and tried it all with no result

So I am at a loss here, how do I get my form to know these controls ?

EDIT

I also find more similar questions, but the always want to fill up an element in the listview in the code behind, that is not my problem.
I use binding for all the content.
My problem is that I want to hide one or two labels in the listview.
I have now 3 labels in the listview, but in some cases not all 3 will be filled, and when I just leave it like it is than the binding will show them empty, but it makes the gridrow to large in height, so I wanted to see if in these cases I can hide the empty labels to the gridrow will not be so high anymore.
I have now done this by building up the gridcontent in the code behind, which until now seems to work.

Upvotes: 0

Views: 160

Answers (2)

ToolmakerSteve
ToolmakerSteve

Reputation: 21243

Based on refer to controls inside ListView / add new property to model,
you could bind IsVisible property of each label to a property in the model:

  <Label x:Name="labelEnglish" IsVisible="{Binding HasEnglish}" .../>

Add to the model:

  public bool HasEnglish => !string.IsNullOrEmpty(NameEnglish);

Upvotes: 0

Paweł Kanarek
Paweł Kanarek

Reputation: 2627

You get error because you cannot directly access objects by name in page code-behind (.cs file) within DataTemplate of ListView. If you really want to access this by name you then you should move <ViewCell> to the new .xaml file, then you could access objects by name in code-behind.

As @ewerspej mentioned "You cannot reference elements inside a DataTemplate by their x:Name. The reason being that DataTemplate are used to instantiate VisualElements dynamically at runtime and the names must be unique identifiers."

But the real solution is to use MVVM approach and DataBinding, so you wouldn't change ViewCells objects directly, but rather modify Model class of the given view cell. You would need to implement INotifyPropertyChanged interface into ViewModel, and then use binding for IsVisible property in your ViewCell

https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/data-and-databinding.

Take a look at this implementation of very simple MVVM pattern:

this is a MainPage.xaml content

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:App1"
             x:Class="App1.MainPage"
             x:DataType="local:MyPageViewModel">

    <StackLayout VerticalOptions="CenterAndExpand">
        <ListView ItemsSource="{Binding ListViewItemsSource}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell x:Name="cell" x:DataType="local:ListViewModel">
                        <Label Text="{Binding Title}"
                               IsVisible="{Binding IsVisible}" />
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage> 

we set here binding for ItemsSoruce for ListView and also we bind Title & IsVisible properties.

this is MainPage.xaml.cs

using Xamarin.Forms;

namespace App1
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            BindingContext = new MyPageViewModel();
        }
    }
}

we set there BindingContext for whole page.

and those are models:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace App1
{
    public class ListViewModel : BaseModel
    {
        private string _title;
        private bool _isVisible;

        // properties that will fire PropertyChanged (auto genertated via JetBrains Rider)
        public string Title
        {
            get => _title;
            set => SetField(ref _title, value);
        }

        public bool IsVisible
        {
            get => _isVisible;
            set => SetField(ref _isVisible, value);
        }
    }
    public class MyPageViewModel : BaseModel
    {
        // Data source for your ListView
        public ObservableCollection<ListViewModel> ListViewItemsSource { get; }

        public MyPageViewModel()
        {
            // Init source for ListView
            ListViewItemsSource = new ObservableCollection<ListViewModel>(new[]
            {
                new ListViewModel() { IsVisible = true, Title = "1" },
                new ListViewModel() { IsVisible = true, Title = "2" },
                new ListViewModel() { IsVisible = true, Title = "3" }
            });

            // Change second item to be invisible after 2 seconds
            Task.Run(async () =>
            {
                await Task.Delay(2000);
                ListViewItemsSource[1].IsVisible = false;
            });
        }
    }


    // base class that implements INotifyPropertyChanged (auto genertated via JetBrains Rider)
    public class BaseModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
}

I've created Base Model, that can be used as base class for models for ViewCells and ViewModels for whole pages. In View Model im creating New list of items that are binded to View. In constructor I added delay to show that changing IsVisible property in Models changes also presentation of the View`.

In the ListViewModel i've created properties that can bind to View. Look how i call SetField in setters - this is the main idea - Model notifies View about change.

Most of the implementation code was autogenerated via JetBrains Rider IDE, but i think that Visual Sutido also can auto that basic code.

Upvotes: 2

Related Questions