Multiple TabBars in single application Xamarin - xamarin

How to use multiple TabBars (Bottom) using Shell in single application so that different page have different TabBar. Could i use multiple Shell or is it possible with single shell.

You can use one Shell application , in this example Home has 3 tabs. The Settings icon a TabbedPage with 3 tabs.
Shell.xaml
<Shell.FlyoutFooterTemplate>
<DataTemplate>
<Grid RowDefinitions="30" ColumnDefinitions="150, 150">
<Image
Grid.Row="0"
Grid.Column="0"
Source="Settings.png"
HorizontalOptions="StartAndExpand"
Margin="50,0,0,0"
>
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="TapGestureRecognizer_Tapped"
NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
<Image
Grid.Row="0"
Grid.Column="1"
Source="Power.png"
HorizontalOptions="EndAndExpand"
Margin="0,0,30,0">
<Image.GestureRecognizers>
<TapGestureRecognizer
Tapped="TapGestureRecognizer_Tapped_1"
NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</Grid>
</DataTemplate>
</Shell.FlyoutFooterTemplate>
Shell.xaml.cs
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(ItemDetailPage), typeof(ItemDetailPage));
Routing.RegisterRoute(nameof(NewItemPage), typeof(NewItemPage));
Routing.RegisterRoute(nameof(StartPage), typeof(StartPage));
Routing.RegisterRoute(nameof(HomePage), typeof(HomePage));
}
private async void OnMenuItemClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("ItemDetailPage");
Shell.Current.FlyoutIsPresented = false;
}
private async void MenuItem_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//LoginPage");
}
private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage));
await Shell.Current.GoToAsync("SettingsPage");
Shell.Current.FlyoutIsPresented = false;
}
private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
{
/// do stuff what you want
}
}

A easy way is to conbine the FlyoutPage and TabbedPage.
You could create a FlyoutPage. It would generate FlyoutPage1, FlyoutPage1Detail, FlyoutPage1Flyout and FlyoutPage1FlyoutMenuItem. You could create two or more TabbedPages and make the changes like below.
FlyoutPage1FlyoutMenuItem:
public class FlyoutPage1FlyoutMenuItem
{
public int Id { get; set; }
public string Title { get; set; }
public string PageName { get; set; }
}
FlyoutPage1Flyout.xaml.cs:
private class FlyoutPage1FlyoutViewModel : INotifyPropertyChanged
{
public ObservableCollection<FlyoutPage1FlyoutMenuItem> MenuItems { get; set; }
public FlyoutPage1FlyoutViewModel()
{
MenuItems = new ObservableCollection<FlyoutPage1FlyoutMenuItem>(new[]
{
new FlyoutPage1FlyoutMenuItem { Id = 0, Title = "MainPage", PageName="MainPage"},
new FlyoutPage1FlyoutMenuItem { Id = 1, Title = "TabbedPage1", PageName="TabbedPage1" },
new FlyoutPage1FlyoutMenuItem { Id = 2, Title = "TabbedPage2", PageName="TabbedPage2" },
});
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
FlyoutPage1.xaml.cs:
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as FlyoutPage1FlyoutMenuItem;
if (item == null)
return;
var pageType = Type.GetType($"App1.{item.PageName}");
var page = (Page)Activator.CreateInstance(pageType);
page.Title = item.Title;
Detail = new NavigationPage(page);
IsPresented = false;
FlyoutPage.ListView.SelectedItem = null;
}

Related

Xamarin Forms CollectionView is empty

I have a Xamarin Forms CollectionView, contained within a RefreshView. The binding source is populated but its not showing anything in the CollectionView. I know the binding source is populated as I show a count in the toolbar. Can anyone spot why the list is empty?
Here is my Content Page with my RefreshView and CollectionView:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddDogCommand}" />
<ToolbarItem Text="{Binding Dogs.Count}"></ToolbarItem>
</ContentPage.ToolbarItems>
<RefreshView x:DataType="local:MyDogsViewModel" Command="{Binding LoadDogsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView ItemsLayout="HorizontalList" ItemsSource="{Binding Dogs}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid WidthRequest="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="1"
Text="{Binding DogName}"
FontAttributes="Bold" TextColor="Red"/>
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Nickname}"
FontAttributes="Italic" TextColor="Green"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
Here is my ViewModel
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
public ObservableCollection<DogModel> Dogs { get; }
public Command LoadDogsCommand { get; }
public Command<DogModel> ItemTapped { get; }
public Command AddDogCommand { get; }
public Command SaveDogCommand { get; }
public string DogName
{
get => _dogName;
set => SetProperty(ref _dogName, value);
}
public string Nickname
{
get => _nickname;
set => SetProperty(ref _nickname, value);
}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
AddDogModel model = new AddDogModel
{
DogName = DogName,
Nickname = Nickname
};
await dogService.AddDog(model);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
DogModel class
public class DogModel
{
public int Id { get; set; }
public string DogName { get; set; }
public string Nickname { get; set; }
}
keep your xaml the same and i will edit your ViewModel and take out the properties that is causing you the issue ;
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
private ObservableCollection<DogModel> dogs;
public ObservableCollection<DogModel> Dogs
{
get{return dogs;}
set{dogs=value;}
}
public Command LoadDogsCommand { get; set; }
public Command<DogModel> ItemTapped { get; set;}
public Command AddDogCommand { get; set;}
public Command SaveDogCommand { get; set;}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command<object>(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
DogModel newdog = obj as DogModel;
await dogService.AddDog(newdog);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
dont forget to bind to the viewmodel. in the xaml.cs add this in the constuctor.
this.BindingContext = new MyDogsViewModel();

CollectionView dose not show content, when filled in onappearing

I have a collectionview that is bound to an ObservableRangeCollectionin my ViewModel.
In my ViewModel there is a Method that runs onAppearing and I want my ColletionViewto be filled from there, but when I do so the collectionveiw dose not display the content only when i reload the content is shown.
View:
<RefreshView Grid.Row="1"
Grid.RowSpan="2"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<RefreshView.RefreshColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White"/>
</OnPlatform>
</RefreshView.RefreshColor>
<CollectionView x:Name="Collection"
ItemsSource="{Binding Locations, Mode=OneWay}"
ItemTemplate="{StaticResource ListDataTemplate}"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
RemainingItemsThreshold="10"
SelectionMode="Single"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding SelectedCommand}">
<CollectionView.EmptyView>
<StackLayout Padding="12">
<Label HorizontalOptions="Center" Text="Keine Daten vorhanden!" TextColor="White"/>
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
</RefreshView>
ViewModel:
namespace YourPartys.ViewModels
{
public class ListViewModel : ViewModelBase
{
#region Variables
#endregion
#region Propertys
LocationModel selectedItem;
public LocationModel SelectedItem
{
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
public ObservableRangeCollection<LocationModel> Locations { get;set; } = new ObservableRangeCollection<LocationModel>();
double distance;
public double Distance
{
get => distance;
set => SetProperty(ref distance, value);
}
#endregion
#region Commands
public ICommand FilterButtonCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SelectedCommand { get; }
public ICommand LoadMoreCommand { get; }
#endregion
//Constructor
public ListViewModel()
{
FilterButtonCommand = new Command(OpenFilter);
RefreshCommand = new AsyncCommand(Refresh);
SelectedCommand = new AsyncCommand(Select);
}
public override async void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
}
private void OpenFilter(object obj)
{
PopupNavigation.Instance.PushAsync(new ListFilterPage());
}
private async Task Refresh()
{
IsBusy = true;
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
IsBusy = false;
}
private async Task Select()
{
if (SelectedItem == null)
return;
var route = $"{nameof(DetailPage)}?Locationid={SelectedItem.Locationid}";
SelectedItem = null;
await AppShell.Current.GoToAsync(route);
}
}
}
There are several problems in your demo.
1.Since you set the BindingContext for your page in xaml as follows:
<ContentPage.BindingContext>
<viewmodels:MainViewModel/>
</ContentPage.BindingContext>
you didn't need to recreate another object MainViewModel in a CS file and reference it. These are two different objects.
MainViewModel viewModel;
viewModel = new MainViewModel();
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.VModelActive(this, EventArgs.Empty);
}
So, you can get the BindingContext in MainPage.xaml.cs in function OnAppearing as follows:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
The whole code is
public partial class MainPage : ContentPage
{
MainViewModel viewModel;
public MainPage()
{
InitializeComponent();
// viewModel = new MainViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
}
2.when we set the text color of the Label to White,this makes it hard to see the text,so you can reset it to another color,for example Black:
<Label Text="{Binding Name}"
FontSize="30"
TextColor="White"/>

Pushing ImageButton makes the image very small [Xamarin]

Please check the GIF for the problem.
I am actually using two imagebutton here and change IsVisible, since I couldn't accomplish swapping the image by Binding on the source.
ViewModel:
public bool IsAudioPlaying
{
get => player.IsPlaying;
}
...
public void PlayOrPause()
{
if (player.IsPlaying)
player.Pause();
else
player.Play();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsAudioPlaying"));
}
XAML:
<ImageButton AbsoluteLayout.LayoutBounds=".5, 0, 100, 100" AbsoluteLayout.LayoutFlags="PositionProportional" Source="play.png" Padding="20" WidthRequest="80" HeightRequest="80"
CornerRadius="40" VerticalOptions="Center" HorizontalOptions ="Center" BackgroundColor="#cea448" Clicked="PlayOrPause" Margin="10" IsVisible="{Binding IsAudioPlaying, Converter={StaticResource InverseBoolConverter}}" />
<ImageButton AbsoluteLayout.LayoutBounds=".5, 0, 100, 100" AbsoluteLayout.LayoutFlags="PositionProportional" Source="pause.png" Padding="20" WidthRequest="80" HeightRequest="80"
CornerRadius="40" VerticalOptions="Center" HorizontalOptions ="Center" BackgroundColor="#cea448" Clicked="PlayOrPause" Margin="10" IsVisible="{Binding IsAudioPlaying}" />
I am actually using two imagebutton here and change IsVisible, since I couldn't accomplish swapping the image by Binding on the source.
Create a View Model.
public class ViewModel : INotifyPropertyChanged
{
private bool _isAudioPlaying;
public bool IsAudioPlaying
{
get
{
return _isAudioPlaying;
}
set
{
_isAudioPlaying = value;
OnPropertyChanged("IsAudioPlaying");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Use ObservableCollection to update.
ObservableCollection<ViewModel> observableCollection { get; set; }
public MainPage()
{
InitializeComponent();
observableCollection = new ObservableCollection<ViewModel>()
{
new ViewModel(){ IsAudioPlaying=true}
};
this.BindingContext = observableCollection;
}
private void PlayOrPause(object sender, EventArgs e)
{
if (observableCollection[0].IsAudioPlaying == true)
{
observableCollection[0].IsAudioPlaying = false;
imageButton.Source = "pause.png";
}
else
{
observableCollection[0].IsAudioPlaying = true;
imageButton.Source = "play.png";
}
}

Xamarin Forms Behaviors - interaction with external class

I have created a ViewModel related to a custom Xamarin Forms Map
Over this Map, I am displaying an Entry field, on a trigger to be visible or invisible according to conditions
I have successfully bound the Entry IsVisible parameter to the behavior, and can toggle it on a button
However, what I actually want is to achieve this in response to the click on a Pin
When I attempt this, i cannot get the OnPropertyChange to fire
I'm not sure how to overcome this - the pin is in a separate class so I cannot bind it to the Grid view that contains the text entry
I've attached a click event to the custom pin and used it to interact with the view model
pin.Clicked += (object sender, EventArgs e) =>
{
var p = sender as CustomPin;
var callingMethod = new HandleEditor();
callingMethod.Pin_Clicked(p);
};
This is the XAML
<Grid x:Name="LayoutGrid">
<Grid.BindingContext>
<local:HandleEditor />
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="150" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:CustomMap Grid.RowSpan="3" Grid.Row="0" MapType="Street" WidthRequest="300" HeightRequest="300" />
<Editor x:Name="TextEntry" Grid.Row="1" VerticalOptions="FillAndExpand" IsVisible="{Binding SwitchVisible, Mode=TwoWay}"/>
<Button Grid.Row="2" Text="Switch Bool Test" Command="{Binding ChangeBoolValue}"/>
</Grid>
This is the ViewModel
public event PropertyChangedEventHandler PropertyChanged;
bool isEditorVisible = false;
public ICommand ChangeBoolValue { protected set; get; }
public ICommand Debug { protected set; get; }
public bool SwitchVisible
{
get
{
System.Diagnostics.Debug.WriteLine("Debug: SwitchVisible has been fired " + isEditorVisible);
return isEditorVisible;
}
}
public HandleEditor()
{
ChangeBoolValue = new Command(() =>
{
if (isEditorVisible == true)
{
isEditorVisible = false;
OnPropertyChanged("SwitchVisible");
}
else
{
isEditorVisible = true;
OnPropertyChanged("SwitchVisible");
}
});
}
protected virtual void OnPropertyChanged(string propertyName)
{
System.Diagnostics.Debug.WriteLine("Debug: OnPropertyChanged has been fired " + propertyName + " : " + isEditorVisible);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Pin_Clicked(CustomPin pin)
{
isEditorVisible = true;
OnPropertyChanged("SwitchVisible");
}
So I might miss something but I don't see the purpose of the isEditorVisible property.
I have successfully bound the Entry IsVisible parameter to the
behavior, and can toggle it on a button
You are using an Editor and not an Entry. Plus, you are binding the Command property of the button to a Command in your ViewModelwhich is ChangeBoolValue. You are not using a Behavior<T>, or you missed it in your snipet :).
<Editor x:Name="TextEntry" Grid.Row="1" VerticalOptions="FillAndExpand" IsVisible="{Binding SwitchVisible, Mode=TwoWay}"/>
So what you want to achieve is the Editor IsVisibleProperty to change according to SwitchIsVisible property, which one you are binding to, right ?
I would do something like that in the ViewModel instead of your actual implementation :
public event PropertyChangedEventHandler PropertyChanged;
public ICommand ChangeBoolValue { protected set; get; }
public ICommand Debug { protected set; get; }
private bool _switchVisible;
public bool SwitchVisible
{
get => _switchVisible;
set
{
if(_switchVisible == value) return;
_switchVisible = value;
OnPropertyChanged("SwitchVisible");
}
}
public HandleEditor()
{
ChangeBoolValue = new Command(() =>
{
SwitchVisible = !SwitchVisible;
});
}
protected virtual void OnPropertyChanged(string propertyName)
{
System.Diagnostics.Debug.WriteLine("Debug: OnPropertyChanged has been fired " + propertyName + " : " + isEditorVisible);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Pin_Clicked(CustomPin pin)
{
SwitchVisible = true;
}

Update list view on expand

I am coding a Xamarin.Forms project and I have a list view but whenever I show hidden content, for example, make an entry visible it the ViewCell overlaps the one beneath it.
Is there a way I could .Update() the listview or something to refresh it and make them all fit.
I don't want the refresh to cause it to go back to the top though.
Android seems to be able to automatically update the height when I show something.
I tried using HasUnevenRows="True" but that still didn't fix it.
Code:
Message.xaml
<StackLayout>
<local:PostListView x:Name="MessageView" HasUnevenRows="True" IsPullToRefreshEnabled="True" Refreshing="MessageView_Refreshing" SeparatorVisibility="None" BackgroundColor="#54a0ff">
<local:PostListView.ItemTemplate>
<DataTemplate>
<local:PostViewCell>
<StackLayout>
<Frame CornerRadius="10" Padding="0" Margin="10, 10, 10, 5" BackgroundColor="White">
<StackLayout>
<StackLayout x:Name="MessageLayout" BackgroundColor="Transparent" Padding="10, 10, 15, 10">
...
<Label Text="{Binding PostReply}" FontSize="15" TextColor="Black" Margin="10, 0, 0, 10" IsVisible="{Binding ShowReply}"/>
<StackLayout Orientation="Vertical" IsVisible="{Binding ShowReplyField}" Spacing="0">
<Entry Text="{Binding ReplyText}" Placeholder="Reply..." HorizontalOptions="FillAndExpand" Margin="0, 0, 0, 5"/>
...
</StackLayout>
<StackLayout x:Name="MessageFooter" Orientation="Horizontal" IsVisible="{Binding ShowBanners}">
<StackLayout Orientation="Horizontal">
...
<Image x:Name="ReplyIcon" Source="reply_icon.png" HeightRequest="20" HorizontalOptions="StartAndExpand" IsVisible="{Binding ShowReplyButton}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ReplyClick}" CommandParameter="{Binding .}"/>
</Image.GestureRecognizers>
</Image>
...
</StackLayout>
...
</StackLayout>
</StackLayout>
</StackLayout>
</Frame>
</StackLayout>
</local:PostViewCell>
</DataTemplate>
</local:PostListView.ItemTemplate>
</local:PostListView>
</StackLayout>
Message.cs
using Newtonsoft.Json;
using SocialNetwork.Classes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace SocialNetwork
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessagePage : ContentPage
{
public MessagePage()
{
InitializeComponent();
LoadPage();
}
private async void LoadPage()
{
await LoadMessages();
}
private async void RefreshPage()
{
await LoadMessages();
MessageView.EndRefresh();
}
private async Task LoadMessages()
{
//*Web Request*
MessageView.ItemsSource = FormatPosts(this, Navigation, page_result);
...
}
public IList<MessageObject> FormatPosts(Page page, INavigation navigation, string json)
{
IList<MessageObject> Posts = new List<MessageObject>() { };
var messages = JsonConvert.DeserializeObject<List<Message>>(json);
foreach (var message in messages)
{
MessageObject mo = MessageObject.CreateMessage(...);
Posts.Add(mo);
}
return Posts;
}
public async void ShowOptionActions(string id, string poster_id, object message)
{
...
}
public async void ShowReportOptions(string id, string poster_id)
{
...
}
public void SubmitReplyClick(string id, object msg)
{
...
}
public async void SendReplyAsync(string id, object msg, string reply)
{
await SendReply(id, msg, reply);
}
public void ReplyCommandClick(string id, object msg)
{
MessageObject message = (MessageObject) msg;
message.ShowReplyField = message.ShowReplyField ? false : true;
//Update Cell Bounds
}
private async Task SendReply(string id, object msg, string reply)
{
MessageObject message = (MessageObject)msg;
...
message.PostReply = reply;
//Update Cell Bounds
}
public async void LikeMessageClick(string id, object message)
{
await LikeMessage(id, message);
}
private async Task LikeMessage(string id, object msg)
{
...
}
public async void DeleteMessage(string id, object msg)
{
MessageObject message = (MessageObject)msg;
message.ShowBanners = false;
message.ShowReply = false;
...
//Update Cell Bounds
}
public async Task ReportMessage(...)
{
...
}
private void MessageView_Refreshing(object sender, EventArgs e)
{
RefreshPage();
}
}
public class MessageObject : INotifyPropertyChanged
{
private Boolean showBannersValue = true;
private string replyValue = String.Empty;
private bool showReplyValue;
private bool showReplyButtonValue;
private bool showReplyFieldValue;
private Command replyCommandValue;
private Command replySubmitValue;
private string replyTextValue;
...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private MessageObject(...)
{
...
}
public static MessageObject CreateMessage(...)
{
return new MessageObject(...);
}
public Boolean ShowBanners
{
get
{
return this.showBannersValue;
}
set
{
if (value != this.showBannersValue)
{
this.showBannersValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyField
{
get
{
return this.showReplyFieldValue;
}
set
{
if(value != this.showReplyFieldValue)
{
this.showReplyFieldValue = value;
NotifyPropertyChanged();
}
}
}
public string PostReply
{
get
{
return this.replyValue;
}
set
{
if (value != this.replyValue)
{
this.replyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReply
{
get
{
return this.showReplyValue;
}
set
{
if(value != this.showReplyValue)
{
this.showReplyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyButton
{
get
{
return this.showReplyButtonValue;
}
set
{
if (value != this.showReplyButtonValue)
{
this.showReplyButtonValue = value;
NotifyPropertyChanged();
}
}
}
public string ReplyText
{
get
{
return this.replyTextValue;
}
set
{
if(value != this.replyTextValue)
{
this.replyTextValue = value;
NotifyPropertyChanged();
}
}
}
public Command ReplyClick
{
get
{
return this.replyCommandValue;
}
set
{
if (value != this.replyCommandValue)
{
this.replyCommandValue = value;
NotifyPropertyChanged();
}
}
}
...
}
}
Save your IList<MessageObject> which gets returned from your FormatPosts method in a field IList<MessageObject> _messages = new List<MessageObject>()
And use the following snippet to update the ListView whenever you need, includes a check to see if the device runs on iOS:
if(Device.RuntimePlatform == Device.iOS)
{
MessageView.ItemsSource = null;
MessageView.ItemsSource = _messages;
}
Especially with iOS there are issues resizing rows in a ListView according to changes of cells (see here). There is a method ForceUpdateSize on Cell, which should notify the ListView that the size of the cell has changed, which should cause the ListView to resize its rows.
Oh, I faced the same thing.
I guess that you just need to add this somewhere in your listview:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.row='0'>
...
</Grid>
// This, in my case, makes my cell expand when it's true. Normal behavior
<Grid Grid.row='1' isVisible="{Binding Expand}">
...
</Grid>
</Grid>
Plus, if you want to update cells individually, I use a CustomObservableCollection:
public class CustomObservableCollection<T> : ObservableCollection<T>
{
public CustomObservableCollection() { }
public CustomObservableCollection(IEnumerable<T> items) : this()
{
foreach(var item in items)
this.Add(item);
}
public void ReportItemChange(T item)
{
NotifyCollectionChangedEventArgs args =
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
item,
item,
IndexOf(item));
OnCollectionChanged(args);
}
}
With a Custom ListView to do ItemClickCommand:
public class CustomListView : ListView
{
#pragma warning disable 618
public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<CustomListView, ICommand>(x => x.ItemClickCommand, null);
#pragma warning restore 618
public CustomListView(ListViewCachingStrategy cachingStrategy = ListViewCachingStrategy.RetainElement) :
base(cachingStrategy)
{
this.ItemTapped += this.OnItemTapped;
}
public ICommand ItemClickCommand
{
get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
set { this.SetValue(ItemClickCommandProperty, value); }
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if(e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e.Item))
{
this.ItemClickCommand.Execute(e.Item);
this.SelectedItem = null;
}
}
}
then in xaml:
...
...
<Customs:CustomListView
HasUnevenRows="true"
ItemsSource="{Binding PersonList}"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding DoRefreshCommand}"
ItemClickCommand="{Binding ItemClickCommand}">
...
...
</Customs:CustomListView>
Finally:
public Command<Person> ItemClickCommand { get; set; }
...
ItemClickCommand = new Command<Person>(SelectionExecute);
...
private void SelectionExecute(Person arg)
{
arg.Expand = !arg.Expand;
foreach(var item in PersonList)
{
if(item.Key == arg.Id)// you will change this probably
item.ReportItemChange(arg);
}
}
Hope it help a bit :)

Resources