I have the following form
<ListView x:Name="DocumentList"
HasUnevenRows="true"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Check In"
Clicked="CheckInFile"/>
<MenuItem Text="Check Out"
Clicked="CheckOutFile"/>
<MenuItem Text="Status"
Clicked="FileStatus"/>
</ViewCell.ContextActions>
<Grid>
<Label Text="{Binding Path=WebUrl}"
FontSize="Small" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
And here's the click event handler for one of the items in the context menu:
protected void CheckOutFile(object sender, EventArgs e)
{
var menuItem = sender as MenuItem;
if (menuItem != null)
{
var webUrl = menuItem.BindingContext;
DisplayAlert("Alert", "Check Out File: " + webUrl + "requested", "ok");
}
}
Problem / Questions
Question 1
When the form loads - the "WebUrl" value appears correctly.
But when I click on "CheckOut", i don't know how to extract the value / url from the menuItem.BindingContext. The alert shows null.
Question 2
I only want the user to see the WebUrl, but I want to pass 3 different values let's say when the click event is triggered. How would I do this?
Sorry, I'm just a noob with xaml and xamarin.
Thanks.
If your ListView ItemsSource is a List<SomeType> then
var item = (SomeType)menuItem.BindingContext;
DisplayAlert("Alert", "Check Out File: " + item.webUrl + "requested", "ok");
assuming that webUrl is a property of SomeType
Related
I'm trying to make a custom home page where pages are listed on an Horizontal scroll view as "Services" so each one of them should navigate to a different Page.
I have a view like this:
<controls:HorizontalScrollView HeightRequest="160"
Orientation="Horizontal"
ItemsSource="{Binding OwnerServicesList}"
x:Name="OwnerServicesSlider"
ItemSelected="OwnerServicesSlider_ItemSelected">
<controls:HorizontalScrollView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="10,0,5,0" WidthRequest="100" HeightRequest="100">
<Image HorizontalOptions="Start" Source="{Binding ImgUrl}" WidthRequest="100" HeightRequest="100" />
<Label Style="{StaticResource BoldLabel}" HorizontalTextAlignment="Center" FontSize="13" LineBreakMode="TailTruncation" Text="{Binding Name}" TextColor="Black"/>
</StackLayout>
</ViewCell>
</DataTemplate>
Im using a custom made controller for a Horizontal Scroll view that works like a listview, every item on tap raises a ItemTappedEventArgs event.
Inside my model i´ve declared a public Page Page { get; set; } for each object in the scroll view.
What im trying to do is recover the tapped element and recover the Page stored in it so that I can Navigate to that specific page.
So far I have something like this:
private void OwnerServicesSlider_ItemSelected(object sender, ItemTappedEventArgs e)
{
var service = OwnerServicesSlider.SelectedItem as Services;
Navigation.PushAsync(service.Page);
}
It shows no errors but when I run it I get a
InvalidOperationException: 'Page must not already have a parent.
Any hint will be appreciated!
as Jason said,maybe the page you would push which is exist in thecurren navigation structure,there is a workaround ,before you push the page :
private void OwnerServicesSlider_ItemSelected(object sender, ItemTappedEventArgs e)
{
var service = OwnerServicesSlider.SelectedItem as Services;
service.Page.Parent = null;
Navigation.PushAsync(service.Page);
}
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
}
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.
I've read about messaging, triggers, behaviors, etc. ... all seems a bit overkill for what I am trying to accomplish.
I have a repeating data entry screen in xaml that has 1 picker, 2 entries, and 1 button. The picker, once a value is selected, keeps that selection. The 1st entry does the same as the picker. The 2nd entry is the one that is always getting new values.
I want to collect the filled in values on click of the button and then clear the last entry field of its data and put focus back on that entry so the user can enter a new value and hit save. repeat repeat repeat etc.
I understand the MVVM model and theory - but I just want to put the focus on an entry field in the xaml view and am completely stumped.
EDIT to add code samples
view.xaml:
<StackLayout Spacing="5"
Padding="10,10,10,0">
<Picker x:Name="Direction"
Title="Select Direction"
ItemsSource="{Binding Directions}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedDirection}"/>
<Label Text="Order"/>
<Entry Text="{Binding Order}"
x:Name="Order" />
<Label Text="Rack"/>
<Entry Text="{Binding Rack}"
x:Name="Rack" />
<Button Text="Save"
Style="{StaticResource Button_Primary}"
Command="{Binding SaveCommand}"
CommandParameter="x:Reference Rack" />
<Label Text="{Binding Summary}"/>
</StackLayout>
viewmodel.cs
public ICommand SaveCommand => new DelegateCommand<View>(PerformSave);
private async void PerformSave(View view)
{
var scan = new Scan()
{
ScanType = "Rack",
Direction = SelectedDirection.Name,
AreaId = 0,
InsertDateTime = DateTime.Now,
ReasonId = 0,
ScanItem = Rack,
OrderNumber = Order,
ScanQty = SelectedDirection.Value,
IsUploaded = false
};
var retVal = _scanService.Insert(scan);
if (!retVal)
{
await _pageDialogService.DisplayAlertAsync("Error", "Something went wrong.", "OK");
}
else
{
view?.Focus();
Rack = string.Empty;
Summary = "last scan was great";
}
}
Error shows up in this section:
private void InitializeComponent() {
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(RackPage));
Direction = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Picker>(this, "Direction");
Order = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Entry>(this, "Order");
Rack = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Entry>(this, "Rack");
}
You can send the Entry as a View parameter to your view model's command. Like that:
public YourViewModel()
{
ButtonCommand = new Command<View>((view) =>
{
// ... Your button clicked stuff ...
view?.Focus();
});
}
And from XAML you call this way:
<Entry x:Name="SecondEntry"
... Entry properties ...
/>
<Button Text="Click Me"
Command="{Binding ButtonCommand}"
CommandParameter="{Reference SecondEntry}"/>
I hope it helps.
You can set the CommandParameter in the XAML and use that in the viewmodel.
In Xaml:
<ContentView Grid.Row="0" Grid.Column="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Image Source="downarrow" HeightRequest="15" WidthRequest="15" VerticalOptions="Center" HorizontalOptions="End" Margin="0,0,5,0" />
<ContentView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SecurityPopupCommand}" CommandParameter="{x:Reference AnswerEntry}"/>
</ContentView.GestureRecognizers>
</ContentView>
In view model:
public ICommand SecurityPopupCommand { get { return new Command(OpenSecurityQuestionPopup); } }
private void OpenSecurityQuestionPopup(object obj)
{
var view = obj as Xamarin.Forms.Entry;
view?.Focus();
}
Not sure you can set the focus of an entry from the XAML, but from the page, you could just use the function Focus of the Entry on the Clicked Event:
saveButton.Clicked += async (s, args) =>
{
//save the data you need here
//clear the entries/picker
yourEntry.Focus();
};
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