0
votes

I'm trying to create a "desktop" page, to display icons (programs).

Now the basic idea is to use a 3x3 Grid and each Cell represents a spot for an icon.

I used to just call the View directly from my ViewModel, but I wanna stay true to MVVM and thought I'd attempt to get it to work using DataBinding and an ObservableCollection in a custom Grid class.

Here's my code

ViewModel.cs

/*
ViewModel for the DashboardView
*/
public class DashboardViewModel : ViewModelBase
{
    INavigationService navigation;
    INetworkService network;

    public DashboardViewModel(INavigationService _navigation,
           INetworkService _network
           )
    {
        navigation = _navigation;
        network = _network;
        //modelService = _modelService;

        Startup();
    }

    /*
    Called when the application starts
    */
    public async void Startup()
    {
        //Bunch of network mumbojumbo that returns a bunch of services as JSON

        IList<JToken> results = result["data"]["services"].Children().ToList();
        foreach (JToken r in results)
        {
            ServiceModel temp = JsonConvert.DeserializeObject<ServiceModel>(r.ToString());
            Services.Add(temp);
            Debug.WriteLine(r);
        }
    }

    /*
    Holds all services displayed on the dashboard
    */
    public IList<ServiceModel> Services { get; } = new ObservableCollection<ServiceModel>();

This is the relevant code in View.cs

        serviceBoard = new DashboardDesktop()
        {
            VerticalOptions = LayoutOptions.FillAndExpand,
            HorizontalOptions = LayoutOptions.FillAndExpand,
            BackgroundColor = Color.FromRgb(0.94, 0.94, 0.94),

            RowDefinitions =
            {
                new RowDefinition() { Height = new GridLength(0.333, GridUnitType.Star) },
                new RowDefinition() { Height = new GridLength(0.333, GridUnitType.Star) },
                new RowDefinition() { Height = new GridLength(0.333, GridUnitType.Star) }
            },

            ColumnDefinitions =
            {
                new ColumnDefinition() { Width = new GridLength(0.333, GridUnitType.Star) },
                new ColumnDefinition() { Width = new GridLength(0.333, GridUnitType.Star) },
                new ColumnDefinition() { Width = new GridLength(0.333, GridUnitType.Star) }
            }
        };

        serviceBoard.SetBinding(DashboardDesktop.ServiceIconsProperty, "Services");

And here's the custom grid class where I attempt to create my ServiceIcon property that my ObservableCollection should bind to

public class DashboardDesktop : Grid
{
    public static readonly BindableProperty ServiceIconsProperty =
         BindableProperty.Create
        (
        "ServiceIconsProperty",
        typeof(IList<ServiceModel>),
        typeof(DashboardDesktop),
        null,
        BindingMode.OneWay,
        (bindable, value) =>
        {
            return true;
        },
        (bindable, oldValue, newValue) =>
        {
            //do stuffs here
            //This is random stuff so I could add a breakpoint here
            int x = 10;
            var b = (IList<ServiceModel>)newValue;

            //Determine if an service have been removed or added
        });

    private IList<ServiceModel> Services { set; get; }


    public DashboardDesktop()
    {

    }
}

Now I've tried to put a breakpoint inside the "OnChanged" lambda function in the ServiceIconProperty but it never stops there even tho I add a lot of elements into the ObservableCollection in my ViewModel

What am I doing wrong?

2

2 Answers

1
votes

You are binding the collection, not the collection's content.

So this binding is called only once (at the time it is created) and never again.

In the setter of Services, you have to subscribe to change events of the ObservableCollection object you received.

1
votes

You are doing several things wrong.

  1. You are creating a Binding, but not giving it a binding source (there are multiple ways to do that).
  2. The propertyName parameter of BindableProperty.Create() should not include the "Property" suffix.
  3. There is no reason to include a "Services" collection on your DashboardDesktop class.
  4. You aren't providing any visuals for the ServiceIcons property.

To fix the first problem you simply need to set the BindingSource of serviceBoard to an instance of the viewmodel OR use a different overload of SetBinding() that allows you to specify the Source object.

Number four is pretty important too - but maybe you were planning to insert/remove visual children from within your property callback where the comment //Determine if an service have been removed or added is located?

In any case, I'm not sure that I would take this overall approach in the first place. It would be cleaner (and more in the spirit of XAML) to instead define your own custom templated layout container. Essentially, subclassing from Layout<View> and adding your grid-based positioning logic accordingly, and adding an ItemsSource and ItemTemplate. You should be able to look at the built-in ListView class to see how Xamarin implemented those features there and borrow from it.