Xamarin Forms FindByName always returns null - c#

I have a ContentPage that is bound to a viewmodel. Still, at one point, I need to access a Picker inside a StackLayout which in turn is in a Syncfusion ListViewItem. The XAML is pretty straightforward:
<?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" xmlns:sf="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
mc:Ignorable="d"
x:Class="PerformanceTM.Views.NewCircuitView">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
<sf:SfListView x:Name="ExerciseList" ItemsSource="{Binding Exercises}" DragStartMode="OnHold">
<sf:SfListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Picker Title="Kategorie" x:Name="CategoryPicker" ItemsSource="{Binding Source={x:Reference ExerciseList}, Path=BindingContext.ExerciseCategories}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding Category}" SelectedIndexChanged="CategoryChanged"/>
<Picker Title="Übung" x:Name="ExerciseNamePicker" ItemDisplayBinding="{Binding Name}" SelectedIndexChanged="ExerciseSelected"/>
<Button Text="..." Clicked="ConfigureSetsClicked"/>
<Button Text="(-)" />
</StackLayout>
</ViewCell>
</DataTemplate>
</sf:SfListView.ItemTemplate>
</sf:SfListView>
<StackLayout Orientation="Horizontal">
<Button Text="(+) Übung" Command="{Binding AddExerciseCommand}"/>
<Button Text="Ok" Command="{Binding ApplyChangesCommand}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
As you can see, both Pickers in the ViewCell have x:Name assigned to them. When I select a value in the ExerciseCategoryPicker, I load and assign new values to to ExerciseNamePicker in Code behind, like so:
private async void CategoryChanged(object sender, EventArgs e)
{
var picker = sender as Picker;
var stackLayout = picker.Parent as StackLayout;
ListViewItem listViewItem = stackLayout.Parent as ListViewItem;
var ex = picker.BindingContext as Exercise;
if (ex is null)
return;
ex.Category = picker.SelectedItem as ExerciseCategory;
var apiService = new ApiServices();
var exsForCategory = await apiService.GetExercisesForCategory(ex.Category.Name);
Picker exnp = stackLayout.FindByName<Picker>("ExerciseNamePicker");
if (exnp is null)
exnp = stackLayout.Children.OfType<Picker>().Where(x => x.Title == "Übung").FirstOrDefault();
exnp.ItemsSource = exsForCategory;
if (exsForCategory.Count > 0)
exnp.SelectedItem = exsForCategory.FirstOrDefault();
var bc = this.BindingContext as NewCircuitViewModel;
bc.ExercisePairing.Descriptor = bc.ExercisePairing.UpdateDescriptor();
}
private void ExerciseSelected(object sender, EventArgs e)
{
try
{
var picker = (sender as Picker);
var stackLayout = picker.Parent as StackLayout;
var vc = picker.Parent.Parent as Syncfusion.ListView.XForms.ListViewItem;
var ex = vc.BindingContext as Exercise;
var se = picker.ItemsSource[picker.SelectedIndex] as Exercise;
var exnp = stackLayout.FindByName("CategoryPicker") as Picker;
if (exnp is null)
exnp = stackLayout.Children.OfType<Picker>().Where(x => x.Title == "Kategorie").FirstOrDefault();
var ec = exnp.SelectedItem as ExerciseCategory;
ex.Category = ec;
ex.Name = se.Name;
ex.Id = se.Id;
ex.Description = se.Description;
ex.VideoUrl = se.VideoUrl;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Now what happens is that FindByName() returns null in each situation. Oddly enough, when I debug and inspect the Children of StackLayout just before calling FindByName, it does contain children with the appropriate IDs (i.e., the x:Name). When I access them via get, I get the GUID (this somehow confuses me, as I thought there should only be a GUID in the first place, but well).
I have found a workaround by just selecting the element by Title, but this is a rather strange behaviour, especially considering that this has worked in the past. Only change I made since then was the integration of the SyncFusion ListView. Could that be an issue? Has anyone experienced this and/or can provide more insight?
PS: I have gone through all the "usual" fixes such as deleting the .v, bin and obj folders...

You can get the children element of SfListView.ItemTemplate using behavior for the parent element of the ItemTemplate. Please refer the following code snippet for getting the named elements inside DataTemplate,
Xaml: Define Behavior for StackLayout
<sf:SfListView x:Name="ExerciseList" ItemsSource="{Binding Exercises}" DragStartMode="OnHold">
<sf:SfListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<StackLayout.Behaviors>
<local:Behavior/>
</StackLayout.Behaviors>
<Picker Title="Kategorie" x:Name="CategoryPicker" ItemsSource="{Binding Source={x:Reference ExerciseList}, Path=BindingContext.ExerciseCategories}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding Category}"/>
<Picker Title="Übung" x:Name="ExerciseNamePicker" ItemDisplayBinding="{Binding Name}"/>
<Button Text="..." Clicked="ConfigureSetsClicked"/>
<Button Text="(-)" />
</StackLayout>
</ViewCell>
</DataTemplate>
</sf:SfListView.ItemTemplate>
Behavior: Get element using FindByElement. Trigger SelectedIndexChanged event for Picker.
public class Behavior : Behavior<StackLayout>
{
Picker CategoryPicker;
Picker ExerciseNamePicker;
protected override void OnAttachedTo(StackLayout bindable)
{
CategoryPicker = bindable.FindByName<Picker>("CategoryPicker");
ExerciseNamePicker = bindable.FindByName<Picker>("ExerciseNamePicker");
CategoryPicker.SelectedIndexChanged += CategoryPicker_SelectedIndexChanged;
ExerciseNamePicker.SelectedIndexChanged += ExerciseNamePicker_SelectedIndexChanged;
base.OnAttachedTo(bindable);
}
private void ExerciseNamePicker_SelectedIndexChanged(object sender, EventArgs e)
{
//Your logic here.
}
private void CategoryPicker_SelectedIndexChanged(object sender, EventArgs e)
{
//Your logic here.
}
}
You can also refer our online document regarding the same from the following link,
Document

Related

Xamarin Forms - How to select a ListView item when its image is clicked?

I have a data template coded in xaml where the ListViewItem has an ImageButton:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="George.KeySave.FilesList"
xmlns:localImg="clr-namespace:George.Image"
Title="KeySave">
<ContentPage.Content>
<ListView x:Name="Flist" ItemSelected="Flist_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,0,0,0">
<Label x:Name="SelFileName" Text="{Binding fname}" FontSize="Medium" TextColor="Blue" VerticalOptions="Center"/>
<ImageButton HeightRequest="33" WidthRequest="33" Source="{localImg:Emb ResId=George.Image.deer.png}"
x:Name="delete" Clicked="delete_Clicked" HorizontalOptions="EndAndExpand"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
But it fails under these conditions. Suppose I click the name of the second item so that the second item is selected. The problem is if I now click the ImageButton of the first item, the wrong file will be deleted because the second item is still selected.
This code shows how I handle the ImageButton.Click and the ListView.ItemSelected events.
public partial class FilesList : ContentPage
{
private void delete_Clicked(object sender, EventArgs e)
{
DisplayAlert("delete file", $"{TdelFile.fname}", "ok");
}
listdatas TdelFile;
private void Flist_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var delFile = e.SelectedItem as listdatas;
TdelFile = delFile;
}
}
The problem: The incorrect file pops up when I click the ImageButton of the first item while the second item is selected.
As your code is written now, you would look at the sender argument of your ImageButton.Click handler which will be the ImageButton. The BindingContext of the ImageButton will be the item itself and you can set FList.SelectedItem from that. Then, the user is given a confirmation prompt before proceeding (or not) with the file deletion.
private async void delete_Clicked(object sender, EventArgs e)
{
if (sender is ImageButton imageButton)
{
Flist.SelectedItem = imageButton.BindingContext;
var fileName = ((DataModel)Flist.SelectedItem).fname;
if (await App.Current.MainPage.DisplayAlert(
"delete file",
fileName, accept: "OK",
cancel: "CANCEL"))
{
System.Diagnostics.Debug.WriteLine($"DELETED {fileName}");
}
else
{
System.Diagnostics.Debug.WriteLine("Cancelled!");
}
}
}

how to clear collectionview selection in xamarin

On Page#1, i have a collectionview with text/string items list. If you tap a a item, it will get the current tapped text/string and send to Page#2. sound simple
issue I am having: if you tap on item#1, then it will send item#1 to page2, this part working fine. But on page#2, if you hit back button, and tap on item#1 again.. than nothing happens, it doesnt go to page#2
Fix: i think i need to somehow clear tap selection, and than send the item to page#2. but im not sure how to do this
On Page#1, i have a simple collectionview. Collection view contains text/string list
<CollectionView ItemsSource="{Binding MyListItem}"
SelectionMode="Single"
SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<!-- Body -->
<Grid Padding="0">
<Frame CornerRadius="3" BorderColor="#f2f4f5" HasShadow="True">
<StackLayout Orientation="Horizontal">
<Image Source="icon_about"
WidthRequest="25" />
<StackLayout VerticalOptions="Center">
<Label VerticalOptions="Center"
FontSize="16"
Text="{Binding .}" />
</StackLayout>
</StackLayout>
</Frame>
back end code to handle selection is:
private async void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection.FirstOrDefault();
var current = e.CurrentSelection.FirstOrDefault();
var route = $"{ nameof(Page2) }?URLCardType={current}";
await Shell.Current.GoToAsync(route);
//clear selection
((CollectionView)sender).SelectedItem = null;
}
Update ((CollectionView)sender).SelectedItem = null; fixed the issue of clearing selected item but CollectionView_SelectionChanged method is get run twice on single tap. why? this is all the code i have
#jason thanks, this worked for me. i just had to check if selection item is null than do nothing
private async void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var MyCollectionView = sender as CollectionView;
if (MyCollectionView.SelectedItem == null)
return;
var previous = e.PreviousSelection.FirstOrDefault();
var current = e.CurrentSelection.FirstOrDefault();
var route = $"{ nameof(Page2) }?URLCardType={current}";
await Shell.Current.GoToAsync(route);
//clear selection
MyCollectionView.SelectedItem = null;
}
Here is a simple solution that worked for me.
Setting to null did not do the trick, it was causing all kinds of
weird stuff.
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var itemselected = e.CurrentSelection[0] as ProductsItem;
if (itemselected != null)
Shell.Current.GoToAsync(nameof(SelectedProductPage));
//Setting the selected item to an emptyviewproperty after navigation
CollectionList.SelectedItem = SelectableItemsView.EmptyViewProperty;
}

ListView items get updated only after scrolled back into view - Xamarin Forms

I have created a listview which has children whose background is bound to itemssource.
This works well on Android with the PropertyChanged event called.
But unfortunately it doesn't work as smooth on iOS the background change gets reflected only after I've scrolled the element out of view and back into the view to redraw it.
Is there any other way I can make the content refresh manually?
Code:
<ListView x:Name="ListView" ItemsSource="{Binding ListSource}" RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<ContentView Padding="10" BackgroundColor="{Binding BackgroundColor}">
<Label Text="{Binding Name}" HorizontalOptions="Center" TextColor="White" />
</ContentView>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Handling the itemclick to change the background and remove the selecteditem.
ListView.ItemTapped += async (s, e) =>
{
var list = ListSource;
var listItem = list.First(c => c.Id == ((ListItem)e.Item).Id);
listItem.Selected = !listItem.Selected;
SelectListSource = list;
ListView.SelectedItem = null;
};
Code in the model :
public Boolean Selected
{
get
{
return _selected;
}
set
{
_selected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("BackgroundColor"));
}
}
public Color BackgroundColor
{
get
{
if (Selected)
return Color.Black;
else
return Color.Blue
}
}
Ok so I found a work around by using XFGloss.
Added Binding to the ViewCell with the help of this nuget.
<ViewCell xfg:CellGloss.BackgroundColor="{Binding BackgroundColor}">

How to make the loading screen to disappear when my data is loaded in Xamarin Forms?

I want to build a loading screen when no data is displayed. But it's not working, it keeps loading forever. How to make the loading screen to disappear when my data is loaded?
This is my C# code
if (Clublistview.ItemsSource == null)
{
try
{
base.OnAppearing();
await setClubs(Clublistview);
overlay.IsVisible = false;
Clublistview.IsVisible = true;
}
catch (Exception ex)
{
//MessagingCenter
await DisplayAlert("Error",
"There seems to be an error, please check your internet connection.",
"OK");
}
}
else
{
overlay.IsVisible = true;
Clublistview.IsVisible = false;
}
This is the XAML code
<ListView x:Name="Clublistview" HasUnevenRows="true" ItemSelected="OnItemSelected" ItemsSource="{Binding Id}" IsVisible="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="55">
<StackLayout BackgroundColor="White"
Orientation="Vertical">
<StackLayout Orientation="Horizontal" Padding="2,2,2,2">
<Image Source="{Binding Logo}" IsVisible="true" WidthRequest="50" HeightRequest="50"/>
<StackLayout Orientation="Vertical">
<Label Text="{Binding Name}" FontSize="20" x:Name="BtnClub"
TextColor="Black" />
<Label HorizontalOptions="Start" Text="Select for more info" FontSize="10"/>
<!--<Button BackgroundColor="White" TextColor="Black" HorizontalOptions="Start" x:Name="btnInfo"
Text="Select for more info" FontSize="10" Clicked="OnInfoClicked" CommandParameter="{Binding Id}"/>-->
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentView x:Name="overlay" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" AbsoluteLayout.LayoutFlags="All" IsVisible="false">
<ActivityIndicator IsRunning="True" IsVisible="True" Color="Black" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
</ContentView>
It looks like this code is placed on the OnAppearing method of your ContentPage. If that's the case, it's only going to be called 1 time as the page is shown. Assuming that Clublistview.ItemsSource is not null, then this code gets executed:
overlay.IsVisible = true;
Clublistview.IsVisible = false;
This means that your overlay is visible and the ActivityIndicator will be spinning. If this is not in OnAppearing then I am not sure when you are calling the method it is in.
You might want to do something like this instead:
public override async void OnAppearing()
{
base.OnAppearing();
// Show your overlay
overlay.IsVisible = true;
Clublistview.IsVisible = false;
// Load the items into the ItemsSource
await setClubs(Clublistview);
// Hide the overlay
overlay.IsVisible = false;
Clublistview.IsVisible = true;
}
You can achieve this type of behavior in a cleaner way with the MVVM pattern. With MVVM you can use a property binding to control when the overlay is shown. We have some guides on MVVM and Xamarin.Forms that can help get you started here. Here is a blog post that shows an example too.
This is what worked for me:
protected override void OnAppearing()
{
base.OnAppearing();
this.layoutLoadingSpinner.IsVisible = true;
this.layoutContent.IsVisible = false;
// Load slow-loading model on a separate thread
MySlowLoadingModel model = null;
await Task.Run(() =>
{
model = new MySlowLoadingModel();
});
this.BindingContext = model;
this.layoutLoadingSpinner.IsVisible = false;
this.layoutContent.IsVisible = true;
}
(Another option that avoids async/await is to call MainThread.BeginInvokeOnMainThread() inside the Task.Run)
An unfortunate side-effect is that any code run on the second thread becomes very difficult to debug. It seems the Xamarin debugger doesn't work right with multiple threads.

Xamarin Listview within Stacklayout displays incorrectly on iOS

I have the following xaml
<StackLayout Orientation="Vertical" VerticalOptions="EndAndExpand" HorizontalOptions="FillAndExpand" Margin="0,0,0,0">
<Label Text="Menu" />
<ListView x:Name="lvMenu" ItemSelected="lvMenu_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding ImageSource}" Text="{Binding TitleText}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The following c# populates it
private void PopulateMenu()
{
var menu = new List<SettingItem>()
{
new SettingItem() { ImageSource="icon.png", TitleText="Show All Jobs", TargetPageType = typeof(JobListPage) },
new SettingItem() { ImageSource="opportunities.png", TitleText="Sync Tasks", TargetPageType = typeof(SyncPage) },
new SettingItem() { ImageSource="leads.png", TitleText="Advanced Settings", TargetPageType = typeof(SettingsPage) },
new SettingItem() { ImageSource="contacts.png", TitleText="About ", TargetPageType = typeof(AboutPage) },
};
lvMenu.ItemsSource = menu;
}
Its all good but when it displays I get
Menu
Show All Jobs
Sync Tasks
Advanced Settings
and 7 blank lines on an iOS emulator
The default behaviour on iOS for a ListView is to add the empty rows.
To overcome this, you need to wrap your ListView in a StackPanel,
Or Create a custom Rendered:
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (this.Control == null) return;
this.Control.TableFooterView = new UIView();
}
Source : Xamarin

Categories

Resources