Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Image in listview is invisible even though its IsVisible is true.

robs23robs23 Member ✭✭

I have this app in XF 4.4 that I've been developing for 2 years now. Lately I noticed some weird Image control behavior. I have a ListView of ViewCells containing Image control. The image's IsVisible property is bound to a property in ProcessAction class. When the user taps a row, the image becomes visible, when it taps it once more it goes hidden. I noticed that when I make some of images visible, close the page and then open it once more (all the time with the same ViewModel) the images are hidden even though their IsVisible properties are correctly set to true. So when I tap the ViewCell once more, apparently nothing happens, as then Image's IsVisible is set to false. So it behaves like Images were not properly restored from ViewModel when page loads. It might be related to GlideX as I lately added it to my project, though I think it worked this way before.

<ListView x:Name="lstActions" ItemsSource="{Binding Items}" ItemTapped="Handle_ItemTapped" VerticalOptions="FillAndExpand" HasUnevenRows="True" SeparatorVisibility="Default" CachingStrategy="RecycleElement"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Padding="10"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="8*"/> <ColumnDefinition Width="2*"/> </Grid.ColumnDefinitions> <Image Source="tick_green.png" Grid.Row="0" Grid.Column="0" HeightRequest="50" IsVisible="{Binding IsChecked}" IsEnabled="{Binding IsMutable}"/> <Label Text="{Binding ActionName}" LineBreakMode="WordWrap" Style="{DynamicResource ListItemTextStyle}" FontSize="Medium" FontAttributes="Bold" IsEnabled="{Binding IsMutable}" Grid.Row="0" Grid.Column="1"/> <Image Source="exclamation_red.png" Grid.Row="0" Grid.Column="2" HeightRequest="50" IsVisible="{Binding IsRequired}"/> </Grid> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView>

Code behind:

`ActionListViewModel vm;

    public ActionList(ActionListViewModel _vm)
    {
        InitializeComponent();
        vm = _vm;
        BindingContext = vm;
    }

    async void Handle_ItemTapped(object sender, ItemTappedEventArgs e)
    {
        if (e.Item == null)
            return;
        if ((bool)((IActionKeeper)e.Item).IsMutable)
        {
            if ((bool)((IActionKeeper)e.Item).IsChecked)
            {
                ((IActionKeeper)e.Item).IsChecked = false;
                IActionKeeper item = ((IActionKeeper)e.Item);
                if (vm.CheckedItems.Any(i => i.ActionId == item.ActionId))
                {
                    vm.CheckedItems.Remove(vm.CheckedItems.FirstOrDefault(i => i.ActionId == item.ActionId));
                }
            }
            else
            {
                ((IActionKeeper)e.Item).IsChecked = true;
                IActionKeeper item = ((IActionKeeper)e.Item);
                if (!vm.CheckedItems.Any(i => i.ActionId == item.ActionId))
                {
                    vm.CheckedItems.Add(ToProcessAction(item));
                }

            }
        }
        else
        {
            DependencyService.Get<IToaster>().LongAlert($"Czynność została oznaczona jako wykonana w innej obsłudze i nie można jej zmienić..");
        }



        //Deselect Item
        ((ListView)sender).SelectedItem = null;
    }`

Items property in ActionListViewModel:

private ObservableCollection<IActionKeeper> _items { get; set; } public ObservableCollection<IActionKeeper> Items { get { return _items; } set { if (_items != value) { _items = value; OnPropertyChanged(); } } }

And ProcessAction class that works as a model for listview's cell:

public class ProcessAction : Entity<ProcessAction>, IActionKeeper, INotifyPropertyChanged
    {
        public int ProcessActionId { get; set; }
        public override int Id
        {
            set => value = ProcessActionId;
            get => ProcessActionId;
        }

        public Nullable<int> ProcessId { get; set; }
        public Nullable<DateTime> PlannedStart { get; set; }
        public Nullable<DateTime> PlannedFinish { get; set; }
        public string PlaceName { get; set; }
        public Nullable<int> ActionId { get; set; }
        public string ActionName { get; set; }
        public int? GivenTime { get; set; }
        public int? HandlingId { get; set; }
        public string Type { get; set; }
        public int? PlaceId { get; set; }
        public bool? _IsMutable { get; set; } = true;

        public bool? IsMutable
        {
            get
            {
                return _IsMutable;
            }
            set
            {
                if (value != _IsMutable)
                {
                    _IsMutable = value;
                    OnPropertyChanged();
                }
            }
        }

        public bool IsRequired
        {
            get
            {
                if (PlannedStart == null)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }

        public bool? _IsChecked { get; set; }
        public bool? IsChecked
        {
            get
            {
                if (_IsChecked == null)
                {
                    return false;
                }
                return _IsChecked;
            }
            set
            {
                if (value != _IsChecked)
                {
                    _IsChecked = value;
                    OnPropertyChanged();
                }
            }
        }

        public List<DateTime?> LastChecks { get; set; }
        public DateTime? LastCheck
        {
            get
            {
                if (LastChecks.Any())
                {
                    return LastChecks.FirstOrDefault();
                }
                else
                {
                    return null;
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

Answers

  • JarvanJarvan Member, Xamarin Team Xamurai

    I created a basic demo to test the function and it works fine. The image's visibility could be changed when tapping the item.

    But the images' state will be lost when colsing the page. If you want to save the status, please use the database.
    Check the tutorial:
    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases

    Here is the demo file without using the database, you can check the code.

  • robs23robs23 Member ✭✭

    @Jarvan, thanks for your answer. Yes, that's how it's supposed to work. But it doesn't work like that in my case. I think I have found a culprit, though. It's this part of my xaml grid definition:
    <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="8*"/> <ColumnDefinition Width="2*"/> </Grid.ColumnDefinitions>

    Column width of 1st column is set to "Auto", because I want it to show when "tick_green.png" IsChecked property is set to true. If IsChecked is false, I don't want this 1st column to take up any space. And it partially works - when user taps the row, the image's IsChecked is set to true and the column appears with the image in it. Another tap causes the image's IsChecked being set to false and image/column goes hidden. The problem is, that when the user goes to other page and then comes back here, ViemModel is restored from property but it doesn't trigger IsChecked change so the setter is not triggered and hence OnPropertyChanged() is not called.. How can I fix this? I know that when I set 1st column with to e.g. "2*" then it works all the time, but then 1st column is visible no matter if image is visible or not, I don't like that.

  • JarvanJarvan Member, Xamarin Team Xamurai

    The problem is, that when the user goes to other page and then comes back here, ViemModel is restored from property but it doesn't trigger IsChecked change

    To preserve and restore the page state, try to use Application.Current.Properties. App class provides Properties dictionary which could help to save the status value.

    Check the tutorial:
    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/application-class#properties-dictionary

  • robs23robs23 Member ✭✭

    Thanks, but I it won't help - I keep view model state between those pages and all properties are in their preserved state, it's just that IsChecked set is not called when I reassign the preserved view model as page's binding context. The setter is only called from listview's ItemTapped event, so when it's restored from preserved ViewModel it's not triggered and hence column witdth is not updated (stay hidden) to contain the image.. All other bound properties look properly in restored view

  • robs23robs23 Member ✭✭

    In other words - when I set 1st column width to e.g. "2*" or some other static value, the image in 1st column is visible because its IsChecked property IS TRUE, but column width set to Auto is hidden as there was no image in it. It's like column width Auto reacts to IsChecked setter, but doesn't "see" the image in restored viewmodel (even though image's IsVisible is equal true then)

  • JarvanJarvan Member, Xamarin Team Xamurai

    Try to create a singleton class as the viewModel class. These values ​​will be retained during navigation.

    public class DataClass
    {
        private DataClass() { }
    
        private static DataClass instance = null;
    
        public static DataClass Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new DataClass();
                }
                return instance;
            }
        }
    
        public ObservableCollection<_Model> collection = new ObservableCollection<_Model>();
    }
    
Sign In or Register to comment.