Databinding between two custom UserControls - c#

I have two custom NumberUpDown user controls that need to interact with each other. The maximum value of the one on the left needs to be the minimum of the one on the right. To accomplish this I put them in another user control which contains both and has all the required data members.
The xaml the container user control looks like
<controls:NumberBox x:Name="LeftNumberBox" HorizontalAlignment="Right" Width="50" Min="{Binding Min}" Current="{Binding Left}" Max="{Binding Right}"/>
<controls:NumberBox x:Name="RightNumberBox" Grid.Column="2" HorizontalAlignment="Left" Width="50" Min="{Binding Left}" Current="{Binding Right}" Max="{Binding Max}"/>
and its cs file looks like
public ThresholdBox()
{
InitializeComponent();
}
public int Min
{
get
{
return (int)GetValue(MinIntProperty);
}
set
{
SetValue(MinIntProperty, value);
}
}
public static readonly DependencyProperty MinIntProperty =
DependencyProperty.Register("Min", typeof(int), typeof(ThresholdBox), new PropertyMetadata(0));
public int Max
{
get
{
return (int)GetValue(MaxIntProperty);
}
set
{
SetValue(MaxIntProperty, value);
}
}
public static readonly DependencyProperty MaxIntProperty =
DependencyProperty.Register("Max", typeof(int), typeof(ThresholdBox), new PropertyMetadata(100));
public int Left
{
get
{
return LeftNumberBox.Current;
}
set
{
LeftNumberBox.Current = value;
}
}
public int Right
{
get
{
if(RightNumberBox != null)
{
return RightNumberBox.Current;
}
else
{
return 10;
}
}
set
{
RightNumberBox.Current = value;
}
}
and the NumberBox aka NumberUpDown user control cs file looks like
public NumberBox()
{
InitializeComponent();
NUDTextBox.Text = Current.ToString();
Min = 0;
Current = 10;
Max = 100;
}
public int Min
{
get
{
return (int)GetValue(MinIntProperty);
}
set
{
SetValue(MinIntProperty, value);
}
}
public static readonly DependencyProperty MinIntProperty =
DependencyProperty.Register("Min", typeof(int), typeof(NumberBox), new PropertyMetadata(0));
public int Current
{
get
{
return (int)GetValue(CurrentIntProperty);
}
set
{
SetValue(CurrentIntProperty, value);
}
}
public static readonly DependencyProperty CurrentIntProperty =
DependencyProperty.Register("Current", typeof(int), typeof(NumberBox), new PropertyMetadata(10));
public int Max
{
get
{
return (int)GetValue(MaxIntProperty);
}
set
{
SetValue(MaxIntProperty, value);
}
}
public static readonly DependencyProperty MaxIntProperty =
DependencyProperty.Register("Max", typeof(int), typeof(NumberBox), new PropertyMetadata(100));
I didn't include the button logic because it works as expected
The container is called with
<controls:ThresholdBox Left="10" Right="90" Min="0" Max="100" Padding="0,20,0,20"/>
Unfortunately the code doesn't work as expected. Both NumberBoxes start at 10, the left one will go down to 0 and up to 10, while the right one goes up to 100 and no lower than 10, even if I lower the left NumberBoxes value to 0 in the UI.
I thought this might be a two way binding problem but that causes as StackOverFlow Exception. What am I missing in my binding code to make both NumberBoxes use/modify the container's Left and Right properties respectively?

If "The maximum value of the one on the left needs to be the minimum of the one on the right", why don't those two properties bind to the same value?
Add a new property to your ViewModel:
public int Intermediate { get; set; }
Then bind left Max and right Min to this property.
<controls:NumberBox x:Name="LeftNumberBox" HorizontalAlignment="Right" Width="50" Min="{Binding Min}" Current="{Binding Left}" Max="{Binding Intermediate}"/>
<controls:NumberBox x:Name="RightNumberBox" Grid.Column="2" HorizontalAlignment="Left" Width="50" Min="{Binding Intermediate}" Current="{Binding Right}" Max="{Binding Max}"/>

Related

Can I combine a Xaml <Label> into a C# template that I use to create a Frame?

I have XAML code that I use to set up a frame and frame heading like this:
<Label Text="ABC" />
<t:ContentFrame>
<Label Text="ABC" />
</t:ContentFrame>
For ContentFrame I use a C# template here:
[Xamarin.Forms.ContentProperty("Contents")]
public class ContentFrame : CustomFrame
{
StackLayout contentStack { get; } = new StackLayout()
{
Spacing = 0,
Padding = new Thickness(0),
Orientation = StackOrientation.Vertical
};
public IList<View> Contents { get => contentStack.Children; }
public ContentFrame()
{
Content = contentStack;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "ContentFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "ContentFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "ContentFrameCornerRadius");
}
}
Is there a way that I could combine the setting of the heading into the ContentFrame class so the same thing could be achieved with:
<t:ContentFrame Heading="ABC">
<Label Text="ABC" />
</t:ContentFrame>
You need a Bindable property for this:
public class ContentFrame : CustomFrame
{
StackLayout contentStack { get; } = new StackLayout()
{
Spacing = 0,
Padding = new Thickness(0),
Orientation = StackOrientation.Vertical
};
public IList<View> Contents { get => contentStack.Children; }
public static readonly BindableProperty HeadingProperty =
BindableProperty.Create(nameof(Heading), typeof(string),
typeof(ContentFrame), null, propertyChanged: OnHeadingChanged);
public string Heading
{
get => (Heading)GetValue(HeadingProperty);
set => SetValue(HeadingProperty, value);
}
static void OnHeadingChanged(BindableObject bindable, object oldValue, object newValue) {
//whatever you want to handle
}
public ContentFrame()
{
Content = contentStack;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "ContentFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "ContentFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "ContentFrameCornerRadius");
}
}
If you don't require InotifypropertyChanged you can remove OnHeadingChanged (also from inside BindableProperty.Create(), if require you can add the below declaration.

How to recycle / reuse list of children views without having to make it recalculate size for better performance? Xamarin Forms

I am currently working with a Stacklayout that has list support. I load the data for this list on another thread, then add it to this view on the main thread. This works fine functionality wise, but I am having a slight delay / performance issue when the items are being loaded to the control. So after the call to the API has been made, and the assignment to the main thread is happening. I load around 5 items to ensure that i do not crowd it since it contains images (that are small in size + i use FFImageLoading).
What i have done now is to cache all of the views and then reuse it when possible. Works fine, but does not seem to improve the performance a lot. What im thinking of is to override LayoutChildren because it appears that the children that is added to this stacklayout, is recalculating it's size even though I reuse the View. What might i be doing wrong?
Below is my custom control:
public class CustomStackLayout : StackLayout
{
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CustomStackLayout),
default(DataTemplate), BindingMode.OneWay);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set
{
SetValue(ItemTemplateProperty, value);
OnPropertyChanged(nameof(ItemTemplate));
}
}
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(CustomStackLayout), default(IEnumerable<object>), BindingMode.TwoWay, propertyChanged: ItemsSourceChanged);
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set
{
SetValue(ItemsSourceProperty, value);
OnPropertyChanged(nameof(ItemsSource));
}
}
private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var items = (CustomStackLayout)bindable;
items.Children.Clear();
if (items.ItemsSource != null)
{
foreach (var item in items.ItemsSource)
{
items.Children.Add(items.GetRowView(item));
}
}
}
protected virtual View GetRowView(object item)
{
// See if cached
var model = item as CustomModel;
if (AllViews.Any(x => x.Message.ObjectID.Equals(model.ObjectID)))
{
return AllViews.Where(x => x.Message.ObjectID.Equals(model.ObjectID)).FirstOrDefault().View;
}
object viewContent;
// Determine if this is a straight up template or using a AllViewselector
if (ItemTemplate is DataTemplateSelector dts)
{
var template = dts.SelectTemplate(item, this);
viewContent = template.CreateContent();
}
else
{
viewContent = ItemTemplate.CreateContent();
}
// Cache views
this.AllViews.Add(new CachedView() { Message = msg, View = viewContent is View ? viewContent as View : ((ViewCell)viewContent).View });
// Bind
var rowView = viewContent is View ? viewContent as View : ((ViewCell)viewContent).View;
rowView.BindingContext = item;
return rowView;
}
public List<CachedView> AllViews = new List<CachedView>();
}
public class CachedView
{
public Type Type { get; set; }
public View View { get; set; }
public Message Message { get; set; }
}
This is how i use it in xaml with a DataTemplateSelector that picks between different views depending on the data:
<controls:CustomStackLayout ItemsSource="{Binding Data}">
<controls:CustomStackLayout.ItemTemplate>
<selector:MyDataTemplateSelector />
</controls:CustomStackLayout.ItemTemplate>
</controls:CustomStackLayout>

Properties of custom control can only be filled by StaticResource but not with values defined in XAML

I've created my own ExpandableView based on this https://www.clearpeople.com/insights/blog/2019/February/how-to-create-a-contentview-with-expandable-functionality
but as all C# code.
My control looks like this (without the animation part)
public class ExpandableView : ContentView
{
public static readonly BindableProperty ExpandableContentProperty = BindableProperty.Create(nameof(ExpandableContent), typeof(View), typeof(ExpandableView));
public static readonly BindableProperty TitleTextProperty = BindableProperty.Create(nameof(TitleText), typeof(string), typeof(ExpandableView));
public View ExpandableContent
{
get => this._content;
set
{
if (this._content == value)
{
return;
}
OnPropertyChanging();
if (this._content != null)
{
this._ContentLayout.Children.Remove(this._content);
}
this._content = value;
this._ContentLayout.Children.Add(this._content);
OnPropertyChanged();
}
}
public string TitleText
{
get => this._Title.Text;
set
{
if (this._Title.Text == value)
{
return;
}
OnPropertyChanging();
this._Title.Text = value;
OnPropertyChanged();
}
}
private readonly StackLayout _OuterLayout;
private readonly StackLayout _ContentLayout;
private readonly StackLayout _TitleLayout;
private View _content;
private readonly Label _Title;
public ExpandableView()
{
this._OuterLayout = new StackLayout();
this._ContentLayout = new StackLayout();
this._TitleLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
};
this._Title = new Label
{
HorizontalOptions = new LayoutOptions(LayoutAlignment.Start, true),
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
Text = "Title",
};
this._Title.FontSize = Device.GetNamedSize(NamedSize.Medium, this._Title);
this._TitleLayout.Children.Add(this._Title);
this._OuterLayout.Children.Add(this._TitleLayout);
this._OuterLayout.Children.Add(this._ContentLayout);
Content = this._OuterLayout;
}
}
But now, when I try to use it in XAML as I normally would:
<controls:ExpandableView TitleText="Equipment">
<controls:ExpandableView.ExpandableContent>
<StackLayout>
<Label Text="EQ_12345" />
<Button Command="{Binding ShowDatacommand}" />
</StackLayout>
</controls:ExpandableView.ExpandableContent>
</controls:ExpandableView>
Setting the properties to some values results in the title still showing me "Title" and no content being shown. If I instead put everything into the StaticResource Everything works just fine:
<controls:ExpandableView ExpandableContent="{StaticResource ExpendableViewContent}"
TitleText="{StaticResource EquiString}" />
While testing, I set some breakpoints within the properties, and only when I used {StaticResource} the properties were set. All values defined directly in XAML were never passed to the properties. What am I doing wrong here?
When defining your own BindableProperty properties, the definitive source for the values is expected to be accessed via BindableObject.SetValue/BindableObject.GetValue. The Xamarin runtime can directly use that rather than going through your get/set methods.
Using TitleText as an example, the implementation should be something like:
public string TitleText
{
get => (string)GetValue(TitleTextProperty);
set
{
SetValue(TitleTextProperty, value);
}
}
The linked article does do this.
In order to create the link between the property and the displayed title, establish data binding in the constructor to link the Text of the title label to the TitleText property in the ExpandableView constructor:
_Title.SetBinding(Label.TextProperty, new Binding(nameof(TitleText)) { Source = this });

Binding Radio Button IsChecked to object's current array of element's state

I am developing a small utility using C#/WPF/MVVM which would allow to set the input state of a controller we are using for testing. The communication between the app I am developing and the hardware/our web service communication to the hardware is only one way, meaning that the app will only be able to set the state of the inputs, but not get the states.
Another point to mention is that some types are already defined for this in some other parts of our solution, which are all in F#. To do my app, I am currently using C#. So I did a Unit class to wrap around the LocalControllerTypes.LocalController type defined in F#, containing a lot of needed information.
In order to do that, I have an enum enumerating the InputState possible (currently there is Active or Normal, but that list could potentially grow with time). Also, the number of inputs present on each unit type is different (some have 2, some have 4, some have more), so I have an ItemControl binded on the selected unit's array of Inputs, which unfortunately only contains the Name of the input which I have to display. The unit has 2 other properties related to the inputs it has, InputWriters, which is an array of a type which is used to send the command to the hardware/web service communicating with that hardware, and InputStates, which is an array of InputState for each input it has, as last set in the app (since we can't get the state from the hardware).
Now I would like to bind the IsChecked property of the radio buttons (which is what I define as ItemTemplate of the ItemsControl) to the InputState of the currently SelectedUnit (in my ViewModel). The problem I am having, is that I would somehow need to know the radio button is for which index of the SelectedUnit's Inputs array, in order to get the item at the same index for the SelectedUnit's InputStates property.
Is there any way to achieve this?
MainWindow.xaml:
...
<ItemsControl Grid.Row="1" ItemsSource="{Binding SelectedUnit.LocalControllerInfo.Inputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10" FontSize="15" Style="{StaticResource TextBlockNormalBase}" Text="{Binding InputName}"/>
<StackPanel Orientation="Horizontal">
<RadioButton Margin="10" Foreground="White" Content="Normal"
IsChecked="{Binding Path=?,
Converter={StaticResource inputToBoolConverter},
ConverterParameter=?}"/>
<RadioButton Margin="10" Foreground="White" Content="Active"
IsChecked="{Binding Path=?,
Converter={StaticResource inputToBoolConverter},
ConverterParameter=?}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
Unit.cs:
public class Unit : BindableObject
{
public enum InputState
{
Normal,
Active
}
private LocalControllerTypes.LocalController _localControllerInfo;
private LocalControllerTypes.ArduinoInjector[] _arduinoInjector;
private WebWriter.WebWriter[] _inputWriters;
private SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection _networkSwitchConnection;
private InputState[] _inputStates;
private bool _isUnitConnected;
public Unit(LocalControllerTypes.LocalController localControllerInfo,
LocalControllerTypes.ArduinoInjector[] arduinoInjector,
WebWriter.WebWriter[] inputWriters,
SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection networkSwitchConnection)
{
_localControllerInfo = localControllerInfo;
_arduinoInjector = arduinoInjector;
_inputWriters = inputWriters;
_networkSwitchConnection = networkSwitchConnection;
// This assumption might not always be true, but there is no way for now to get the input state
_inputStates = Enumerable.Repeat(InputState.Normal, _inputWriters.Length).ToArray();
// This assumption might not always be true, but there is no way for now to get the connection state
_isUnitConnected = true;
}
public LocalControllerTypes.LocalController LocalControllerInfo
{
get
{
return _localControllerInfo;
}
set
{
if (_localControllerInfo != value)
{
_localControllerInfo = value;
RaisePropertyChanged();
}
}
}
public LocalControllerTypes.ArduinoInjector[] ArduinoInjectors
{
get
{
return _arduinoInjector;
}
set
{
if (_arduinoInjector != value)
{
_arduinoInjector = value;
RaisePropertyChanged();
}
}
}
public WebWriter.WebWriter[] InputWriters
{
get
{
return _inputWriters;
}
set
{
if (_inputWriters != value)
{
_inputWriters = value;
RaisePropertyChanged();
}
}
}
public SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection NetworkSwitchConnection
{
get
{
return _networkSwitchConnection;
}
set
{
if (_networkSwitchConnection != value)
{
_networkSwitchConnection = value;
RaisePropertyChanged();
}
}
}
public InputState[] InputStates
{
get
{
return _inputStates;
}
set
{
if (_inputStates != value)
{
_inputStates = value;
RaisePropertyChanged();
}
}
}
public bool IsUnitConnected
{
get
{
return _isUnitConnected;
}
set
{
if (_isUnitConnected != value)
{
_isUnitConnected = value;
RaisePropertyChanged();
}
}
}
}
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
private Unit _selectedUnit;
private ObservableCollection<Unit> _units;
private string _reader1RawCardData;
private string _reader2RawCardData;
private int _reader1BitsCount;
private int _reader2BitsCount;
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel(IUnitStore unitStore)
{
UnitStore = unitStore;
// We could use directly the unitstore instead of creating another container and binding on that, but
// not doing so will allow us to add unit filtering further down the road
_units = new ObservableCollection<Unit>(unitStore.Units);
_selectedUnit = _units.First();
_reader1RawCardData = "";
_reader2RawCardData = "";
_reader1BitsCount = 0;
_reader2BitsCount = 0;
}
protected void RaisePropertyChanged([CallerMemberName]string propertName = "")
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertName));
}
}
protected void RefreshUnitStore(object obj)
{
UnitStore.UpdateStore();
Units = new ObservableCollection<Unit>(UnitStore.Units);
SelectedUnit = Units.First();
}
protected void SendReaderCardSwipe(object obj)
{
int unitReaderNumber = (int)obj;
IPAddress arduinoIp = SelectedUnit.LocalControllerInfo.Readers[unitReaderNumber - 1].InjectorIp;
int injectorNumber = SelectedUnit.LocalControllerInfo.Readers[unitReaderNumber - 1].InjectorNumber;
string serviceUrl = SelectedUnit.ArduinoInjectors.Where(injector => injector.Ip.Equals(arduinoIp)).First().Url;
InjectorInterface.CardSwipe<IPAddress>(serviceUrl, arduinoIp, injectorNumber, Reader1BitsCount, Reader1RawCardData);
}
protected void UpdateSelectedUnitConnectionState(object obj)
{
((INetworkConnection.INetworkConnection)SelectedUnit.NetworkSwitchConnection).SetConnection(SelectedUnit.IsUnitConnected);
}
public IUnitStore UnitStore
{
get;
private set;
}
public Unit SelectedUnit
{
get
{
return _selectedUnit;
}
set
{
if (_selectedUnit != value)
{
_selectedUnit = value;
RaisePropertyChanged();
}
}
}
public ObservableCollection<Unit> Units
{
get
{
return _units;
}
set
{
if (_units != value)
{
_units = value;
RaisePropertyChanged();
}
}
}
public string Reader1RawCardData
{
get
{
return _reader1RawCardData;
}
set
{
if (_reader1RawCardData != value)
{
_reader1RawCardData = value;
RaisePropertyChanged();
}
}
}
public string Reader2RawCardData
{
get
{
return _reader2RawCardData;
}
set
{
if (_reader2RawCardData != value)
{
_reader2RawCardData = value;
RaisePropertyChanged();
}
}
}
public int Reader1BitsCount
{
get
{
return _reader1BitsCount;
}
set
{
if (_reader1BitsCount != value)
{
_reader1BitsCount = value;
RaisePropertyChanged();
}
}
}
public int Reader2BitsCount
{
get
{
return _reader2BitsCount;
}
set
{
if (_reader2BitsCount != value)
{
_reader2BitsCount = value;
RaisePropertyChanged();
}
}
}
public ICommand RefreshSourceCommand
{
get
{
return new RelayCommand(RefreshUnitStore);
}
}
public ICommand SendReaderCardSwipeCommand
{
get
{
return new RelayCommand(SendReaderCardSwipe);
}
}
public ICommand UpdateSelectedUnitConnectionStateCommand
{
get
{
return new RelayCommand(UpdateSelectedUnitConnectionState);
}
}
}
Your ItemsControl is bound to SelectedUnit.LocalControllerInfo.Inputs. What is the type of .Inputs?
As written your bindings will not have access to InputState or InputName. That's not really in the scope of "how to identify what array item goes with what enum"
To address your original issue, one possibility would be to nest some tuples and bind to that, a la
List<Tuple<int,State>> States = new List<Tuple<int,State>>();
States.Add(new Tuple<int, State>(1,State.Bar));
States.Add(new Tuple<int, State>(2, State.Foo));
States.Add(new Tuple<int, State>(3, State.Bar));

Create a ListView With 3 Columns Xamarin Forms

I want to create a customListView in Xamarin Forms that shows 3 columns and N rows (I will get the source from a web service). Each cell has an icon and a description. I have found several examples but no one uses columns.
Depending on what you want, you can create a ListView then have a Grid for each Item, where you define the 3 columns. This will allow you to keep the extra features of the ListView like PullToRefresh.
If you want an automatic GridView type control, I did build one off ChaseFlorell's example as shown in this forum post: https://forums.xamarin.com/discussion/61925/how-to-make-the-dynamic-grid-view-and-make-it-clickable
This way it makes it data bindable, rather than having to explicitly define each one.
First is the Grid Control
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Mobile.Controls.GridView">
</Grid>
public partial class GridView : Grid
{
public GridView()
{
InitializeComponent();
for (var i = 0; i < MaxColumns; i++)
ColumnDefinitions.Add(new ColumnDefinition());
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create<GridView, object>(p => p.CommandParameter, null);
public static readonly BindableProperty CommandProperty = BindableProperty.Create<GridView, ICommand>(p => p.Command, null);
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<GridView, IEnumerable<object>>(p => p.ItemsSource, null, BindingMode.OneWay, null, (bindable, oldValue, newValue) => { ((GridView)bindable).BuildTiles(newValue); });
private int _maxColumns = 2;
private float _tileHeight = 0;
public Type ItemTemplate { get; set; } = typeof(DocumentTypeTemplate);
public int MaxColumns
{
get { return _maxColumns; }
set { _maxColumns = value; }
}
public float TileHeight
{
get { return _tileHeight; }
set { _tileHeight = value; }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public void BuildTiles(IEnumerable<object> tiles)
{
try
{
if (tiles == null || tiles.Count() == 0)
Children?.Clear();
// Wipe out the previous row definitions if they're there.
RowDefinitions?.Clear();
var enumerable = tiles as IList ?? tiles.ToList();
var numberOfRows = Math.Ceiling(enumerable.Count / (float)MaxColumns);
for (var i = 0; i < numberOfRows; i++)
RowDefinitions?.Add(new RowDefinition { Height = TileHeight });
for (var index = 0; index < enumerable.Count; index++)
{
var column = index % MaxColumns;
var row = (int)Math.Floor(index / (float)MaxColumns);
var tile = BuildTile(enumerable[index]);
Children?.Add(tile, column, row);
}
}
catch { // can throw exceptions if binding upon disposal
}
}
private Layout BuildTile(object item1)
{
var buildTile = (Layout)Activator.CreateInstance(ItemTemplate, item1);
buildTile.InputTransparent = false;
var tapGestureRecognizer = new TapGestureRecognizer
{
Command = Command,
CommandParameter = item1,
NumberOfTapsRequired = 1
};
buildTile?.GestureRecognizers.Add(tapGestureRecognizer);
return buildTile;
}
}
Then define a template
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Mobile.TypeTemplate">
<Label Text="{Binding Name}" />
</Grid>
public partial class TypeTemplate : Grid
{
public TypeTemplate()
{
InitializeComponent();
}
public TypeTemplate(object item)
{
InitializeComponent();
BindingContext = item;
}
}
Then use the control
<control:GridView HorizontalOptions="FillAndExpand"
Grid.Row="1"
VerticalOptions="FillAndExpand"
RowSpacing="20"
ColumnSpacing="20"
MaxColumns="2"
ItemsSource="{Binding ListOfData}"
CommandParameter="{Binding}"
Command="{Binding ClickCommand}"
IsClippedToBounds="False">
<control:GridView.TileHeight>
<OnPlatform x:TypeArguments="x:Single"
iOS="60"
Android="60"
WinPhone="90" />
</control:GridView.TileHeight>
</control:GridView>
A much easier solution than the proposed is to use the DevExpress Grid control. It has support for headers, row virtualization, pull-to-refresh, load on demand and a bunch of other useful features.

Categories