Xamarin - Bind ActivityIndicator visibility to async Task finished - xamarin

I'm loading images async into my Listview according to this answer: Xamarin - Asynchronous data binding
I'd like to display a ActivityIndicator while the page is loading the data from the web, but cannot figure out how to bind the visibility it to the task, so that it disappears when the task is done loading..
I've created a isLoading bool variable in my ViewModel.
I tried it this way in my ViewModel, but It's not working:
isLoading = true;
GetImagesCommand = new Command(async () => await DoGetImagesCommand(pCode, gCode, gUrl));
isLoading = false;
I also tried setting it in the Task, but isLoading is always false..
private async Task DoGetImagesCommand(string pCode, string gCode, string gUrl)
{
isLoading = true;
var images = await _galleryService.GetImageList(pCode, gCode, gUrl);
foreach (var image in images)
Galleries.Add(image);
isLoading = false;
}
my 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:vm="clr-namespace:GalShare.ViewModel"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms" xmlns:forms="clr-namespace:RedCorners.Forms;assembly=RedCorners.Forms"
mc:Ignorable="d"
x:Class="GalShare.Views.Gallery">
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1">
<CollectionView ItemsSource="{Binding Galleries}" x:Name="MyCollection" SelectionMode="Single" SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView Padding="1">
<forms:Frame2 CornerRadius="15"
HasShadow="True"
ShadowRadius="8"
Padding="0">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="LightGray" Padding="2">
<ffimageloading:CachedImage x:Name="myImage" Source="{Binding ThumbUrl}" Aspect="AspectFit" CacheDuration="0" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" DownsampleToViewSize="False"></ffimageloading:CachedImage>
<Label x:Name="Picname" Text="{Binding ImageName}" IsVisible="{Binding showName}" VerticalOptions="StartAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" ></Label>
</StackLayout>
</forms:Frame2>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<StackLayout IsVisible="{Binding isLoading}" x:Name="LoadingLayout" Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator />
<Label Text="Loading gallery..." HorizontalOptions="Center" TextColor="Black"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
my ViewModel:
namespace GalShare.ViewModel
{
class GalleryViewModel
{
public string pCode { get; set; }
public string gCode { get; set; }
public string gUrl { get; set; }
public bool isLoading { get; set; }
public bool showname { get; set; }
public ObservableCollection<picdata> Galleries { get; } = new ObservableCollection<picdata>();
public ICommand GetImagesCommand { get; }
private GalleryService _galleryService;
public GalleryViewModel(string pCode, string gCode, string gUrl, string showName)
{
this.pCode = pCode;
this.gCode = gCode;
this.gUrl = gUrl;
_galleryService = new GalleryService();
GetImagesCommand = new Command(async () => DoGetImagesCommand(pCode, gCode, gUrl, showName));
}
private async Task DoGetImagesCommand(string pCode, string gCode, string gUrl, string showName)
{
isLoading = true;
var images = await _galleryService.GetImageList(pCode, gCode, gUrl, showName);
foreach (var image in images)
Galleries.Add(image);
isLoading = false;
}
}
}
galleryservice:
class GalleryService
{
private HttpClient _httpClient;
public GalleryService()
{
_httpClient = new HttpClient();
}
public async Task<IEnumerable<picdata>> GetImageList(string pCode, string gCode, string gUrl, string showName)
{
var response = await _httpClient.GetAsync(gUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// var json = response.Content.GetStringAsync().ConfigureAwait(false);
var deserialized = JsonConvert.DeserializeObject<JsonTxt>(json);
bool shownametemp;
if (showName == "1")
{
shownametemp = true;
}
else
{
shownametemp = false;
}
var images = new List<picdata>();
foreach (var img in deserialized.Files)
{
images.Add(new picdata()
{
ImageName = img.file,
BaseUrl = deserialized.Settings.Path.ToString(),
ThumbUrl = deserialized.Settings.Path.ToString() + "thumbs/" + img.file,
showname = shownametemp
}) ;
}
return images;
}
return new picdata[0]; // return empty set
}
}

You sould use ActivityIndicator like that
<ActivityIndicator AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
Color="{StaticResource PrimaryColor}"
VerticalOptions="Center"
HorizontalOptions="Center" />
Use IsRunning property with IsBusy property
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
RaisePropertyChanged(() => IsBusy);
}
}
in viewmodel
class GalleryViewModel : ExtendedBindableObject
{
...
private async Task DoGetImagesCommand(string pCode, string gCode, string gUrl)
{
IsBusy = true;
var images = await _galleryService.GetImageList(pCode, gCode, gUrl);
foreach (var image in images)
Galleries.Add(image);
IsBusy = false;
}
}
in another class
public abstract class ExtendedBindableObject : BindableObject
{
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
var name = GetMemberInfo(property).Name;
OnPropertyChanged(name);
}
private MemberInfo GetMemberInfo(Expression expression)
{
MemberExpression operand;
LambdaExpression lambdaExpression = (LambdaExpression)expression;
if (lambdaExpression.Body is UnaryExpression)
{
UnaryExpression body = (UnaryExpression)lambdaExpression.Body;
operand = (MemberExpression)body.Operand;
}
else
{
operand = (MemberExpression)lambdaExpression.Body;
}
return operand.Member;
}
}

Related

Xamarin MVVM image viewer with zoom and pan

I have a Xamarin MVVM class that displays a full page image. The code is shown below. The single tap simply closes the view. My question is how to implement the pan or zoom. The only things that I can find are written for Xamarin Forms and I can't figure out how to adapt them to MVVM. Thanks.
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:ImageViewModel"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:BLE.Client.ViewModels;assembly=BLE.Client"
x:Class="BLE.Client.Pages.ImagePage" Title="View Image">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackLayout Grid.Row="0" Orientation="Horizontal" >
<Image x:Name="WaypointImage" Source="{Binding MyImage}" HorizontalOptions="FillAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}"/>
<PinchGestureRecognizer Command="{Binding PinchCommand}"/>
<PanGestureRecognizer Command="{Binding PanCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</Grid>
</views:MvxContentPage>
using System;
using System.Windows.Input;
using Xamarin.Forms;
using Acr.UserDialogs;
using MvvmCross;
using MvvmCross.ViewModels;
using MvvmCross.Navigation;
using Plugin.BLE.Abstractions.Contracts;
namespace BLE.Client.ViewModels
{
public class ImageViewModel : BaseViewModel
{
private ImageSource _myImage;
public ImageSource MyImage
{
get => _myImage;
set
{
_myImage = value;
RaisePropertyChanged(() => MyImage);
}
}
private readonly IUserDialogs _userDialogs;
private readonly IMvxNavigationService _navigation;
public String _waypoint;
public Waypoint waypoint;
public ICommand tapCommand;
public ICommand TapCommand {
get { return tapCommand; }
}
public ICommand pinchCommand;
public ICommand PinchCommand {
get { return pinchCommand; }
}
public ICommand panCommand;
public ICommand PanCommand {
get { return panCommand; }
}
public ImageViewModel(IAdapter adapter, IUserDialogs userDialogs) : base(adapter)
{
_userDialogs = userDialogs;
_navigation = Mvx.IoCProvider.Resolve<IMvxNavigationService>();
tapCommand = new Command (OnTapped);
pinchCommand = new Command (OnPinched);
panCommand = new Command (OnPan);
}
void OnTapped (object s) {
Console.WriteLine($"OnTapped: {s}");
_navigation.Close(this);
}
void OnPinched (object s) {
Console.WriteLine($"OnPinched: {s}");
}
void OnPan (object s) {
Console.WriteLine($"OnPan: {s}");
}
public override async void Prepare(MvxBundle parameters)
{
base.Prepare(parameters);
_waypoint = await GetWaypointFromBundleAsync(parameters);
string[] tags = _waypoint?.Split(' ');
char[] trimChars = { 'I', 'D', '=' };
string id = tags[0].TrimStart(trimChars);
int ID = System.Convert.ToInt32(id);
waypoint = await Database.GetWaypointAsync(ID);
MyImage = ImageSource.FromFile(waypoint.FileName);
Console.WriteLine($"prepare ID={waypoint.ID} {waypoint.FileName} ");
}
public override void ViewAppeared()
{
base.ViewAppeared();
if (_waypoint != null)
{
return;
}
_navigation.Close(this);
}
public override void ViewDisappearing()
{
base.ViewDisappearing();
}
public override void ViewDisappeared()
{
base.ViewDisappeared();
}
}
}
Update:
I found an even better solution to the one below:
My answer to a pinch and pan question
This is my previous answer that works to a point but I could not get the pinch and pan gestures to trigger:
I have found an MVVM solution that takes advantage of the bindable properties Image: TranslationX, TranslationY, and Scale. Here are the relevant code changes. I tested this by modifying MyImageTranslationX in the OnTapped method and saw that the image shifts to the right.
Now I just need to get the Image.GestureRecognizers (PinchGestureRecognizer and PanGestureRecognizer) working and then add code that I have seen elsewhere to smoothly pan and zoom.
<Image x:Name="WaypointImage"
Source="{Binding MyImage}"
Scale = "{Binding MyImageScale}"
TranslationX = "{Binding MyImageTranslationX}"
TranslationY = "{Binding MyImageTranslationY}"
HorizontalOptions="FillAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapCommand}"/>
<PinchGestureRecognizer Command="{Binding PinchCommand}"/>
<PanGestureRecognizer Command="{Binding PanCommand}"/>
</Image.GestureRecognizers>
</Image>
public double _myImageScale = 1.0;
public double MyImageScale
{
get => _myImageScale;
set
{
_myImageScale = value;
RaisePropertyChanged(() => MyImageScale);
}
}
public double _myImageTranslationX = 0;
public double MyImageTranslationX
{
get => _myImageTranslationX;
set
{
_myImageTranslationX = value;
RaisePropertyChanged(() => MyImageTranslationX);
}
}
public double _myImageTranslationY = 0;
public double MyImageTranslationY
{
get => _myImageTranslationY;
set
{
_myImageTranslationY = value;
RaisePropertyChanged(() => MyImageTranslationY);
}
}
private ImageSource _myImage;
public ImageSource MyImage
{
get => _myImage;
set
{
_myImage = value;
RaisePropertyChanged(() => MyImage);
}
}
void OnTapped (object s) {
Console.WriteLine($"OnTapped: {MyImage}");
MyImageTranslationX = MyImageTranslationX + 100;
// _navigation.Close(this);
}

Running a delayed (async) task in view-model causes view to not update

I have a profile view-model that in it's constructor calls the DB for the user that just logged. It works fine but if I delay it by 3s then I assume one of the threads continues and creates the view with nothing inside. I use a data template selector to construct the view and if I delay the task then the view will not render anything. Again, if I don't delay it works fine but I need to make sure that it will work if the server takes longer to respond and 3s is not that much in some cases
here is my view model
public class ProfileViewModel: BaseViewModel
{
private ObservableCollection<Stylist> users;
public ObservableCollection<Stylist> Users
{
get => users;
set
{
users = value;
OnPropertyChanged();
}
}
private Stylist user;
public Stylist User { get => user; set { user = value; OnPropertyChanged(); } }
private bool isStylist;
public AsyncCommand TestCommand { get; set; }
public ProfileViewModel()
{
IsBusy = true;
TestCommand = new AsyncCommand(Test);
Users = new ObservableCollection<Stylist>();
Task.Run(async () =>
{
int id = ((App)App.Current).ID;
if (!((App)App.Current).IsStylist)
{
var user = await DB.GetUser(id);
User = CopyUserToStylist(user);
}
else User = await DB.GetStylist(id);
isStylist = User.IsStylist;
await Task.Delay(3000); // without this line everything is ok
Users.Add(User);
OnPropertyChanged(nameof(Users));
IsBusy = false;
});
}
the view
<ContentPage.Resources>
<DataTemplate x:Key="UserTemplate">
<StackLayout>
<Label Text="user logged"/>
<Label Text="{Binding Name}"/>
</StackLayout>
</DataTemplate>
<DataTemplate x:Key="StylistTemplate">
<StackLayout>
<Label Text="stylist logged"/>
<Label Text="{Binding Name}"/>
<Label Text="{Binding Location}"/>
<Button Text="Test stylist"
Command="{Binding TestCommand}"/>
</StackLayout>
</DataTemplate>
<local:UserTypeSelector
x:Key="personDataTemplateSelector"
StylistTemplate="{StaticResource StylistTemplate}"
UserTemplate="{StaticResource UserTemplate}" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Margin="10"
BindableLayout.ItemTemplateSelector="{StaticResource personDataTemplateSelector}"
BindableLayout.ItemsSource="{Binding Users}">
<Label Text="FML"/>
<Label Text="Loading"
IsVisible="{Binding IsBusy}"/>
<Button Text="Test"
Command="{Binding TestCommand}"/>
</StackLayout>
</ContentPage.Content>
and the db calls
public static async Task<User> GetUser(int id)
{
await Init();
return await db.Table<User>().Where(x => x.id == id).FirstAsync();
}
public static async Task<Stylist> GetStylist(int id)
{
await Init();
return await db.Table<Stylist>().Where(x => x.id == id).FirstAsync();
}
if I don't delay it works fine but I need to make sure that it will work if the server takes longer to respond and 3s is not that much in some cases
If you use Await in Task, the code starting from Await will be in another thread, not in main thread, so the UI will be not updated.
You can use Device.BeginInvokeOnMainThread(Action) Method to update UI by changing data source.
I do one sample that you can take a look.
<StackLayout Margin="10" BindableLayout.ItemsSource="{Binding Users}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding name}" />
<Label Text="{Binding age}" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
public partial class Page52 : ContentPage
{
public Page52()
{
InitializeComponent();
this.BindingContext = new ProfileViewModel();
}
}
public class ProfileViewModel:ViewModelBase
{
public ObservableCollection<user1> Users { get; set; }
public ProfileViewModel()
{
Users = new ObservableCollection<user1>();
Task.Run(async()=> {
user1 user = new user1() { name = "cherry", age = 18 };
await Task.Delay(3000);
Device.BeginInvokeOnMainThread(()=> {
Users.Add(user);
});
});
}
}
public class user1
{
public string name { get; set; }
public int age { get; set; }
}

I have a picker that is in a collectionview but I'm unsure how to get the value

I have a collectionview which has a [Label(day of the week] - [checkbox] - [picker with times].
I want the user to be able to select a day and time, after that I want to then pass those values to my database. So far I am able to select the day and pass this value on. However I am struggling to refence the value in the picker. I think this is due to it being a list in my object. I have tried booking.Listtimes but that is just a list. I want the value selected.
My ViewModel:
public class RepeatMonthly
{
public string Day { get; set; }
public bool Selected { get; set; }
public List<WalkTimes> _ListTimes { get; set; }
}
private WalkTimes _selectedTimes;
public WalkTimes SelectedTimes
{
get
{
return _selectedTimes;
}
set
{
SetProperty(ref _selectedTimes, value);
}
}
public ObservableCollection<RepeatMonthly> DayList { get; set; }
public CreateWeeklyScheduleViewModel()
{
ListTimes = WalkTimesService.GetTimes().OrderBy(c => c.Key).ToList();
DayList = new ObservableCollection<RepeatMonthly>()
{
new RepeatMonthly(){Day="Every Monday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Tuesday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every WednesDay", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Thursday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Friday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Saturday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Sunday", Selected = false, _ListTimes = ListTimes}
};
source = new ObservableCollection<PetProfile>();
}
My Xaml:
<CollectionView x:Name="RepeatCollectionView" HorizontalOptions="Center" ItemsSource="{Binding DayList}" HeightRequest="350">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0" RowDefinitions="25, 20" ColumnDefinitions="*,*">
<Label Text="{Binding Day}" FontAttributes="Bold" FontSize="Medium"
Margin="5,0"
VerticalTextAlignment="Center" HorizontalTextAlignment="Start"/>
<CheckBox x:Name="SelectDayCheckBox" Grid.Row="0" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" BindingContext="{Binding .}" CheckedChanged="SelectDayCheckBox_CheckedChanged"/>
<Picker x:Name="SelectTimeOfWalkPicker" Title="--Select Walk Start Time--" Grid.Column="1" ItemsSource="{Binding _ListTimes}" ItemDisplayBinding="{Binding Value}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" SelectedItem="{Binding SelectedTimes}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
my .cs page:
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
foreach (RepeatMonthly booking in (BindingContext as CreateWeeklyScheduleViewModel).DayList)
{
if (booking.Selected)
{
await passtoDataBase(booking.Day, "where time variable goes");
}
}
await DisplayAlert("Success", "Booked Successfully", "OK");
await Shell.Current.GoToAsync($"//{nameof(MyBookingsPage)}");
}
properties of WalkTimes object:
public class WalkTimes
{
public int Key { get; set; }
public string Value { get; set; }
}

Xamarin Forms: How to Change the textcolor of Collectionview SelectedItem?

I have a CarouselPage having 5 children and every child has a horizontal collection view. When selecting an item in Collectionview or swiping the pages, I need to give a different text color and need to add an underline for the selected item. I have tried like below:
CarouselHomePage.cs
public partial class CarouselHomePage : CarouselPage
{
public List<Activity> activityList { get; set; }
public CarouselHomePage()
{
InitializeComponent();
activityList = new List<Activity>();
AddActivities();
MessagingCenter.Subscribe<App, string>((App)Xamarin.Forms.Application.Current, "child", (s, child) =>
{
CurrentPage = Children[Int32.Parse(child)];
});
}
private void AddActivities()
{
activityList.Add(new Activity() { Title = "PageNumber1" });
activityList.Add(new Activity() { Title = "PageNumber2" });
activityList.Add(new Activity() { Title = "PageNumber3" });
activityList.Add(new Activity() { Title = "PageNumber4" });
activityList.Add(new Activity() { Title = "PageNumber5" });
AddChild(activityList);
}
public void AddChild(List<Activity> activityList)
{
this.Children.Add(new PageNumber1(activityList));
this.Children.Add(new PageNumber2(activityList));
this.Children.Add(new PageNumber3(activityList));
this.Children.Add(new PageNumber4(activityList));
this.Children.Add(new PageNumber5(activityList));
}
}
Activity.cs
public class Activity
{
public string Title { get; set; }
public bool visibility { get; set; }
public bool Visibility
{
set
{
if (value != null)
{
visibility = value;
NotifyPropertyChanged();
}
}
get
{
return visibility;
}
}
private Color textColor;
public Color TextColor
{
set
{
if (value != null)
{
textColor = value;
NotifyPropertyChanged();
}
}
get
{
return textColor;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
PageNumber1.xaml
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<CollectionView
SelectionMode="Single"
x:Name="ActivityList"
Margin="5,10,5,10"
SelectionChanged="TagItemTapped"
ItemsLayout="HorizontalList">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout
Orientation="Vertical"
Margin="15">
<Label
TextColor="{Binding TextColor}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="{Binding Title}">
<Label.FontSize>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Phone>18</OnIdiom.Phone>
<OnIdiom.Tablet>27</OnIdiom.Tablet>
<OnIdiom.Desktop>18</OnIdiom.Desktop>
</OnIdiom>
</Label.FontSize>
</Label>
<BoxView
HeightRequest="2"
IsVisible="{Binding Visibility}"
BackgroundColor="{Binding TextColor}"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Start"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.HeightRequest>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Phone>30</OnIdiom.Phone>
<OnIdiom.Tablet>60</OnIdiom.Tablet>
<OnIdiom.Desktop>30</OnIdiom.Desktop>
</OnIdiom>
</CollectionView.HeightRequest>
</CollectionView>
<Label Text="Welcome to PageNumber1"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
PageNumber1.xaml.cs
public partial class PageNumber1 : ContentPage
{
public PageNumber1(List<Activity> activityList)
{
InitializeComponent();
if (activityList == null)
{
ActivityList.IsVisible = false;
}
else
{
for (int i = 0; i < activityList.Count; i++)
{
if (activityList[i].Title == "PageNumber1")
{
activityList[i].TextColor = Color.FromHex("#26b4d8");
activityList[i].Visibility = true;
}
else
{
activityList[i].TextColor = Color.Gray;
activityList[i].Visibility = false;
}
}
ActivityList.ItemsSource = activityList;
}
}
public void TagItemTapped(object sender, SelectionChangedEventArgs e)
{
var selectedItem = (e.CurrentSelection.FirstOrDefault() as Activity);
if (selectedItem != null)
{
string childnumber = "";
if (selectedItem.Title == "PageNumber1")
{
childnumber = "0";
}
else if (selectedItem.Title == "PageNumber2")
{
childnumber = "1";
}
else if (selectedItem.Title == "PageNumber3")
{
childnumber = "2";
}
else if (selectedItem.Title == "PageNumber4")
{
childnumber = "3";
}
else if (selectedItem.Title == "PageNumber5")
{
childnumber = "4";
}
MessagingCenter.Send<App, string>((App)Xamarin.Forms.Application.Current, "child", childnumber);
}
}
}
I have added the same code on all the other child pages with the corresponding title in the if statement. But the selected page title color is not working and underline is not showing.
Screenshot:
Also if I select the last item in the collectionview, I need to scroll the collection on the last child to the last item. For this I have used ScrollTo feature of Collectioview. But that is also not working.
protected override void OnAppearing()
{
ActivityList.ScrollTo(4);
}
The above code will work if I manually swipe the pages. When directly tap the collectionview item, the scrolling is not working.
I have uploaded a sample project here.
About underline not showing , the reason is HeightRequest of CollectionView setted too small with 30 .
Modify that to above 35 , it will show correcttly . Such as :
<CollectionView.HeightRequest>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Phone>40</OnIdiom.Phone>
<OnIdiom.Tablet>60</OnIdiom.Tablet>
<OnIdiom.Desktop>30</OnIdiom.Desktop>
</OnIdiom>
</CollectionView.HeightRequest>
The effect :
About selected problem , this is the sample project here .

Two search bar Xamarin Forms

I have a question here (I imagine it is for beginners: P).
I have a listview and a searchbar already working. I can make the right filter.
Then my question comes in, this listview has more than one column.
I can't think of a way to make 2 complementary filters.
For example:
Filter column 1 and then filter the result of that initial filter by column 2 (with another searchbar), that is, a filter on top of another filter.
My ListViewItem is like this with the filter:
C#
void InitList()
{
Items = new List<ListViewItem>
{
new ListViewItem { Name = "Guilherme", Bairro = "BOTAFOGO"},
new ListViewItem { Name = "João", Bairro = "FLAMENGO"},
new ListViewItem { Name = "Maria", Bairro = "CENTRO"}
}
}
void InitSearchBarBloco()
{
sb_search_bloco.TextChanged += (s, e) => FilterItem(sb_search_bloco.Text);
sb_search_bloco.SearchButtonPressed += (s, e) =>
FilterItem(sb_search_bloco.Text);
}
private void FilterItem(string filter)
{
exampleListView.BeginRefresh();
if (string.IsNullOrWhiteSpace(filter))
{
exampleListView.ItemsSource = Items;
}
else
{
exampleListView.ItemsSource = Items.Where(x => x.Name.ToLower().Contains(filter.ToLower()));
}
exampleListView.EndRefresh();
}
XAML
<SearchBar x:Name="sb_search_bloco" Placeholder="Nome..." />
<ListView x:Name="exampleListView" RowHeight="22" SelectedItem="{Binding Name}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<Grid>
<Label Text="{Binding Name}" LineBreakMode="TailTruncation" />
<Label Grid.Column="1" Text="{Binding Bairro}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
With this structure I can implement this ... "filtrate filter"?
thanks
I guess the below code should do fine for you. You just need to alter your linq code inside the FilterItem(string filter) method to achieve your requirement.
Note:
I have used OR condition inside the where clause to search if the enter text is available in both Name and Bairro. However, you can modify the condition as you require.
private void FilterItem(string filter)
{
exampleListView.BeginRefresh();
if (string.IsNullOrWhiteSpace(filter))
{
exampleListView.ItemsSource = Items;
}
else
{
//Alter the condition like below or based on requirement to achieve the desired result.
exampleListView.ItemsSource = Items.Where(x => x.Name.ToLower().Contains(filter.ToLower()) || x.Bairro.ToLower().Contains(filter.ToLower()));
}
exampleListView.EndRefresh();
}
I make a sample code for your referece.
xmal:
<StackLayout>
<StackLayout>
<SearchBar x:Name="sb_search_bloco" Placeholder="Nome..." TextChanged="sb_search_bloco_TextChanged"/>
<SearchBar x:Name="searchBar2" Margin="0,10" TextChanged="searchBar2_TextChanged" />
</StackLayout>
<ListView
x:Name="exampleListView"
RowHeight="22"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label LineBreakMode="TailTruncation" Text="{Binding Name}" />
<Label Grid.Column="1" Text="{Binding Bairro}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ListViewItem.cs
public class ListViewItem
{
public string Name { get; set; }
public string Bairro { get; set; }
}
MainPage.xml.cs
public partial class MainPage : ContentPage
{
public List<ListViewItem> Items { get; set; }
public MainPage()
{
InitializeComponent();
Items = new List<ListViewItem>
{
new ListViewItem { Name = "AAAA", Bairro = "BBCFS"},
new ListViewItem { Name = "ABBB", Bairro = "SSDCA"},
new ListViewItem { Name = "AAAA", Bairro = "AAAD"},
new ListViewItem { Name = "CCCC", Bairro = "SSS"},
new ListViewItem { Name = "DAAB", Bairro = "CCC"},
new ListViewItem { Name = "DDDC", Bairro = "QWDAS"}
};
this.BindingContext = this;
}
private void sb_search_bloco_TextChanged(object sender, TextChangedEventArgs e)
{
exampleListView.ItemsSource = FilterItem1(e.NewTextValue);
}
IEnumerable<ListViewItem> FilterItem1(string filter = null)
{
if (string.IsNullOrEmpty(filter))
return Items;
return Items.Where(p => p.Name.StartsWith(filter));
}
private void searchBar2_TextChanged(object sender, TextChangedEventArgs e)
{
exampleListView.ItemsSource = FilterItem2(e.NewTextValue);
}
IEnumerable<ListViewItem> FilterItem2(string filter = null)
{
if (string.IsNullOrEmpty(filter))
return Items;
return Items.Where(p => p.Bairro.StartsWith(filter));
}
}

Resources