Xamarin.Forms: Create instance of a DataTemplate - xamarin

I wan't to create a ContentView with a BindableProperty of type DataTemplate, so that when I use my custom ContentView i can customize how the elements should look like.
But I wan't to arrange and create the contents in code, how can I create an instance from a DataTemplate?
For example, in my custom view, I have a collection of objects, now for each object I want to create a view based on the set data template and set the binding context of that created view to that object.

I worked it out in following way.
I use my custom ContentView in following way:
<controls:MyCustomView Items="{Binding SampleItems}">
<controls:MyCustomView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SampleProperty}" />
</DataTemplate>
</controls:MyCustomView.ItemTemplate>
</controls:MyCustomView>
then in the code behind of the MyCustomView I declare a ItemTemplate bindable property:
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
nameof(ItemTemplate),
typeof(DataTemplate),
typeof(MyCustomView),
propertyChanged: (bObj, oldValue, newValue) =>
{
var view = bObj as MyCustomView;
if (view != null)
view.SampleMethodToArrangeItems();
}
);
now let's say that in the SampleMethodToArrangeItems method I want to create and arrange, the items created from the provided data template:
foreach (var item in Items)
{
var itemView = ItemTemplate.CreateContent() as View;
if (itemView != null)
{
itemView.BindingContext = item;
// Do something with the create view e.g. add it to Grid.Children
}
}

Related

Xamarin Forms ListView cancel ItemSelected event

With the Xamarin Forms ListView, the ItemSelected event is fired each time an element is selected in the list.
Is there a way to cancel out of this event so that the new item isn't selected and the old item remains selected?
The use case is a master/detail type of view where selecting an item in the list changes the detail. But the detail view from the previous selection might have been altered and the user needs to decide to discard or save the previous changes before changing the List's SelectedItem.
#SushiHangover's suggestion to control the SelectionMode property and disable/enable the selection of the ListView is a good one. However, I have an alternate solution that will revert the ListView's selected item to the previous item for anyone who might have a similar need.
I will only post snippets of the solution, but they should be complete enough for someone else to learn and implement.
First, I am using FreshMVVM which provides (amongst many things), essentially, syntactic sugar over binding the View to the ViewModel. Also, the PropertyChanged nuget package creates the INotifyPropertyChanged boilerplate code at compile time. That is why you don't see the familiar XF patterns you normally see with that interface. AddINotifyPropertyChanged handles all that.
The solution to my problem is a dedicated, generic ListViewModel that can be bound to any ListView that needs the ability "roll back" a selection changed event. It binds to the Items collection. Additionally the SelectedItem property is bound to the control as well.
The constructor takes a Func which is called to determine if it's ok to move the selection or not.
[AddINotifyPropertyChangedInterface]
public class ListViewModel<T>
{
private Func<bool> _beforeChangeValidator;
private Action _afterChange;
public ListViewModel(Func<bool> beforeChangeValidator, Action afterChange)
{
_beforeChangeValidator = beforeChangeValidator;
_afterChange = afterChange;
_changing = false;
}
public int SelectedIndex { get; set; }
public T SelectedItem { get; set; }
public ObservableCollection<T> Items { get; set; }
private bool _changing;
public Command SelectedItemChanged
{
get
{
return new Command((args) =>
{
if (!_changing)
{
if (_beforeChangeValidator())
{
SelectedIndex = ((SelectedItemChangedEventArgs)args).SelectedItemIndex;
}
}
_changing = false;
});
}
}
public void RevertSelectedItemChanged()
{
_changing = true;
SelectedItem = Items[SelectedIndex];
}
}
And the code in the parent ViewModel has the Func (TagListBeforeChange) that determines if it's ok to move the selection or not. In this case I am checking if the last selected item has been changed, and if it has, prompt the user for what to do.
public override void Init()
{
TagListViewModel = new ListViewModel<Tag>(TagListBeforeChange, null);
}
private bool TagListBeforeChange()
{
if (ActiveTag.HasChanged)
{
var confirmConfig = new ConfirmConfig()
{
Message = "Current tag has changed. Discard changes and continue?",
OkText = "Discard Changes",
CancelText = "Cancel",
OnAction = (result) =>
{
if (result)
{
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
}
else
{
TagListViewModel.RevertSelectedItemChanged();
}
}
};
_userDialogs.Confirm(confirmConfig);
return false;
}
_mapper.Map(TagListViewModel.SelectedItem, ActiveTag);
return true;
}
And finally, here is the ListView control declaration...
<ListView ItemsSource="{Binding TagListViewModel.Items}"
SelectedItem="{Binding TagListViewModel.SelectedItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction Command="{Binding TagListViewModel.SelectedItemChanged}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="8">
<Label Text="{Binding DisplayValue}" />
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Xamarin Forms - access databound object in ViewCell

I am creating a ListView whose items I want to directly control with code. I have the following xaml that declares a generic ListView.
<?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:ViewCellClick"
x:Class="ViewCellClick.MainPage">
<ListView x:Name="___listview" HasUnevenRows="True">
<ListView.ItemTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and then in the code behind, I set up the ItemsSource property.
namespace ViewCellClick
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
___listview.ItemsSource = Repository.GetList();
___listview.ItemTemplate = new DataTemplate(typeof(CustomViewCell));
}
}
and this leverages a custom template defined as...
public class CustomViewCell : ViewCell
{
public CustomViewCell()
{
var stack = new StackLayout();
stack.Children.Add(new Label() { Text = "Label" });
// HOW DO I ACCESS THE MODEL HERE SO I CAN DRAW CUSTOM UI DEPENDING ON THE MODEL INSTANCE?
View = stack;
}
}
}
at this level, I have a very generic custom view cell as it only creates a label. However, say the model is complicated, and depending on the data in the model I would draw a different UI for that ViewCell instance.
How do I get access to the model for each CustomViewCell and then draw the UI according to that model?
I tried BindingContext, but it's null in the CustomViewCell constructor.
The BindingContext of thew ViewCell will have a reference to the current item in the list. You will need to cast it to the appropriate type in order to use it.

Change Label Value on button click in Xamarin MVVM

I'm facing an issue in Xamarin forms Mvvm. I have 2 different layouts say Layout1 and Layout2 which are bounded with a common ViewModel. Layout1 contains multiple Labels which I'm generating dynamically using for loop in xaml.cs file and bind each Label'sTextProperty using SetBinding. Layout2 contain a button.
Now I want to change Text of a particular Label when button clicked.
Layout1.xaml
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Layout1">
<StackLayout x:Name="ParentStack">
// dynamic Labels to be added here..
</StackLayout>
</StackLayout>
Layout1.xaml.cs
public partial class Layout1: StackLayout
{
public Label dummyLabel;
public Layout1()
{
InitializeComponent();
for (int i = 0; i < 3; i++)
{
dummyLabel= new Label
{
Text = " ",
};
dummyLabel.SetBinding (Label.TextProperty,"PhaseValue");
parentRowCells.Children.Add(dummyLabel);
var tapGestureRecognizer_1 = new TapGestureRecognizer();
tapGestureRecognizer_1.SetBinding(TapGestureRecognizer.CommandProperty,"LabelClicked");
tapGestureRecognizer_1.CommandParameter = dummyLabel;
dummyLabel.GestureRecognizers.Add(tapGestureRecognizer_1);
}
}
}
Layout2.Xaml
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Layout2">
<StackLayout x:Name="ParentStack">
<Button Command={Binding ButtonClickedCommand} Text="Click Me" />
</StackLayout>
</StackLayout>
ViewModel.cs
class ViewModel
{
public Label label = new Label();
public string textstring = "new text string";
ICommand _labelClicked;
public ICommand LabelClicked
{
get
{
this._labelClicked= this._labelClicked?? new Command(s =>
{
label = s as Label;
label.Text = "new text"; //this change the text of particular label when clicked but i need it from button clicked event from another layout.
// here I'm getting the instance of label which i clicked on label.
});
return this._labelClicked;
}
}
public ICommand ButtonClickedCommand{ protected set; get; }
public ViewModel()
{
this.ButtonClickCommand = new Command<Button>((key) =>
{
//here I want to change the value of label when button command is clicked.
aa.Text = "this is not changing the text";
});
}
}
Any help in this or do I need to follow some other pattern..??
My first thought would be to add each Label that you add to a List<Label> somewhere that you can access from both layouts... your View Model would seem like the logical place. Then when you click your button, you can find the particular Label whose text you want to change and change it. You will likely then have to reload your list.
However I think that a better way would be to use a ListView instead of a StackLayout. Then you can have an ItemTemplate for the ListView that includes one Label. You can then set up an ObservableCollection<T> of objects to use as the ListView.ItemsSource. You would want to make some custom object that has a Text property, or whatever you want to call the property that will hold the text for the Labels. It is better to use an object for the T in ObservableCollection<T> rather than using ObservableCollection<string> because changes to a string type will not be reflected in the ListView item, but changes to a property of an object (assuming of course that you make it a Bindable Property) will be reflected in those controls that are bound to that property. So in a nutshell, something like (in your ViewModel):
// Class level variable
ObservableCollection<CustomType> dummyLabelContents;
// then either in the ViewModel constructor or somewhere else:
dummyLabelContents = new ObservableCollection<CustomType>();
CustomType dummyText;
for (int i = 0; i < 3; i++)
{
dummyText = new CustomType
{
Text = " ",
};
}
dummyLabelContents.Add(dummyText);
And your CustomType would just be a simple class with only a BindableProperty called Text.
Set up like this, you can assign your ListView.ItemsSource to be the dummyLabelContents ObservableCollection and then whenever you add an item to the ObservableCollection, the ListView will update automatically. Also, since using a custom type with a bindable text property in the ObservableCollection, when that text property is changed the item in the ListView should also update accordingly.

How do I bind TwoWay between a CheckBox in a UserControl and the ViewModel in the wider scope?

I have a UserControl that has a CheckBox on it. When I consume the UserControl on my main XAML page, I'd like to TwoWay bind a property on the control to a property on my ViewModel e.g.
<myUserControl BtnIsBlacklisted="{Binding IsBlacklisted, Mode=TwoWay}" />
When IsBlacklisted changes, I'd like my checkbox to change too and vice-versa.
Here is what I have,
public static readonly DependencyProperty BtnIsBlacklistedProperty =
DependencyProperty.Register("BtnIsBlacklisted",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(false, new
PropertyChangedCallback(BtnIsBlacklistedPropertyChanged))
);
private static void BtnIsBlacklistedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// ... do something here ...
}
public bool BtnIsBlacklisted
{
get { return (bool)GetValue(BtnIsBlacklistedProperty); }
set { SetValue(BtnIsBlacklistedProperty, value); }
}
My UserControl has this for the CheckBox,
<CheckBox x:Name="myCheckBox"
...
IsChecked="{Binding Path=BtnIsBlacklisted,
ElementName=UserControl,
Converter={StaticResource BoolToNotBool},
Mode=TwoWay}" />
The property on my ViewModel object is as follows,
public bool IsBlacklisted
{
get
{
return App.VM.BlacklistedRetailers.Contains(this.Retailer);
}
set
{
if (value)
{
App.VM.BlacklistedRetailers.Add(this.Retailer);
}
else
{
while (App.VM.BlacklistedRetailers.Contains(this.Retailer))
{
App.VM.BlacklistedRetailers.Remove(this.Retailer);
}
}
this.NotifyPropertyChanged("IsBlacklisted");
}
}
The only way BlacklistedRetailers changes is through the set method above so there is no need to trigger a NotifyPropertyChanged from there ...
I have tried many of the suggestions in other questions i.e.
using a dependency property
including Mode=TwoWay
Binding on the UserControl using a self-referencing DataContext set on the containing grid (this does not work either).
however none of these have worked.
Some final notes:
This is for a Windows Phone 7.5 project
Edit: One way binding doe not work either, it seems it there is a problem binding to the UserControl's own properties
An ElementName Binding matches against x:Name values which are in the same name scope as the element on which the binding is being set. There's not enough of the code shown to tell but you're using "UserControl" which I'm guessing is not set as the name of the element, but is being used to try and match the type. The ElementName also might not be able to resolve if the CheckBox is declared inside a template.

Using ListPicker and DataBinding

Ok. I give up.
I want to use ListPicker control in one of my Windows Phone apps. I am getting an Exception SelectedItem must always be set to a valid value.
This is my XAML piece of ListPicker:
<toolkit:ListPicker x:Name="CategoryPicker"
FullModeItemTemplate="{StaticResource CategoryPickerFullModeItemTemplate}"
Margin="12,0,0,0"
ItemsSource="{Binding CategoryList}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
ExpansionMode="ExpansionAllowed"
FullModeHeader="Pick Categories"
CacheMode="BitmapCache"
Width="420"
HorizontalAlignment="Left" />
CategoryList is an ObservableCollection<Category> in my ViewModel.
SelectedCategory is a property in my ViewModel of type Category.
This is how I am declaring both CategoryList and SelectedCategory:
private Category _selectedCategory;// = new Category();
private ObservableCollection<Category> _categoryList = new ObservableCollection<Category>();
public ObservableCollection<Category> CategoryList
{
get
{
return _categoryList;
}
set
{
_categoryList = value;
RaisePropertyChanged("CategoryList");
}
}
public Category SelectedCategory
{
get
{
return _selectedCategory;
}
set
{
if (_selectedCategory == value)
{
return;
}
_selectedCategory = value;
RaisePropertyChanged("SelectedCategory");
}
}
Appreciate your help!!! Maybe I have not understood the usage of ListPicker very well.
I'd expect the object returned by SelectedCategory to be one of the objects from the CategoryList collection. In your example you are instanciating it within the get, so this is definitely not the case.
If CategoryList contains some values, then perhaps initialize _selectedCategory to null, and then in the get
if(_selectedCategory == null) {
_selectedCategory = CategoryList.First();
}
Take a look at my answer to this question:
Silverlight ComboBox binding with value converter
The short answer is that the selected item must be an item that is contained within the collection. Your getter is setting the selected item to a new object. This new object is not contained within the collection

Resources