I have this listview
<ListView x:Name="LocationsListView" ItemsSource="{Binding ListOfFuel}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<StackLayout>
<Button CommandParameter="{Binding Id}" Clicked="Button_Clicked"></Button>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
With the code behind event I want to get the CommandParameter which is part of the ItemsSource list, the Id value.
I'm doing this:
private void Button_Clicked(object sender, EventArgs e)
{
Button btn = (Button)sender;
int Idvalue = btn.Id;
}
With this approach the app is complaining that the button Id is guid value but in the list I have Id as integer, so I'm assuming the Id is some kind of identification for the button itself not the actual Id value from the items source.
What are my options to find out on button click the listview id or some other property in that list?
You can only get the CommandParameter in Command .
In xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="xxx.MainPage"
x:Name="contentPage">//set the name of contentPage here
<StackLayout>
<Button CommandParameter="{Binding Id}" Command="{Binding Source={x:Reference contentPage}, Path=BindingContext.ClickCommand}"></Button>
</StackLayout>
And in your ViewModel:
public ICommand ClickCommand { get; set; }
//...
public MyViewModel()
{
//...
ClickCommand = new Command((arg)=> {
Console.WriteLine(arg);
});
}
arg here is the CommandParameter that you binding the value of property Id
I can give you two solutions to this.
1. Instead of using a Button inside Nested StackLayout you can use ItemSelected Event of Listview. In that method you can get the entire Item using e.SelectedItem and the cast it to your class type
ListView.ItemSelected += (object sender, SelectedItemChangedEventArgs e) =>
{
var item = (YourClass) e.SelectedItem;
// now you can any property of YourClass.
};
You can find the highest Parent and then get it's BindingContext.
private void Button_Clicked(object sender, EventArgs e)
{
StackLayout stc = ((sender as Button).Parent as StackLayout).Parent as StackLayout;
// Then get the BindingContext
}
Related
I have a bunch of Entrys and Picker and I want the focus to automatically change to the next Entry/Picker.
Im doing that by calling the Focus() method on the Entry/Picker I wanna focus to. If my current focus is on an Entry and my next element is an Entry or a Picker it changes the focus and opens the Keyboard/Pickerdialog as it's supposed to. If my current focus is on a Picker and the next element is an Entry then the Entry gets focused, as supposed to, but the keyboard does not open.
The focus() method is called in the Pickers SelectedIndexChanged event.
How can I fix that?
xaml code:
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="ABM.Ablesegeraet.Views.TourenLadenPage"
Title="Touren laden">
<ContentPage.Content>
<StackLayout>
<Label Grid.Row="0" Grid.Column="0" Margin="30,10,0,0" FontSize="Medium" Text="Mandant:"/>
<Picker x:Name="MandantenPicker" ItemDisplayBinding="{Binding Ort}" Title="Mandant" Grid.Row="0" Grid.Column="1" SelectedIndexChanged="MandantPicker_SelectedIndexChanged"/>
<Entry Grid.Row="1" x:Name="test"/>
</StackLayout>
</ContentPage.Content>
SelectedIndexcChanged:
private void MandantPicker_SelectedIndexChanged(object sender, EventArgs e)
{
test.Focus();
}
Picker did not lose focus during the SelectedIndexChanged method of Picker, and then if you set Entry.Focus, it will cause a display error.
The solution is that Picker loses focus after the SelectedIndexChanged method runs, and then sets Entry.Focus to display it correctly.
Use Task to open a new thread to complete this operation.
Here is the backgroundcode:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private async void picker_SelectedIndexChanged(object sender, EventArgs e)
{
var a = picker.IsFocused;
string res = (sender as Picker).SelectedItem.ToString();
Task.Run(()=> myTask(res));//Create and start the thread
}
private void myTask(string res)
{
Thread.Sleep(300);
var a = picker.IsFocused;
if (res == "Test1")
{
entry1.Focus();
}
if (res == "Test2")
{
entry2.Focus();
}
}
}
The code below shows a simple example of a CollectionView. I am not receiving the event for the SelectionChangedCommand. Can someone see what I am doing wrong?
btw, the complete source for this can be found on GitHub here.
MainPage.xaml
<?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:ControlDemo"
x:Class="ControlDemo.MainPage">
<StackLayout>
<CollectionView SelectionMode ="Single"
ItemsSource="{Binding Tags}"
SelectionChangedCommand="{Binding SelectedTagChanged}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding .}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
MainPageModel.cs
public class MainPageModel : FreshBasePageModel
{
public override void Init(object initData)
{
Tags = new List<string>() { "A", "B", "C" };
base.Init(initData);
}
public List<string> Tags { get; set; }
public Command SelectedTagChanged
{
get
{
return new Command(() =>
{
});
}
}
}
Couple of things that worked at my side (additionally to the SelectionMode = Single):
Make sure your Command' signature is <object> at your PageModel and do any cast according to your needs (specially if your collection becomes more complex).
Also at your XAML you want to give your CollectionView a name and use the SelectedItem property.
SelectionChangedCommandParameter="{Binding SelectedItem, Source={x:Reference cvTagsCollectionView}}"
I use your code and created a demo on my side, I add the widthRequest and HeightRequest to make the collectionView work:
<CollectionView
HeightRequest="170"
WidthRequest="200"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedTagChangedCommand}"
ItemsSource="{Binding Tags}"
>
The SelectionChangedCommand did triggered after I click different items in the CollectionView.
I uploaded a sample here and you can check it: collectionView-selectItemChanged-xamarin.forms
If you want to use your ViewModel, then you should use the Binding for the SelectedItem:
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectedItem="{Binding SelectedMonkey, Mode=TwoWay}">
...
</CollectionView>
and, in your ViewModel:
Monkey selectedMonkey;
public Monkey SelectedMonkey
{
get
{
return selectedMonkey;
}
set
{
if (selectedMonkey != value)
{
selectedMonkey = value;
}
}
}
So everytime you select a new object, the SelectedMonkey will be updated.
If you want to track the SelectionChanged, then, it should be in the code-behind (not sure how to implement within the viewmodel, nothing about that in the docs)
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>
And, in your Page.xaml.cs:
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection;
var current = e.CurrentSelection;
...
}
I improved Fabricio P. answer in this way:
Used {RelativeSource Self} for SelectionChangedCommandParameter. It helps to omit named collection views.
So your xaml part will be something like this:
<CollectionView
ItemsSource="{Binding Objects}"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectObjectCommand}"
SelectionChangedCommandParameter="{Binding SelectedItem, Source={RelativeSource Self}}">
And in your view model:
public ICommand SelectObjectCommand => new Command<string>(i => { Debug.WriteLine("Selected: " + i); });
public IEnumerable<string> Objects { get; set; }
It doesn't look like you set the SelectionMode property. According to the docs:
By default, CollectionView selection is disabled. However, this behavior can be changed by setting the SelectionMode property value to one of the SelectionMode enumeration members:
None – indicates that items cannot be selected. This is the default value.
Single – indicates that a single item can be selected, with the selected item being highlighted.
Multiple – indicates that multiple items can be selected, with the selected items being highlighted.
Adding SelectionMode = Single to the CollectionView will resolve your problem.
if you are using the Model for it you can use the following method
My C# Model View Class
public class MainView : INotifyPropertyChanged
{
public ICommand SelectionChangedCommands => new Command<GroupableItemsView>((GroupableItemsView query) =>
{
GO_Account test = query.SelectedItem as GO_Account;
});
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And this is my XAML
<CollectionView x:Name="myAccounts"
SelectionMode="Single"
ItemsSource="{Binding Products}"
SelectionChangedCommand="{Binding SelectionChangedCommands}"
SelectionChangedCommandParameter="{Binding Source={x:Reference myAccountsModel}}">
</CollectionView>
Im trying to send picker values that the user has chosen to a list. The list should keep on adding what the user chooses and not delete the previous. Any tips on how to do this?
private void MainPicker_SelectedIndexChanged(object sender, EventArgs e)
{
var product = MainPicker.Items[MainPicker.SelectedIndex];
DisplayAlert(product, "Layer added to calculation list", "OK");
}
private void ListView_ItemSelected(object sender,SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
// if selected add to list?
}
I believe your question is about adding a selected item from a picker to a list view which keeps on adding based on the picker selection.
You can do the below code as a public object in the class.
ObservableCollection<LayersClass> listProducts = new ObservableCollection<LayersClass>();
Then get the selected picker item. Assuming the output and collection object is of same type.
var product = MainPicker.Items[MainPicker.SelectedIndex];
if(null != product)
{
LayersClass layer = new LayersClass();
layer.Product = product;
listProducts.Add(layer);
}
XAML changes for the list view - You need to add the ViewCell tag below the DataTemplate which has your listview item child
<ListView
x:Name="productsListView"
HasUnevenRows="False"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="White" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Product}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have added the ViewCell inside the DataTemplate tag.
Update: Just a reminder, there's a 500 point bonus on this if someone can just show me how to implement this functionality without using Gestures>
I am using a ViewCell and a gesture recognizer to open up a picker with the following code. The ViewCell has a label on the left and a label area on the right that is populated initially when the app starts and later with the picker when the ViewCell is clicked.
XAML
<ViewCell x:Name="ati" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding OpenPickerCommand}"
CommandParameter="{x:Reference atiPicker}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<local:LabelBodyRendererClass Text="Answer Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="atiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="atiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="atiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
<ViewCell x:Name="pti" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding OpenPickerCommand}"
CommandParameter="{x:Reference ptiPicker}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<local:LabelBodyRendererClass Text="Phrase Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="ptiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="ptiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="ptiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
C# This works for different pickers (ati, bti, pti etc) with CommandParameter
public SettingsPage()
{
InitializeComponent();
BindingContext = new CommandViewModel();
}
void atiPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
App.DB.UpdateIntSetting(Settings.Ati, selectedIndex);
atiLabel.Text = AS.ati.Text();
}
}
void ptiPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
App.DB.UpdateIntSetting(Settings.Pti, selectedIndex);
ptiLabel.Text = AS.pti.Text();
}
}
public class CommandViewModel: ObservableProperty
{
public ICommand openPickerCommand;
public CommandViewModel()
{
openPickerCommand = new Command<Picker>(PickerFocus);
//openPickerCommand = new Command(tapped);
}
public ICommand OpenPickerCommand
{
get { return openPickerCommand; }
}
void PickerFocus(Picker param)
{
param.Focus();
}
}
I would like to remove the use of TapGestureRecognizers but I still want to retain the functionality and layout.
It's been suggested to me that it would be better if I used the Tapped event of the ViewCell like this:
Tapped="OnTapped"
Can someone explain in some detail how I could wire this up in C#. Would I be best to code something into the CommandViewModel as well as in the C# backing code. Also can the view model have one method that takes an argument so it could be used to open up different pickers?
An example of how I could do this would be very much appreciated. Note that I don't particularly need to use the CommandViewModel if there is a way that I could do this by coding just in the .cs backing code.
(Sorry for the poor english)
Despite not being best practice, I guess you can do something like this, dismissing the viewmodel:
XAML:
<ViewCell x:Name="ati" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<local:LabelBodyRendererClass Text="Answer Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="atiPicker"
IsVisible="false"
HorizontalOptions="End"
SelectedIndexChanged="atiPickerSelectedIndexChanged"
ItemsSource="{Binding Times}">
</Picker>
<local:LabelBodyRendererClass x:Name="atiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
<ViewCell x:Name="pti" Tapped="OpenPickerCommand">
<Grid VerticalOptions="CenterAndExpand" Padding="20, 0">
<local:LabelBodyRendererClass Text="Phrase Time Interval" HorizontalOptions="StartAndExpand" />
<Picker x:Name="ptiPicker" IsVisible="false" HorizontalOptions="End" SelectedIndexChanged="ptiPickerSelectedIndexChanged" ItemsSource="{Binding Times}"></Picker>
<local:LabelBodyRendererClass x:Name="ptiLabel" HorizontalOptions="End"/>
</Grid>
</ViewCell>
C#:
private void OpenPickerCommand(object sender, System.EventArgs e)
{
if (sender != null)
{
Picker pkr = sender == ati ? atiPicker : ptiPicker;
pkr.Focus();
}
}
Answering your question "Can the view model have one method that takes an argument?", it is exactly what you're already doing using the 'OpenPickerCommand' method. The problem is that using the ViewCell's public event 'Tapped', you can't set parameters to the delegate handler.
Let me know if it works for you or if you do need some more information.
I hope it helps.
You can solve this with attached properties. Simply define a "behavior" class for ViewCell that adds the Command/Parameter properties.
public static class TappedCommandViewCell
{
private const string TappedCommand = "TappedCommand";
private const string TappedCommandParameter = "TappedCommandParameter";
public static readonly BindableProperty TappedCommandProperty =
BindableProperty.CreateAttached(
TappedCommand,
typeof(ICommand),
typeof(TappedCommandViewCell),
default(ICommand),
BindingMode.OneWay,
null,
PropertyChanged);
public static readonly BindableProperty TappedCommandParameterProperty =
BindableProperty.CreateAttached(
TappedCommandParameter,
typeof(object),
typeof(TappedCommandViewCell),
default(object),
BindingMode.OneWay,
null);
private static void PropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is ViewCell cell)
{
cell.Tapped -= ViewCellOnTapped;
cell.Tapped += ViewCellOnTapped;
}
}
private static void ViewCellOnTapped(object sender, EventArgs e)
{
if (sender is ViewCell cell && cell.IsEnabled)
{
var command = GetTappedCommand(cell);
var parameter = GetTappedCommandParameter(cell);
if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
public static ICommand GetTappedCommand(BindableObject bindableObject) =>
(ICommand)bindableObject.GetValue(TappedCommandProperty);
public static void SetTappedCommand(BindableObject bindableObject, object value) =>
bindableObject.SetValue(TappedCommandProperty, value);
public static object GetTappedCommandParameter(BindableObject bindableObject) =>
bindableObject.GetValue(TappedCommandParameterProperty);
public static void SetTappedCommandParameter(BindableObject bindableObject, object value) =>
bindableObject.SetValue(TappedCommandParameterProperty, value);
}
After that reference your behavior namespace in XAML and specify the property values using fully qualified names:
<ViewCell StyleId="disclosure-indicator"
behaviors:TappedCommandViewCell.TappedCommand="{Binding BrowseCommand}"
behaviors:TappedCommandViewCell.TappedCommandParameter="https://www.google.com">
<StackLayout Orientation="Horizontal">
<Label Text="Recipient"
VerticalOptions="Center"
Margin="20,0"/>
<Label Text="{Binding LedgerRecord.Recipient}"
HorizontalOptions="EndAndExpand"
VerticalOptions="Center"
Margin="0,0,20,0"/>
</Label>
</StackLayout>
</ViewCell>
The above will allow you to use MVVM and no Tap Gesture Recognizers.
The first problem is that you're mixing the code-behind and MVVM
approaches in the same code. It is confusing and certainly not the
right way to code what you want to achieve. So, all commanding must
be in the ViewModel attached to the View, no code-behind apart some
code only used for UI effects.
There is no need to define a gesture recognizer for all visual items since you just want to detect the tap on all the surface of the viewcell. To achieve this you must define all children of the ViewCell with InputTransparent=true. So the tap will not be detected and will be trapped by the parent ViewCell (you
must indicate the InpuTransparent because there is no tap event
bubbling in X.Forms).
Showing and Hidding the picker is a View problem not a ViewModel one. So here you can use some code-behind to create an event handler for the ViewCell Tapped event. This handler will just set visible=true on the picker.
The picker selected event must be connected to a corresponding Command in the ViewModel. So each time the picker is displayed and a value is selected your viewmodel will be aware of the action. This is the only command you need in your viewmodel. Depending of XForms version the picker has no bindable command, so you can use one of the numerous "bindablepicker" implementation you can find on the web or you can also use a XAML EventToCommand Behavior.
So there is two different problems : showing/hidding the picker which can be achieved directly in XAML or with the help of a bit of code-behind; and the picker item selection that must be managed using a Command in the viewmodel.
Hoping this will help you
How can I remove item form ListView in Xamarin Cross Platform Forms
<ViewCell.ContextActions>
<MenuItem Text="Delete" IsDestructive="True" Command="Binding DleteItemCommand}" />
</ViewCell.ContextActions>
But I want user code complies with MVVM pattern.
So, View model is just for presentation layer, you need interact with your cell instead of viewmodel. Follow the next steps:
1.Create a Observable collection of ViewModels for Cells.
2. Add this collection to ItemSource of ListView.
3. Then for command add callback method
<ListView x:Name="citiesListView" ItemTapped="OnSelection">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Clicked="DeleteAction" Text="Delete" IsDestructive="true" CommandParameter="{Binding .}" />
</ViewCell.ContextActions>
<StackLayout Padding="15,0">
<Label
Text="{Binding .}"
FontSize="30"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Then in code:
public partial class YourPage : ContentPage
{
public ObservableCollection<string> YourCollection { get; set; }
public YourPage()
{
InitializeComponent();
// initialize at this point
YourCollection = new ObservaleCollection(<Some collection of view models>);
citiesListView.ItemsSource = YourCollection;
}
private void DeleteAction(object sender, System.EventArgs e)
{
var menuItem = ((MenuItem)sender);
var yourViewModel = (YourViewModelType) menuItem.CommandParameter;
YourCollection.Remove(yourViewModel);
}
You can add ObservableCollection<YourType> and in command delete element from collection.
var collection = new ObservableCollection<YourType>();
yourList.ItemSource = collection;
// in Command
public void OnDelete (object sender, EventArgs e)
{
// getting reference on menu item
var menuItem = ((MenuItem)sender).CommandParameter;
// cast to underlying viewModel
var yourObject = (YourType)menuItem;
collection.Remove(yourObject);
}
Yes, it is compatible with MVVM pattern. So, you have a Cell in ListView and it is a single representation of viewModel. And using it is approach you have the next relationship: "model - viewModel - view". ObservableCollection has a references on ViewModels that you display in ListView's Cells, and you now can easily delete cells that you want. See improvements above in code