Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

In this tutorial, you're going to learn how to perform simple data binding, in MVVM-fashion with Crosslight, such as:

  • Binding text to labels and text boxes
  • Binding numeric values to slider, text boxes and stepper
  • Binding boolean values to switch
  • Binding DateTime values to DateTime picker
  • Binding image to image view
  • Binding simple list of data to a list

At the end of this tutorial, your app will look like the following:

Panel

Samples:

the_sampleCrosslightDataBinding.zip

Follow these steps:

Table of Contents
maxLevel3
stylecircle

Anchor
preqreuisite
preqreuisite

Prerequisite

Before starting the walkthrough, it is recommended that you have followed these walkthroughs in order:

It is also recommended that you have read through these conceptual topics in order to get a better understanding:

Let's get started. 

Preparing the Project

Create a new Blank project using Crosslight Project Wizard and give it a name of CrosslightDataBinding.

Determining the UI Bindings

Before starting out, it's recommended to get an overall high-level overview of what your app is going to look like. This will help you ease development and provides clear guidelines.

Scrolling down further,

When clicking on the Select Image button, you'll be able to select an image from the photo library, or taking one with your camera. When you click on the Open List button, you should get the following result.

Preparing the ViewModel

Open up the SimpleViewModel.cs located inside CrosslightDataBinding.Core/ViewModels folder and use the following code.

Code Block
languagec#
using System;
using System.Collections.Generic;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Input;
using Intersoft.Crosslight.Mobile;
using Intersoft.Crosslight.ViewModels;
namespace CrosslightDataBinding.ViewModels
{
    /// <summary>
    ///     The ViewModel used in the Blank template. This is the first view you'll see after running the application.
    /// </summary>
    /// <seealso cref="Intersoft.Crosslight.ViewModels.ViewModelBase" />
    public class SimpleViewModel : ViewModelBase
    {
        public SimpleViewModel()
        {
            this.Title = "Crosslight Data Binding";
            this.BoundText = "BoundText";
            this.BoundNumeric = 30;
            this.BoundDate = DateTime.Now;
            this.BoundBool = true;
            this.OpenListCommand = new DelegateCommand(ExecuteOpenList);
            this.SelectImageCommand = new DelegateCommand(ExecuteSelectImage);
        }

        private bool _boundBool;
        private DateTime _boundDate;
        private byte[] _boundImage;
        private int _boundNumeric;
        private string _boundText;

        public bool BoundBool
        {
            get { return _boundBool; }
            set
            {
                if (_boundBool != value)
                {
                    _boundBool = value;
                    OnPropertyChanged("BoundBool");
                }
            }
        }
        public DateTime BoundDate
        {
            get { return _boundDate; }
            set
            {
                if (_boundDate != value)
                {
                    _boundDate = value;
                    OnPropertyChanged("BoundDate");
                }
            }
        }
        public byte[] BoundImage
        {
            get { return _boundImage; }
            set
            {
                if (_boundImage != value)
                {
                    _boundImage = value;
                    OnPropertyChanged("BoundImage");
                }
            }
        }
        public int BoundNumeric
        {
            get { return _boundNumeric; }
            set
            {
                if (_boundNumeric != value)
                {
                    _boundNumeric = value;
                    OnPropertyChanged("BoundNumeric");
                }
            }
        }
        public string BoundText
        {
            get { return _boundText; }
            set
            {
                if (_boundText != value)
                {
                    _boundText = value;
                    OnPropertyChanged("BoundText");
                }
            }
        }

        public DelegateCommand OpenListCommand { get; set; }
        public DelegateCommand SelectImageCommand { get; set; }

        private void ExecuteOpenList(object parameter)
        {
            this.NavigationService.Navigate<SimpleListViewModel>();
        }
        private void ExecuteSelectImage(object parameter)
        {
            List<string> options = new List<string>();
            if (this.MobileService.MediaLibrary.IsSupported())
                options.Add("Choose photo from library");
            if (this.MobileService.Camera.IsSupported())
                options.Add("Capture from camera");
            if (this.MobileService.MediaLibrary.IsSupported())
            {
                this.ActionPresenter.Show("Select Image", options.ToArray(), index =>
                {
                    if (index == 0)
                    {
                        this.MobileService.MediaLibrary.ShowPicker(new MediaLibrarySettings()
                        {
                            MediaType = CameraMediaType.Photo,
                            Source = MediaLibraryType.PhotoLibrary,
                            ImageResultMode = ImageResultMode.Both
                        }, pickerResult =>
                        {
                            if (!pickerResult.IsCancelled)
                                this.BoundImage = pickerResult.ImageData;
                            else
                                this.ToastPresenter.Show("Picker cancelled");
                        });
                    }
                    else if (index == 1)
                    {
                        this.MobileService.Camera.Capture(new CameraCaptureSettings
                        {
                            AllowEditing = true,
                            ThumbnailHeight = 200,
                            ThumbnailWidth = 200,
                            ImageResultMode = ImageResultMode.Thumbnail
                        }, result => { this.BoundImage = result.ThumbnailImageData; });
                    }
                });
            }
            else
                this.ToastPresenter.Show("Media library functions are not supported on this device.");
        }
    }
}

The ViewModel above contains various properties that will bound to the view. Let's elaborate:

  • In the Binding Text section, both of the LblBindText and TxtBindText will be bound to BoundText property of the ViewModel, with its value initialized as "BoundText" in the ViewModel's constructor.
  • In the Binding Numeric Values section, LblBindNumeric, SliderBindNumeric, TxtBindNumeric and StepperBindNumeric will be bound to BoundNumeric, with its value initialized to 30 int he ViewModel's constructor.
  • In the Binding Boolean Values section, LblBindBool and SwitchBindBool will be bound to BoundBool, with its value initialized to True in the ViewModel's constructor.
  • In the Binding DateTime section, LblBindDate and DateBindDate will be bound to BoundDate, with its value initialized to DateTime.Now in the ViewModel's constructor
  • In the Binding Image section, BtnBindImage will be bound to the SelectImageCommand in the ViewModel, which will show an action presenter that will allow you to choose the image from the photo library, or take one with your device camera (if it's supported). The latter feature is not supported in the iOS Simulator, and should be deployed to an iPhone. The ImgBindImage will be bound to the BoundImage property in the ViewModel.
  • In the Binding to List section, BtnBindList will be bound to the OpenListCommand in the ViewModel, which will simply open up a new screen with the list of items.

Preparing the List of Items

For the last section, Binding to List, there are several things to be done for this feature to work properly. Copy the entire Assets folder found in this link and place it inside the CrosslightDataBinding.Core project. Don't forget to set the entire contents' BuildAction to EmbeddedResource.

Image Added

Also do this for Categories.xml and Inventories.xml located inside Assets/Data folder.

Create a new Crosslight ViewModel, give it a name of SimpeListViewModel and place it under CrosslightDataBinding.Core/ViewModels folder. Use the following code.

Code Block
languagec#
using Intersoft.Crosslight.ViewModels;
using System.Xml.Linq;
using System.Reflection;
using System.Collections.ObjectModel;
using CrosslightDataBinding.Models;
using System.Globalization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Intersoft.Crosslight.ViewModels;

namespace CrosslightDataBinding.ViewModels
{
    public class SimpleListViewModel : ListViewModelBase<Item>
    {
        #region Constructors
        public SimpleListViewModel()
        {
            this.Title = "Simple List";
            XDocument doc = XDocument.Load(this.GetType().GetTypeInfo().Assembly.GetManifestResourceStream("CrosslightDataBinding.Core.Assets.Data.Inventories.xml"));
            var itemsQuery = from x in doc.Descendants("Item") select CreateItem(x);
            this.SourceItems = new ObservableCollection<Item>(itemsQuery);
        }
        #endregion
        #region Methods
        private Item CreateItem(XElement x)
        {
            Item item = new Item()
            {
                Name = x.Element("Name").Value,
                Image = x.Element("Image").Value,
                CategoryId = int.Parse(x.Element("CategoryId").Value),
                Quantity = int.Parse(x.Element("Qty").Value),
                Price = decimal.Parse(x.Element("Price").Value, CultureInfo.InvariantCulture),
                Location = x.Element("Location").Value,
                Description = x.Element("Description").Value,
                SerialNumber = x.Element("SerialNumber").Value,
                Notes = x.Element("Notes").Value,
                IsSold = ParseBoolean(x.Element("IsSold")),
                PurchaseDate = DateTime.Parse(x.Element("PurchaseDate").Value, CultureInfo.InvariantCulture),
            };
            if (item.IsSold)
                item.SoldDate = DateTime.Parse(x.Element("SoldDate").Value, CultureInfo.InvariantCulture);
            if (!string.IsNullOrEmpty(item.Image))
                item._thumbnailImage = this.GetBytes(this.GetType().GetTypeInfo().Assembly.GetManifestResourceStream("CrosslightDataBinding.Core.Assets.Images.Thumbnail." + item.Image));
            return item;
        }
        public byte[] GetBytes(Stream input)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                input.CopyTo(ms);
                return ms.ToArray();
            }
        }
        protected override void OnSourceItemsChanged(ICollection<Item> items)
        {
            if (items != null)
                this.Items = items.OrderBy(o => o.Name);
            else
                this.Items = null;
        }
        private bool ParseBoolean(XElement element)
        {
            if (element == null || string.IsNullOrEmpty(element.Value))
                return false;
            return bool.Parse(element.Value);
        }
    }
}

In the ViewModel, there's nothing much here, except for the initialization of items when this ViewModel is constructed. Here, we simply parse from the XML and put them inside SourceItems property of the ViewModel. Next, create a new Crosslight BindingProvider file called SimpleListBindingProvider and place it under CrosslightDataBinding.Core/BindingProviders folder. Use the following code.

Code Block
languagec#
using Intersoft.Crosslight;
namespace #endregionCrosslightDataBinding.Core
{
    public class SimpleListBindingProvider : BindingProvider
    {
        public SimpleListBindingProvider()
        {
            ItemBindingDescription itemBinding = new ItemBindingDescription()
            {
                DisplayMemberPath = "Name",
                DetailMemberPath = "Location",
                ImageMemberPath = "ThumbnailImage"
            };
            this.AddBinding("TableView", BindableProperties.ItemsSourceProperty, "Items");
            this.AddBinding("TableView", BindableProperties.ItemTemplateBindingProperty, itemBinding, true);
        }

Running the Sample

Download and run MVVM Samples here. To use this sample, simply extract the zip file, Restore NuGet packages, and run it on Xamarin Studio and Visual Studio. For more information on restoring NuGet Packages, see 

Second Step

Content for second step.

Third Step

Content for third step.

Conclusion

Congratulations! You've just finished creating your first Crosslight app and ready to show it to the world

    }
}

This is the glue that will bind the Items from the ViewModel to the view. We simply prepare an ItemBindingDescription to be used as the ItemTemplateBinding for our list with the iD specified to TableView. This is Crosslight's built-in identifier when using the TableView/RecyclerViewFragment.

Preparing the BindingProvider

Next, open up SimpleBIndingProvider.cs located inside CrossslightDataBinding.Core/BindingProviders folder and use the following code.

Code Block
languagec#
using Intersoft.Crosslight;
namespace CrosslightDataBinding
{
    public class SimpleBindingProvider : BindingProvider
    {
        public SimpleBindingProvider()
        {
            //Binding Text
            this.AddBinding("LblBindText", BindableProperties.TextProperty, "BoundText", BindingMode.TwoWay);
            this.AddBinding("TxtBindText", BindableProperties.TextProperty, "BoundText", BindingMode.TwoWay);
            //Binding Numeric
            this.AddBinding("LblBindNumeric", BindableProperties.TextProperty, "BoundNumeric");
            this.AddBinding("SliderBindNumeric", BindableProperties.ValueProperty, "BoundNumeric", BindingMode.TwoWay);
            this.AddBinding("TxtBindNumeric", BindableProperties.TextProperty, "BoundNumeric", BindingMode.TwoWay);
            this.AddBinding("StepperBindNumeric", BindableProperties.ValueProperty, "BoundNumeric", BindingMode.TwoWay);
            //Binding Boolean
            this.AddBinding("LblBindBool", BindableProperties.TextProperty, "BoundBool");
            this.AddBinding("SwitchBindBool", BindableProperties.ValueProperty, "BoundBool", BindingMode.TwoWay);
            //Binding Date
            this.AddBinding("LblBindDate", BindableProperties.TextProperty, new BindingDescription("BoundDate", BindingMode.OneWay)
            {
                StringFormat = "{0:ddd, d MMM HH:mmm}"
            });
            this.AddBinding("DateBindDate", BindableProperties.ValueProperty, "BoundDate", BindingMode.TwoWay);
            //Binding Image
            this.AddBinding("BtnBindImage", BindableProperties.CommandProperty, "SelectImageCommand");
            this.AddBinding("ImgBindImage", BindableProperties.ImageProperty, "BoundImage");
            //Binding List
            this.AddBinding("BtnBindList", BindableProperties.CommandProperty, "OpenListCommand");
        }
    }
}

I've commented each section that explains how you can bind the views to the properties in the ViewModel, as previously discussed. For label controls, usually you'll only need to perform one-way binding as the label cannot update the values back to the ViewModel, while for input controls, you need to two-way binding as the input controls can set values back to the ViewModel, we need these values to properly synced with each other, and therefore two-way binding is used.

Running on iOS

Let's get this sample up and running on iOS. Copy over the MainStoryboard.storyboard file and replace the one you found inside CrosslightDataBinding.iOS/Views folder. This storyboard contains the finished layout for the iOS view. Next, create a new Crosslight iOS Table ViewController for iPhone file and give it a name of ListViewController, place it under CrosslightDataBinding.iOS/ViewControllers folder, the replace the contents with the following code.

Code Block
languagec#
using System.Drawing;
using CrosslightDataBinding.Core;
using CrosslightDataBinding.ViewModels;
using Foundation;
using Intersoft.Crosslight;
using Intersoft.Crosslight.iOS;
namespace CrosslightDataBinding.iOS
{
    [Register("ListViewController")]
    [ImportBinding(typeof(SimpleListBindingProvider))]
    public class ListViewController : UITableViewController<SimpleListViewModel>
    {
        public override ImageSettings CellImageSettings
        {
            get
            {
                return new ImageSettings
                {
                    ImageSize = new SizeF(36, 36)
                };
            }
        }

        public override TableViewCellStyle CellStyle
        {
            get { return TableViewCellStyle.Subtitle; }
        }
    }
}

We simply set the image size to be used, and the presentation style by specifying the CellStyle.

Next, simply run the iOS project. You should get the following result.

Image Added

Running on Android

On Android, open up main.axml located under CrosslightDataBinding.Android/Resources/layout folder and replace everything there with the following code.

Code Block
languagexml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:padding="10dp">
    <!-- Binding Text -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Binding Text"
                android:textSize="24sp"
                android:fontFamily="sans-serif-condensed" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Bind to Label" />
                <TextView
                    android:id="@+id/LblBindText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="end|center"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end"
                    android:text="BoundText" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Bind to Text Field"
                    android:layout_gravity="center" />
                <EditText
                    android:id="@+id/TxtBindText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="BoundText"
                    android:gravity="end|center"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end" />
            </LinearLayout>
        </LinearLayout>
    <!-- Binding Numeric Values -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Binding Numeric Values"
                    android:textSize="24sp"
                    android:fontFamily="sans-serif-condensed"
                    android:layout_weight="1" />
                <TextView
                    android:id="@+id/LblBindNumeric"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="3"
                    android:gravity="end|center"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end|center"
                    android:text="30" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Bind to Slider" />
                <SeekBar
                    android:id="@+id/SliderBindNumeric"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:max="100" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Bind to Numeric Text Field"
                    android:layout_gravity="center" />
                <EditText
                    android:id="@+id/TxtBindNumeric"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="30"
                    android:inputType="number"
                    android:gravity="end|center"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end" />
            </LinearLayout>
        </LinearLayout>
    <!-- Binding Boolean Values -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Binding Boolean Values"
                    android:textSize="24sp"
                    android:layout_weight="1"
                    android:fontFamily="sans-serif-condensed" />
                <TextView
                    android:id="@+id/LblBindBool"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="5"
                    android:gravity="end|center"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end|center"
                    android:text="true" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Bind to Switch" />
                <Switch
                    android:id="@+id/SwitchBindBool"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1" />
            </LinearLayout>
        </LinearLayout>
    <!-- Binding DateTime Values -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Binding DateTime"
                    android:textSize="24sp"
                    android:layout_weight="1"
                    android:fontFamily="sans-serif-condensed" />
                <TextView
                    android:id="@+id/LblBindDate"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textAlignment="viewEnd"
                    android:layout_gravity="end|center"
                    android:text="DateTime" />
            </LinearLayout>
            <DatePicker
                android:id="@+id/DateBindDate"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    <!-- Binding Image -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Binding Image"
                    android:textSize="24sp"
                    android:layout_weight="1"
                    android:fontFamily="sans-serif-condensed" />
                <Button
                    android:id="@+id/BtnBindImage"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="3"
                    android:text="Select" />
            </LinearLayout>
            <ImageView
                android:id="@+id/ImgBindImage"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:scaleType="centerCrop" />
        </LinearLayout>
    <!-- Binding to List -->
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Binding to List"
                    android:textSize="24sp"
                    android:layout_weight="1"
                    android:fontFamily="sans-serif-condensed" />
                <Button
                    android:id="@+id/BtnBindList"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="3"
                    android:text="Open" />
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</ScrollView>

The above layout contains the finished layout for Android so you can run the sample without any problems. Similar to iOS, let's prepare the Fragment that will display our list of items. Create a new Crosslight RecyclerViewFragment file, name it ListFragment, and place it under CrosslightDataBinding.Android/Fragments folder. Use the following code.

Code Block
languagec#
 using CrosslightDataBinding.Core;
using CrosslightDataBinding.ViewModels;
using Android.Runtime;
using Intersoft.Crosslight;
using Intersoft.Crosslight.Android;
using Intersoft.Crosslight.Android.v7;
using Intersoft.Crosslight.Android.v7.ComponentModels;
using System;
namespace CrosslightDataBinding.Android
{
    [ImportBinding(typeof(SimpleListBindingProvider))]
    public class ListFragment : RecyclerViewFragment<SimpleListViewModel>
    {
        public ListFragment()
        {
        }

        public ListFragment(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }

        protected override void Initialize()
        {
            base.Initialize();
            this.CellStyle = CellStyle.SubTitle;
        }
    }
}

Similar to iOS, all we did is just specify the CellStyle to be used. Run the project and you should get the following result.

Image Added

Conclusion

Congratulations! You've just finished learning how to bind basic data types with Crosslight. To learn more about advanced binding features, please check out MVVM Samples and run it on the simulator.

Sample

You can also find the resulting sample here: the_sample CrosslightDataBinding.zip. Simply open this sample with Xamarin Studio or Visual Studio, restore the NuGet packages and run the project.

 

Related Topics

Content by Label
spacescrosslight
reversetrue
showLabelsfalse
max5
sortmodified
labelsstarter-walkthroughs -walkthrough-getting-started-with-mvvm-data-binding
showSpacefalse
typepage