Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 93 additions & 14 deletions XFBindableStackLayout/Controls/BindableStackLayout.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,134 @@
using System.Collections;
using System;
using System.Collections;
using System.Windows.Input;
using Xamarin.Forms;

namespace XFBindableStackLayout
{
public class BindableStackLayout : StackLayout
{
readonly Label header;

public BindableStackLayout()
{
header = new Label();
Children.Add(header);

ItemSelectedCommand = new Command<object>(item =>
{
SelectedItem = item;
});
}

public event EventHandler<SelectedItemChangedEventArgs> SelectedItemChanged;

public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(BindableStackLayout),
propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems()
);

public DataTemplate ItemDataTemplate
{
get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
set { SetValue(ItemDataTemplateProperty, value); }
}
public static readonly BindableProperty ItemDataTemplateProperty =
BindableProperty.Create(nameof(ItemDataTemplate), typeof(DataTemplate), typeof(BindableStackLayout));
public static readonly BindableProperty ItemDataTemplateProperty = BindableProperty.Create(
nameof(ItemDataTemplate), typeof(DataTemplate), typeof(BindableStackLayout)
);

public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly BindableProperty TitleProperty =
BindableProperty.Create(nameof(Title), typeof(string), typeof(BindableStackLayout),
propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateHeader());
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title), typeof(string),
typeof(BindableStackLayout),
propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateHeader()
);

public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}

public static BindableProperty SelectedItemProperty = BindableProperty.Create(
propertyName: "SelectedItem",
returnType: typeof(object),
declaringType: typeof(BindableStackLayout),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindingMode should be set to BindingMode.TwoWay.

propertyChanged: OnSelectedItemChanged
);

private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to keep the code style consistent the access modifier private should be removed as it is a default access modifier.

{
var itemsView = (BindableStackLayout)bindable;
if (newValue == oldValue)
return;

itemsView.SetSelectedItem(newValue);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method SetSelectedItem(..) can be replaced with a single line of code SelectedItemChanged?.Invoke(...)

}

protected virtual void SetSelectedItem(object selectedItem)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why protected and virtual? In my opinion should be just private.

P.S.: In order to keep the code style consistent the access modifier private should be removed as it is a default access modifier.

{
var handler = SelectedItemChanged;
if (handler != null)
handler(this, new SelectedItemChangedEventArgs(selectedItem));
}

void PopulateItems()
{
if (ItemsSource == null) return;
foreach (var item in ItemsSource)
{
var itemTemplate = ItemDataTemplate.CreateContent() as View;
itemTemplate.BindingContext = item;
Children.Add(itemTemplate);
Children.Add(GetItemView(item));
}
}

void PopulateHeader() => header.Text = Title;

protected virtual View GetItemView(object item)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why protected and virtual? In my opinion should be just private.

P.S.: In order to keep the code style consistent the access modifier private should be removed as it is a default access modifier.

{
var content = ItemDataTemplate.CreateContent();

var view = content as View;
if (view == null)
return null;

view.BindingContext = item;

var gesture = new TapGestureRecognizer
{
Command = ItemSelectedCommand,
CommandParameter = item
};

AddGesture(view, gesture);

return view;
}

protected readonly ICommand ItemSelectedCommand;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why protected? In my opinion should be private and since it is a field it should be declared before constructor.

P.S.: In order to keep the code style consistent the access modifier private should be removed as it is a default access modifier.


protected void AddGesture(View view, TapGestureRecognizer gesture)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why protected? In my opinion should be private.
  2. There are different types of Gestures, it will make sense to rename the method to AddTapGestureRecognize.

P.S.: In order to keep the code style consistent the access modifier private should be removed as it is a default access modifier.

{
view.GestureRecognizers.Add(gesture);

var layout = view as Layout<View>;

if (layout == null)
return;

foreach (var child in layout.Children)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to add a TapGestureRecognizer to sub views?

AddGesture(child, gesture);
}
}
}
}
5 changes: 3 additions & 2 deletions XFBindableStackLayout/Views/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<local:BindableStackLayout
<local:BindableStackLayout
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, it will make sense to add a sample of binding to SelectedItem.
It will require to implement a INotifyPropertyChanged on the VM level and add public property with getter and setter.

Title="Colors:"
ItemsSource="{Binding MyColors}"
VerticalOptions="Center"
HorizontalOptions="Center">
HorizontalOptions="Center"
SelectedItemChanged="OnSelectedItemChanged">
<local:BindableStackLayout.ItemDataTemplate>
<DataTemplate>
<StackLayout
Expand Down
7 changes: 7 additions & 0 deletions XFBindableStackLayout/Views/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@ public MainPage()
{
InitializeComponent();
}

public void OnSelectedItemChanged(object sender, SelectedItemChangedEventArgs e)
{
var selectedItem = (MyColor)e.SelectedItem;
System.Diagnostics.Debug.WriteLine(selectedItem.Name);

}
}
}