5
votes

I try to rewrite my UWP C# app for Windows10 to Xamarin app using XAML. But Binding (for example here in ListView ItemSource=...) is not working for me and I don´t know why.

Visual Studio tells me, Cannot Resolve Symbol Recording due to unknown Data Context.

Here is my XAML (MainPage.xaml) for testing purpose:

<?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:XamarinTest;assembly=XamarinTest"
             x:Class="XamarinTest.MainPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.View>
                            <StackLayout Orientation="Horizontal">
                                <Image Source="Accept" WidthRequest="40" HeightRequest="40" />
                                <StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
                                    <Label Text="TEST" HorizontalOptions="FillAndExpand" />
                                    <Label Text="TEST" />
                                </StackLayout>
                            </StackLayout>
                        </ViewCell.View>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

Here is C# (MainPage.xaml.cs):

namespace XamarinTest
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.AllTestViewModel = new RecordingViewModel();
            this.BindingContext = AllTestViewModel;                
        }
        public RecordingViewModel AllTestViewModel { get; set; }
    }
}

And finally ViewModel (RecordingViewModel.cs):

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinTest.Model;

namespace XamarinTest.ViewModel
{
    public class RecordingViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Recording> Recordings { get; } = new TrulyObservableCollection<Recording>();

        public RecordingViewModel()
        {
            Recordings.Add(new RecordingTest2()
            {
                TestName = "Test 1",
                TestNote = "Vytvoreni DB",
                TestTime = new TimeSpan(0, 0, 0)
            });
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
    {
        public TrulyObservableCollection()
        {
            CollectionChanged += FullObservableCollectionCollectionChanged;
        }

        public TrulyObservableCollection(IEnumerable<T> pItems) : this()
        {
            foreach (var item in pItems)
            {
                this.Add(item);
            }
        }

        private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
                }
            }
        }

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
            OnCollectionChanged(args);
        }
    }
}

Everything (models and viewmodels) are working in native UWP Windows 10 app. Only the Binding and making same view is problem in Xamarin. Could someone please help with binding? Thx.

EDIT

Recording.cs is here:

using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XamarinTest.Model
{
    public abstract class Recording : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string TestName { get; set; }
        private TimeSpan _testTime;
        private string _testNote;
        private string _actualIco = "Play";
        private bool _isActive = false;
        private bool _enabled = true;
        public double IcoOpacity { get; private set; } = 1.0;
        public string ActualIco
        {
            get => _actualIco;
            set
            {
                if (_actualIco == null) _actualIco = "Admin";
                _actualIco = value;
                NotifyPropertyChanged("ActualIco");
            }
        }
        public bool IsActive
        {
            get => _isActive;
            set
            {
                if (_isActive == value) return;
                _isActive = value;
                IcoOpacity = !value ? 1.0 : 0.3;
                NotifyPropertyChanged("IsActive");
                NotifyPropertyChanged("IcoOpacity");
            }
        }
        public bool Enabled
        {
            get => _enabled;
            set
            {
                if (_enabled == value) return;
                _enabled = value;
                NotifyPropertyChanged("Enabled");
            }
        }
        public string TestNote
        {
            get => _testNote;
            set
            {
                if (_testNote == value) return;
                _testNote = value;
                NotifyPropertyChanged("TestNote");
            }
        }
        public TimeSpan TestTime
        {
            get => _testTime;
            set
            {
                if (_testTime == value) return;
                _testTime = value;
                NotifyPropertyChanged("TestTime");
            }
        }

        protected Recording()
        {
            TestName = "Unkonwn";
            TestNote = "";
            _testTime = new TimeSpan(0, 0, 0);
        }

        protected Recording(string testName, string testNote, TimeSpan testTime)
        {
            TestName = testName;
            TestNote = testNote;
            _testTime = testTime;
        }
        public string OneLineSummary => $"{TestName}, finished: "
                                        + TestTime;

        private void NotifyPropertyChanged(string propertyName = "")
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public abstract bool playTest();

    }
}

I tried add DataContext in XAML (postet in origin question), because of intellisence like this:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:dvm="clr-namespace:XamarinTest.ViewModel"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{system:Type dvm:RecordingViewModel}"

and this to Grid:

<Label Text="{Binding Recordings[0].TestName}" Grid.Row="0" Grid.Column="2" />

IntelliSence is OK, but text doesn´t show in app.

2
XF XAML uses BindingContext, not DataContextJason
@Jason I tried it declared BidingContext in public MainPage() evene without using DataContext in XAML, but it didn´t work too.Jakub Kameniar
there is way too much going on here to decipher - I'd suggest you boil it down to a much simpler test case - just a page with a Label and a VM - get that to work, and then build from there. Or look at any of the Xamarin samples.Jason
That´s no problem. I could only make string var in MainPage() and set it to BindingContext and try it to set by Binding to only Label in XAML (rest is commented) and it is still not working. I see many examples, but when I try something with binding and XAML it is not working.Jakub Kameniar

2 Answers

5
votes

Finally is working!

XAML should looks like code below. Imporant is xmls:viewModel="..." and <ContentPage.BindingContext>...</>.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"                                                  
             xmlns:viewModel="clr-namespace:XamarinTest.ViewModel;assembly=XamarinTest"
             x:Class="XamarinTest.MainPage"         
             >
    <ContentPage.BindingContext>
        <viewModel:RecordingViewModel/>
    </ContentPage.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Recordings}" Grid.Row="1" Grid.Column="1">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.View>
                            <StackLayout Orientation="Horizontal">
                                <Image Source="Accept" WidthRequest="40" HeightRequest="40" />
                                <StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
                                    <Label Text="{Binding TestName}" HorizontalOptions="FillAndExpand" />
                                    <Label Text="{Binding TestNote}" />
                                </StackLayout>
                            </StackLayout>
                        </ViewCell.View>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

and MainPage.xaml.cs is okey

namespace XamarinTest
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.AllTestViewModel = new RecordingViewModel();
            this.BindingContext = AllTestViewModel;                
        }
        public RecordingViewModel AllTestViewModel { get; set; }
    }
}
-1
votes

looking at your ViewModel, it looks like there is no Recording member, but you do have a Recordings member.

EDIT

So you are adding your DataContext in the code behind so ignore the Xaml part.

Your View (MainPage.xaml) has a ViewModel(RecordingViewModel.cs). The ViewModel has a member called Recordings (a collection of type Recording). But in your Xaml, you are try to bind to Recording.

Change:

<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">

to:

<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recordings}">

2nd EDIT

The only Labels in your example is the one inside of the ListView yes?

If so, you can access the Recordings children like TestNote by: