ListView Xamarin not Binding when adding item - xamarin

I Am working with a ListView Control in XF application. My XAML Code looks like this.
<ListView ItemsSource="{Binding RechargeList}" HasUnevenRows="True" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Path=SelectedParkingID}" TextColor="Red" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
While my code behind looks like
private ObservableCollection<Recharge> _RechargeList = new ObservableCollection<Recharge>();
public ObservableCollection<Recharge> RechargeList
{
get
{
return _RechargeList;
}
set
{
SetProperty(ref _RechargeList, value);
}
}
And I add Items to Collection in DelegateCommand Event
RechargeList.Add(new Recharge() { SelectedParkingIDParkingID = ParkingID, RechargeAmount = double.Parse(RechargeAmount), BalanceAmount = 10 });
However, the Listview fails to refresh. Could some one help me ?

Looks like you have a typo
<TextCell Text="{Binding Path=SelectedParkingID}" TextColor="Red" />
Should be
<TextCell Text="{Binding Path=SelectedParkingIDParkingID }" TextColor="Red" />
based on what your model looks like. If you try to bind to a property that doesn't exist, it fails softly. So you're adding an item, but the TextCell doesn't render since it has no content.

Please try to implement INotifyPropertyChanged interface in your class.
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Each property is then just something like:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}

Related

How can I pass a command to a template and have it execute in my back end code and pass the parameter?

I have this template:
<?xml version="1.0" encoding="utf-8"?>
<Grid Padding="20,0" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.Templates.DataGridTemplate"
x:Name="this" HeightRequest="49" Margin="0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand, Source={x:Reference this}}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="{Binding Test" />
</Grid>
Behind this I have:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
"Command",
typeof(ICommand),
typeof(DataGridTemplate),
null);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
}
and I am trying to call the template like this in file: Settings.xaml.cs
<template:DataGridTemplate TapCommand="openCFSPage" />
hoping that it will call my method here in file: Settings.cs
void openCFSPage(object sender, EventArgs e)
{
Navigation.PushAsync(new CFSPage());
}
The code compiles but when I click on the grid it doesn't call the openCFSPage method.
1) Does anyone have an idea what might be wrong?
2) Also is there a way that I can add a parameter to the template and then have that parameter passed to my method in the CS back end code?
Note that I would like to avoid adding a view model if possible. The application is small and I'd like to just have the code I need in the CS code of the page that calls the template.
Please note that the simplest way to implement this would be through MVVM (i.e. a view-model), but if you want to side-step this option (as you mentioned in the question) then you can use one of the following options
Option1 : Wrap delegate into command object
If you look at it from the perspective of a XAML parser, you are technically trying to assign a delegate to a property of type ICommand. One way to avoid the type mismatch would be to wrap the delegate inside a command-property in the page's code-behind.
Code-behind [Settings.xaml.cs]
ICommand _openCFSPageCmd;
public ICommand OpenCFSPageCommand {
get {
return _openCFSPageCmd ?? (_openCFSPageCmd = new Command(OpenCFSPage));
}
}
void OpenCFSPage(object param)
{
Console.WriteLine($"Control was tapped with parameter: {param}");
}
XAML [Settings.xaml]
<!-- assuming that you have added x:Name="_parent" in root tag -->
<local:DataGridView TapCommand="{Binding OpenCFSPageCommand, Source={x:Reference _parent}}" />
Option2 : Custom markup-extension
Another option (a bit less mainstream) is to create a markup-extension that wraps the delegate into a command object.
[ContentProperty("Handler")]
public class ToCommandExtension : IMarkupExtension
{
public string Handler { get; set; }
public object Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
var lineInfo = (serviceProvider?.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo ?? new XmlLineInfo();
object rootObj = Source;
if (rootObj == null)
{
var rootProvider = serviceProvider.GetService<IRootObjectProvider>();
if (rootProvider != null)
rootObj = rootProvider.RootObject;
}
if(rootObj == null)
{
var valueProvider = serviceProvider.GetService<IProvideValueTarget>();
if (valueProvider == null)
throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
//we assume valueProvider also implements IProvideParentValues
var propInfo = valueProvider.GetType()
.GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(propInfo == null)
throw new ArgumentException("valueProvider does not provide an ParentObjects");
var parentObjects = propInfo.GetValue(valueProvider) as IEnumerable<object>;
rootObj = parentObjects?.LastOrDefault();
}
if(rootObj != null)
{
var delegateInfo = rootObj.GetType().GetMethod(Handler,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(delegateInfo != null)
{
var handler = Delegate.CreateDelegate(typeof(Action<object>), rootObj, delegateInfo) as Action<object>;
return new Command((param) => handler(param));
}
}
throw new XamlParseException($"Can not find the delegate referenced by `{Handler}` on `{Source?.GetType()}`", lineInfo);
}
}
Sample usage
<local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
You have 2 options depending on the the use case :
FYI, there's no way to call another method directly from the view (its a bad design pattern to do so)
Using Event Aggregator :
Create interface
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
}
All you have to do is call it from you TapCommand
_eventAggregator.GetEvent<ItemSelectedEvent>().Publish(_selectedItem);
Then in your Settings.cs you can Create a method that can receive the data
this.DataContext = new ListViewModel(ApplicationService.Instance.EventAggregator);
Inheritance and Polymorphism / Making openCFSPage a service :
Creating a interface / service that links both models
public interface IOpenCFSPage
{
Task OpenPage();
}
and a method :
public class OpenCFSPage : IOpenCFSPage
{
private INavigationService _navigationService;
public OpenCFSPage(INavigationService navigationService){
_navigationService = navigationService;
}
public async Task OpenPage()
{
await _navigationService.NavigateAsync(new CFSPage());
}
}
Settings.xaml:
<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" />
<!-- Uncomment below and corresponding parameter property code in DataGridTemplate.xaml.cs to pass parameter from Settings.xaml -->
<!--<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" CommandParameter="A" />-->
Settings.xaml.cs:
public Settings()
{
InitializeComponent();
OpenCFSPage = new Command(p => OpenCFSPageExecute(p));
BindingContext = this;
}
public ICommand OpenCFSPage { get; private set; }
void OpenCFSPageExecute(object p)
{
var s = p as string;
Debug.WriteLine($"OpenCFSPage:{s}:");
}
DataGridTemplate.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
Padding="0,20"
HeightRequest="49" Margin="0"
x:Class="Japanese.DataGridTemplate">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="Test" />
</Grid>
DataGridTemplate.xaml.cs:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
nameof(TapCommand), typeof(ICommand), typeof(DataGridTemplate), null,
propertyChanged: OnCommandPropertyChanged);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
//public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
// nameof(CommandParameter), typeof(string), typeof(DataGridTemplate), null);
//public string CommandParameter
//{
// get { return (string)GetValue(CommandParameterProperty); }
// set { SetValue(CommandParameterProperty, value); }
//}
static TapGestureRecognizer GetTapGestureRecognizer(DataGridTemplate view)
{
var enumerator = view.GestureRecognizers.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
if (item is TapGestureRecognizer) return item as TapGestureRecognizer;
}
return null;
}
static void OnCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is DataGridTemplate view)
{
var tapGestureRecognizer = GetTapGestureRecognizer(view);
if (tapGestureRecognizer != null)
{
tapGestureRecognizer.Command = (ICommand)view.GetValue(TapCommandProperty);
//tapGestureRecognizer.CommandParameter = (string)view.GetValue(CommandParameterProperty);
}
}
}
}
Check this code you help you. Here you have to pass a reference of list view and also you need to bind a command with BindingContext.
<ListView ItemsSource="{Binding Sites}" x:Name="lstSale">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text="{Binding FriendlyName}" />
<Button Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding
Path=BindingContext.RoomClickCommand,
Source={x:Reference lstSale}}"
CommandParameter="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

How to pass data from the listview tapped event to a telerik dataform in another page

I need to pass data from a ListView to a TodoDetail page where I have a Telerik DataForm, but I don't know how to make it work. If I use normal Xamarin Forms controls it works fine, but need it to work with the Telerik DataForm control.
Here is my code:
​Todo.xaml
list item tapped handler
private async void ToDoTaskTap(object sender, ItemTappedEventArgs e)
{
var user = ToDoTask.SelectedItem as tblEmpTask;
if (user != null)
{
var mainViewModel = BindingContext as MainViewModel;
if (mainViewModel != null)
{
mainViewModel.Selected = user;
await Navigation.PushAsync(new ToDoDetail(mainViewModel)); ​
}
}
}
tblEmpTask.cs
public class tblEmpTask
{
public string strTaskName { get; set; }
}
TodoDetail.xaml
<telerikInput:RadDataForm x:Name="dataForm">
<telerikInput:RadDataForm.Source>
<local1:MainViewModel />
</telerikInput:RadDataForm.Source>
</telerikInput:RadDataForm>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
​public tblEmpTask Selected
{
get { return _Selected; }
set
{
_Selected = value;
OnPropertChanged();
}
}
​
[DisplayOptions(Header = "Name")]
public string Name
{
get { return this.Selected.strTaskName; }
set
{
if (value != this.Selected.strTaskName)
{
this.Selected.strTaskName = value;
OnPropertChanged();
}
}
}
}
You must add a binding between the SelectedItem and the ListView
Here's an example:
ViewModel:
public List<object> ItemsSource { get; set; }
public object SelectedItem {
set { SelectedItemChanged(value); }
}
async void SelectedItemChanged(object value) {
await App.Current.MainPage.Navigation.PushAsync(new AboutPage(SelectedItem));
}
Page:
<ListView
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding .}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Xamarin Forms - remove extra space / embedded ListView

I am trying to figure out how to remove the white space you see in the image below (surrounded by a red rectangle). Notice I have a ListView embedded in a parent ListView.
XAML
<ListView x:Name="___listview" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Button Image="{Binding ImageName}" Command="{Binding ShowDetailsCommand}" />
<ListView ItemsSource="{Binding Notes}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Note}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This probably isn't needed, but here is the model...
MODEL
namespace ViewCellClick
{
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model : ModelBase
{
public Model()
{
_imageName = "ellipses_vertical.png";
_showDetails = true;
ShowDetailsCommand = new Command(() =>
{
ShowDetails = !_showDetails;
ImageName = (_imageName == "ellipses_vertical.png")
? "ellipses_horizontal.png"
: "ellipses_vertical.png";
});
}
bool _showDetails;
public bool ShowDetails
{
get { return _showDetails; }
set { if (_showDetails != value) { _showDetails = value; OnPropertyChanged("ShowDetails"); } }
}
string _imageName;
public string ImageName
{
get { return _imageName; }
set { if (_imageName != value) { _imageName = value; OnPropertyChanged("ImageName"); } }
}
public ICommand ShowDetailsCommand { get; set; }
List<ChildModel> _notes;
public List<ChildModel> Notes { get { return _notes; } set { _notes = value; } }
}
public class ChildModel : ModelBase
{
public ChildModel(string note) { _note = note; }
string _note;
public string Note
{
get { return _note; }
set { if (_note != value) { _note = value; OnPropertyChanged("Note"); } }
}
}
}
You can't do this with Xamarin.Forms.ListView and nesting them is not supported. Really on iOS this would be very difficult and I'm not sure you could get it working without some weird gesture behavior.

Two Way Binding not working

I am new to two way binding in wp7.The below code does not assign the textbox value to the object automatically and returns null.
Xaml:
<Grid x:Name="ContentPanel" DataContext="{Binding usd}" Grid.Row="1" Margin="14,10,10,-10" >
<TextBox Text="{Binding UserName,Mode=TwoWay}" Name="txt1" Width="200" Height="60" FontSize="20" Margin="128,48,128,499"/>
<TextBox Text="{Binding Password,Mode=TwoWay}" Name="txt2" Width="200" Height="60" FontSize="20" Margin="128,263,128,284"/>
<TextBox Text="{Binding Email,Mode=TwoWay}" Name="txt3" Width="200" Height="60" FontSize="20" Margin="128,159,128,388"/>
<Button Content="Send" FontSize="18" Margin="179,413,170,129"
Click="Button_Click_1" />
</Grid>
Cs:
public class UserLogin:INotifyPropertyChanged
{
private string _username;
private string _pwd;
private string _email;
public string UserName
{
get
{
return _username;
}
set
{
_username = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get
{
return _pwd;
}
set
{
_pwd = value;
OnPropertyChanged("Password");
}
}
public string Email
{
get
{
return _email;
}
set
{
_email = value;
OnPropertyChanged("Email");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Instantiation:
public UserLogin usd = null;
In constructor:
usd = new UserLogin();
In Button ClickEvent:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
// ContentPanel.DataContext = usd;
MessageBox.Show(usd.Email);
}
Null reference exception in Message box statement. Thanks..
you can just bind to public properties - so your: DataContext="{Binding usd}" should be wrong because usd is just a field
btw if you set this in your ctor too, an remove the xaml binding it could work
usd = new UserLogin();
ContentPanel.DataContext = usd;
About your control/page (which XAML belongs to it)
it's datacontext should contain a usd property
that property should also notifying property!
of course your control/page's datacontext class also should implement INotifyPropertyChanged
because your usd is not set as a property its just a variable.... do one thing
public UserLogin usd {get;set;}
usd = null;

Listbox items won't update with binding wp7

I have a listbox, with custom items. Code:
<ListBox Height="600" HorizontalAlignment="Left" Margin="7,6,0,0" Name="friendList" VerticalAlignment="Top" Width="449" ItemsSource="{Binding Friends}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5,0">
<Image Height="120" HorizontalAlignment="Left" Name="image" Stretch="Fill" VerticalAlignment="Top" Width="120" Source="{Binding ImageUri}" GotFocus="image_GotFocus"/>
<CheckBox Height="78" HorizontalAlignment="Left" Margin="65,63,0,0" x:Name="selectedChckbox" VerticalAlignment="Top" Width="55" IsChecked="{Binding Selected, Mode=TwoWay}"/>
<TextBlock Height="58" HorizontalAlignment="Left" Margin="0,122,0,0" x:Name="nameTextBlck" VerticalAlignment="Top" Text ="{Binding Title}" Width="120" TextWrapping="Wrap" GotFocus="name_GotFocus"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've created a veiwmodel for the values for binding, and when I click on one item I want to change the checkbox state like so:
friendSelectionViewModel.Friends[_selectFriendContent.friendList.SelectedIndex].Selected = !friendSelectionViewModel.Friends[_selectFriendContent.friendList.SelectedIndex].Selected;
The ViewModel Code:
public class FacebookFriendSelectionViewModel : INotifyPropertyChanged
{
public FacebookFriendSelectionViewModel()
{
Friends = new ObservableCollection<TempFriends>();
}
/// <summary>
/// A collection for MenuItemViewModel objects.
/// </summary>
public ObservableCollection<TempFriends> Friends { get; private set; }
public void AddItem(TempFriends item)
{
Friends.Add(item);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
public class TempFriends
{
bool _selected;
public string Title { get; set; }
public string ImageUri { get; set; }
public bool Selected {
get
{
return _selected;
}
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
public string Id { get; set; }
public TempFriends(string title, string imageUir, bool selected, string id)
{
Title = title;
ImageUri = imageUir;
_selected = Selected = selected;
Id = id;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
But the only way the listbox gets the values updated, if I set the datacontext to null and than assign the viewmodel aggain like so:
_selectFriendContent.DataContext = null;
_selectFriendContent.DataContext = friendSelectionViewModel;
But this takes about 5-10 seconds to refresh the list. I know there is a better solution, I just cant figure out how.
Thanks in advance!
TempFriends class doesn't implement INotifyPropertyChanged as far as I see. Just add public class TempFriends : INotifyPropertyChanged

Resources