Command Binding for removing item from list view in XAML - xaml

I am trying to bind a command to my button so it can delete item from my list view.
My xaml looks like this
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TradeMaker.Pages.AddItem"
Title="AddItem">
<Grid MaximumWidthRequest="800" ColumnDefinitions="*" RowDefinitions="auto,*, auto, auto" >
<SearchBar Grid.Column="0" Grid.Row="0" x:Name="searchBar"/>
<ListView x:Name="searchResult"
Grid.Row="1"
ZIndex="0"
BackgroundColor="AliceBlue"
Opacity="100"
ItemsSource="{Binding SelectedProducts}"
Header="{Binding .}"
SelectionMode="Single" >
<ListView.HeaderTemplate>
<DataTemplate>
<Grid ColumnDefinitions="410,130,130,130">
<Label Grid.Column="0" Text="Item" FontSize="14" HorizontalTextAlignment="Center" />
<Label Grid.Column="1" Text="Quantity" FontSize="14" HorizontalTextAlignment="Center"/>
<Label Grid.Column="2" Text="Total" FontSize="14" HorizontalTextAlignment="Center"/>
<Label Grid.Column="3" Text="Delete" FontSize="14" HorizontalTextAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid ColumnDefinitions="410, 130,130,130 ">
<Label Text="{Binding Title}" Grid.Column="3" FontAttributes="Bold" VerticalTextAlignment="Center" HorizontalTextAlignment="Start"/>
<Entry Text="{Binding Quantity}" Grid.Column="1" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
<Label Text="{Binding Total}" Grid.Column="2" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<Button Grid.Column="0" VerticalOptions="Center" HorizontalOptions="Center" BackgroundColor="LightGray"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView x:Name="resultCollection"
Grid.Row="1"
ZIndex="1"
BackgroundColor="AntiqueWhite"
Opacity="100"
ItemsSource="{Binding Products}"
SelectionMode="Single">
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell >
<VerticalStackLayout Margin="5" >
<Label Text="{Binding Title, StringFormat='Name: {0}'}" />
<Label Text="{Binding price}"/>
</VerticalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Grid.Row="2" x:Name="dataLabe" Text="{Binding SubTotal}"/>
<Button Grid.Row="3" x:Name="next" Command="{Binding DeleteSelected}" />
</Grid>
</ContentPage>
And the code behind file is this
ProductsManager productsManager { get; }= new ProductsManager();
public AddItem()
{
InitializeComponent();
BindingContext = productsManager;
}
and the view model called productsmanager is as follow
public class ProductsManager:INotifyPropertyChanged
{
private ObservableCollection<Product> products;
private ObservableCollection<Product> selectedProducts;
public ObservableCollection<Product> Products { get => products; set { products = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Products))); } }
public ObservableCollection<Product> SelectedProducts { get=>selectedProducts; set { selectedProducts = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedProducts))); } }
public ProductsManager()
{
products = new ObservableCollection<Product>();
selectedProducts = new ObservableCollection<Product>();
}
private string subTotal="0";
public string SubTotal { get => subTotal; }
public void GetTotal()
{
float x = 0;
if (selectedProducts.Count > 0)
{
foreach (var item in selectedProducts)
{
var z = float.Parse(item.Total, System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
x += z;
subTotal = x.ToString();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubTotal)));
}
}
}
public ICommand DeleteSelected{ get; set; }
private Product _selectedProduct;
public event PropertyChangedEventHandler PropertyChanged;
}
I can from above files add products to the resultCollection list view and from there select and add a product item in the searchResult list view. But I cannot figure out how to delete the product item from the searchResult list.

Related

Using DataTrigger to change colour of ListView depending on value returned in ItemSource

I have a XAML page with a listview. What I want to do is when the value of the first column "NumType" equals "S" the background colour for that row is set to a different colour.
I have been looking at using DataTriggers, but I'm not sure if this is the way to go.
Below is the code that I currently have.
?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MobileWarehouseXamarin.Controls"
xmlns:ef="clr-namespace:MobileWarehouseXamarin.Effects"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="MobileWarehouseXamarin.Controls.MW_AdjustmentsAwaiting_0"
xmlns:vm="clr-namespace:MobileWarehouseXamarin.ViewModels;assembly=MobileWarehouseXamarin"
x:Name="this"
RowSpacing="0"
ColumnSpacing="0"
Padding="5,0,5,0">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
<!--<ColumnDefinition Width="300"/>-->
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Text="Location" Style="{StaticResource KeyValueSmall_Key}" />
<Label Grid.Column="1" Text="Barcode" Style="{StaticResource KeyValueSmall_Key}" />
<Label Grid.Column="2" Text="User" Style="{StaticResource KeyValueSmall_Key}" />
<Label Grid.Column="3" Text="Date" Style="{StaticResource KeyValueSmall_Key}" />
<!--<TextBlock Grid.Column="4" Text="Reason" Style="{StaticResource TextBlockLabel}" />-->
</Grid>
<ListView Grid.Row="1" x:Name="gridViewAwaitingAdjustmentDetails" ItemsSource="{Binding AwaitingAdjustment}" SelectedItem="{Binding SelectedAdjustment, Mode=TwoWay}" >
<!--Style="{StaticResource ListViewItemHighlighted}">-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
<!--<ColumnDefinition Width="300" />-->
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding NumType}" Style="{StaticResource KeyValueSmall_Value}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="0" Text="{Binding LocationCode}" Style="{StaticResource KeyValueSmall_Value}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Text="{Binding Barcode}" Style="{StaticResource KeyValueSmall_Value}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="2" Text="{Binding UserName}" Style="{StaticResource KeyValueSmall_Value}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Text="{Binding PickingAdjustementDate}" Style="{StaticResource KeyValueSmall_Value}" Margin="0,0,0,0" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Any thoughts or suggestions would be very much appreciated
I have looked at using the DataTrigger, but not sure how to go about it or would I be better off looking at Template Selectors?
There are several ways to achieve this.
A simple method is to add a field to the item of your ListView and bind it to the BackgroundColor of the item, for example:
public Color BgColor { get; set; } = Color.Yellow;
I created a simple demo and achieved this function,you can refer to the following code:
Item.cs
public class Item: INotifyPropertyChanged
{
public Color BgColor { get; set; } = Color.Yellow;
private string _numType;
public string NumType
{
get => _numType;
set
{
SetProperty(ref _numType, value);
setBgColor();// set BgColor
}
}
private void setBgColor()
{
if (NumType != null && NumType.Equals("S")) {
BgColor = Color.Green;
}
}
public string LocationCode { get; set; }
public string Barcode { get; set; }
public string UserName { get; set; }
public string PickingAdjustementDate { get; set; }
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MyViewModel.cs
public class MyViewModel
{
public ObservableCollection<Item> Items { get; set; }
public MyViewModel() {
Items = new ObservableCollection<Item>();
Items.Add( new Item { NumType = "S" , LocationCode = "0001"});
Items.Add(new Item { NumType = "M", LocationCode = "0002" });
Items.Add(new Item { NumType = "L", LocationCode = "0003" });
}
}
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:xamlistviewapp131="clr-namespace:XamListViewApp131"
x:Class="XamListViewApp131.MainPage">
<ContentPage.BindingContext>
<xamlistviewapp131:MyViewModel></xamlistviewapp131:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<ListView Grid.Row="1" x:Name="gridViewAwaitingAdjustmentDetails" ItemsSource="{Binding Items}" >
<!--Style="{StaticResource ListViewItemHighlighted}">-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{Binding BgColor}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
<!--<ColumnDefinition Width="300" />-->
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding NumType}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="0" Text="{Binding LocationCode}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Text="{Binding Barcode}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="2" Text="{Binding UserName}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Text="{Binding PickingAdjustementDate}" Margin="0,0,0,0" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

Nested CollectionView null reference exception upon refreshing

On initial load of the page, everything is fine and displays all data correctly.
But when I update the page (either by switching page and switching back, or Pull to refresh) I get null reference exception. I don't know how to debug this or how to fix it.
About my page:
My page displays a list of Displays. Each Display has a list of videos and a list of images.
XAML (DisplaysPage.xaml):
<StackLayout>
<Button Text="+ Create new display" Command="{Binding UploadDisplayCommand}" CornerRadius="0" BackgroundColor="ForestGreen" FontAttributes="Bold" />
<Label Text="No displays found in database." TextColor="White" FontSize="20" HorizontalTextAlignment="Center" IsVisible="{Binding IsEmpty}" Padding="20" />
<RefreshView IsRefreshing="{Binding IsRefreshing}" RefreshColor="Cyan" Command="{Binding LoadDisplaysCommand}" Margin="0, 5, 0, 1">
<CollectionView ItemsSource="{Binding Displays}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Display">
<Grid Padding="5">
<Frame CornerRadius="5" Padding="10">
<StackLayout Spacing="10">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" FontAttributes="Bold" FontSize="23" TextColor="White" />
<StackLayout Padding="0, -10, 0, -10" HorizontalOptions="EndAndExpand">
<Switch IsToggled="{Binding IsOn}" Toggled="Switch_Toggled" OnColor="LightGreen" ThumbColor="White" />
</StackLayout>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Videos}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:Video">
<Grid Padding="0, 5">
<Frame CornerRadius="5" Padding="0" BackgroundColor="AliceBlue">
<Image Source="{Binding ThumbnailPath}" Aspect="AspectFill" />
</Frame>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Images}">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:Video">
<Grid Padding="0, 5">
<Frame CornerRadius="5" Padding="0" BackgroundColor="AliceBlue">
<Image Source="{Binding FilePath}" Aspect="AspectFill" />
</Frame>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<Label Text="{Binding Description}" FontSize="16" TextColor="White" />
<StackLayout Orientation="Horizontal">
<Label Text="Number of videos associated with the display: " FontSize="16" TextColor="White" />
<Label Text="{Binding Videos.Count}" FontSize="16" TextColor="White" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Number of images associated with the display: " FontSize="16" TextColor="White" />
<Label Text="{Binding Images.Count}" FontSize="16" TextColor="White" />
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</StackLayout>
ViewModel (DisplaysViewModel.cs)
[INotifyPropertyChanged]
public partial class DisplaysViewModel
{
private readonly Service<Display> displayService;
[ObservableProperty]
private ObservableCollection<Display> displays = new();
[ObservableProperty]
private bool isEmpty;
[ObservableProperty]
private bool isRefreshing;
public DisplaysViewModel(Service<Display> displayService)
{
this.displayService = displayService;
}
[ICommand]
internal async Task LoadDisplaysAsync()
{
#if ANDROID || IOS || tvOS || Tizen
UserDialogs.Instance.ShowLoading("Loading displays from the database...");
#endif
if (Displays.Count is not 0)
{
Displays.Clear();
}
try
{
await foreach (Display display in displayService.GetAllAsync().OrderBy(x => x.Id))
{
Displays.Add(display);
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
if (Displays.Count is 0)
{
IsEmpty = true;
}
else
{
IsEmpty = false;
}
IsRefreshing = false;
#if ANDROID || IOS || tvOS
UserDialogs.Instance.HideLoading();
#endif
}
}
[ICommand]
private async Task UploadDisplayAsync()
{
await Shell.Current.DisplayAlert("Create a new display", "Under construction!", "OK");
}
}
Edit:
From inspiration from Jason, I wrapped my code with try-catch and debugged it, and it showed the issue. It was in my code-behind DisplaysPage.xaml.cs and the null reference is on my call to GetByIdAsync method inside Switch_Toggled event:
public partial class DisplaysPage : ContentPage
{
private readonly Service<Display> displayService;
private readonly Service<Log> logService;
public DisplaysPage(DisplaysViewModel displaysViewModel, Service<Display> displayService, Service<Log> logService)
{
InitializeComponent();
this.displayService = displayService;
this.logService = logService;
BindingContext = displaysViewModel;
}
protected override async void OnAppearing()
{
await ((DisplaysViewModel)BindingContext).LoadDisplaysAsync();
}
private async void Switch_Toggled(object sender, ToggledEventArgs e)
{
Switch displaySwitch = sender as Switch;
Display selectedDisplay = displaySwitch.BindingContext as Display;
try
{
Display displayToUpdate = await displayService.GetByIdAsync(selectedDisplay.Id);
displayToUpdate.IsOn = displaySwitch.IsToggled;
string isOnString = displaySwitch.IsToggled ? "on" : "off";
await displayService.UpdateAsync(displayToUpdate);
await logService.CreateAsync(new Log()
{
Description = $"{JwtParser.ParseClaimsFromJwt(await SecureStorage.GetAsync("JWT")).Where(x => x.Type == ClaimTypes.Name).FirstOrDefault()?.Value} has {isOnString} the display.",
UserId = JwtParser.ParseClaimsFromJwt(await SecureStorage.GetAsync("JWT")).Where(x => x.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value
});
}
catch (Exception)
{
throw new Exception("null exception");
}
}
}
This sort of exception as shown in the post was found by wrapping code with try-catch and doing a null-check.

The property doesn't update on the UI consistently

I have the problem that one property of the selected item in collection view doesn't update consistently. I have INotifyPropertyChanged and all of that. The property that I am talking about is progress in CurrentEntry. As you can see after that I have Console.WriteLine with success string so I know for a fact that everything went as It should and I don't get any exceptions. I even check website that it updated there just to be sure. As I said it is very consistent, sometimes I can do the task 5 times successfully so that it updates and other 5 times it doesn't update.
I'm not sure what could be causing this. I tried putting the line where I update it in MainThread.BeginInvokeOnMainThread but this probably isn't relevant as I am updating property not a label or anything. This is MVVM not code behind.
async Task Info()
{
await Task.Run(async () =>
{
IsBusy = true;
try
{
if (currentEntry.progress < currentEntry.media.Episodes)
{
string token = Preferences.Get("token", "default_value");
var anilistGraphQlHttpClient =
new GraphQLHttpClient("https://graphql.anilist.co", new NewtonsoftJsonSerializer());
anilistGraphQlHttpClient.HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var IncreaseProgressEntry = new GraphQLRequest
{
Query = #"mutation IncreaseProgress($id: Int, $progress: Int) { SaveMediaListEntry(id: $id, progress: $progress) { id progress status } } ",
OperationName = "IncreaseProgress",
Variables = new
{
id = CurrentEntry.id,
progress = currentEntry.progress + 1
}
};
var graphQlResponseAnimeList =
await anilistGraphQlHttpClient.SendQueryAsync<Data>(IncreaseProgressEntry);
CurrentEntry.progress = currentEntry.progress + 1;
Console.WriteLine("success");
}
}
catch (HttpRequestException httpRequestException)
{
Console.WriteLine(httpRequestException);
MainThread.BeginInvokeOnMainThread(() =>
{
DependencyService.Get<Toast>().Show("No internet");
});
}
catch (GraphQLHttpRequestException e)
{
Console.WriteLine(e);
Preferences.Set("token", "default_value");
await Shell.Current.GoToAsync($"//LandingPage");
MainThread.BeginInvokeOnMainThread(() =>
{
DependencyService.Get<Toast>().Show("Token revoked or expired");
});
}
catch (Exception e)
{
Console.WriteLine(e);
MainThread.BeginInvokeOnMainThread(() =>
{
DependencyService.Get<Toast>().Show("Unexpected error happened");
});
}
finally
{
IsBusy = false;
}
});
}
CurrentEntry type model
public class ListEntry : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public int? id { get; set; }
public Media media { get; set; }
public int? _score;
public int? score
{
get => _score;
set
{
if (value == _score)
return;
_score = value;
OnPropertyChanged();
}
}
public int _Progress;
public int progress
{
get => _Progress;
set
{
if (value == _Progress)
return;
_Progress = value;
OnPropertyChanged();
}
}
public string status { get; set; }
}
My xaml:
<ContentPage
x:Class="OtakuApp.Pages.AnimeListPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Name="AnimeListPagee">
<RefreshView
x:Name="HomeRefreshView"
BackgroundColor="DodgerBlue"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy}"
RefreshColor="White">
<CollectionView
x:Name="CollectionView1"
BackgroundColor="Transparent"
IsGrouped="True"
ItemsSource="{Binding AnimeGroupObservable}"
SelectedItem="{Binding CurrentEntry}"
SelectionMode="Single"
VerticalOptions="Fill">
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="15" Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<StackLayout>
<Label
Margin="30,15,0,5"
FontAttributes="Bold"
FontSize="25">
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Name}" />
<Span Text=": " />
<Span Text="{Binding entryCount}" />
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame
Margin="15,0,15,0"
Padding="0"
BackgroundColor="Brown"
CornerRadius="5"
HeightRequest="150"
HorizontalOptions="Fill">
<Grid
Padding="0"
xct:TouchEffect.NativeAnimation="True"
BackgroundColor="Transparent"
ColumnSpacing="0"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Image
Grid.RowSpan="2"
Aspect="AspectFill"
Source="{Binding media.CoverImage.Large}" />
<Label
Grid.Row="0"
Grid.Column="1"
Margin="5"
FontSize="14"
LineBreakMode="TailTruncation"
MaxLines="3"
Text="{Binding media.Title.UserPreferred}"
TextColor="White" />
<Label
Grid.Row="1"
Grid.Column="1"
Margin="5"
FontSize="14"
HorizontalOptions="End"
TextColor="White"
VerticalOptions="End">
<Label.FormattedText>
<FormattedString>
<Span Text="Score: " />
<Span Text="{Binding score}" />
</FormattedString>
</Label.FormattedText>
</Label>
<StackLayout
Grid.RowSpan="2"
Grid.Column="2"
BackgroundColor="Transparent"
VerticalOptions="Center">
<Label
HorizontalOptions="Center"
Text="{Binding progress}"
TextColor="White" />
<BoxView
Margin="15,0,15,0"
HeightRequest="2"
HorizontalOptions="Center"
Color="White" />
<Label
HorizontalOptions="Center"
Text="{Binding media.Episodes}"
TextColor="White" />
</StackLayout>
<Frame
Grid.RowSpan="2"
Grid.Column="2"
Padding="0"
xct:TouchEffect.Command="{Binding BindingContext.InfoCommand, Source={x:Reference Name=AnimeListPagee}}"
xct:TouchEffect.NativeAnimation="True"
xct:TouchEffect.NativeAnimationColor="White"
BackgroundColor="Transparent"
CornerRadius="30"
HeightRequest="45"
VerticalOptions="Start">
<Label
FontSize="30"
HorizontalOptions="Center"
Text="+"
TextColor="White"
VerticalOptions="Center" />
</Frame>
<Frame
Grid.RowSpan="2"
Grid.Column="2"
Padding="0"
xct:TouchEffect.Command="{Binding BindingContext.InfoCommand2, Source={x:Reference Name=AnimeListPagee}}"
xct:TouchEffect.NativeAnimation="True"
xct:TouchEffect.NativeAnimationColor="White"
BackgroundColor="Transparent"
CornerRadius="30"
HeightRequest="45"
VerticalOptions="End">
<Label
FontSize="30"
HorizontalOptions="Center"
Text="-"
TextColor="White"
VerticalOptions="Center" />
</Frame>
</Grid>
</Frame>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>

Define listview in resource file which can be used in different places with same model

I have created the following ListView to display data
<dataControls:RadListView x:Name="ItemsListView"
ItemsSource="{Binding StudyResults,Mode=TwoWay}"
MinimumHeightRequest="70"
HeightRequest="{Binding Height}"
SelectedItem="{Binding SelectedItem,Mode=TwoWay}">
<dataControls:RadListView.ItemTemplate>
<DataTemplate>
<listView:ListViewTemplateCell>
<listView:ListViewTemplateCell.View>
<Grid Padding="2,2,2,5" HorizontalOptions="FillAndExpand">
<StackLayout Padding="5,1,1,5" Grid.Column="0">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<maxline:XfMaxLinesLabel MaxLines="2" Text="{Binding BriefTitle, Mode=TwoWay}" Style="{StaticResource ListViewLabelStyle}" TextColor="Black"/>
</Grid>
<StackLayout Padding="0,0,0,0" HorizontalOptions="Fill">
<BoxView Margin="0"
BackgroundColor="Gray"
HeightRequest=".25" />
<Label Text="{Binding ClosestFacility.Name, Mode=TwoWay}" Style="{StaticResource ListViewLabelStyle}"/>
<BoxView Margin="0"
BackgroundColor="Gray"
HeightRequest=".25" />
</StackLayout>
</StackLayout>
</Grid>
</listView:ListViewTemplateCell.View>
</listView:ListViewTemplateCell>
</DataTemplate>
</dataControls:RadListView.ItemTemplate>
</dataControls:RadListView>
I want to reuse this exact same ListView + markup in a other screens/view, just with a different ItemsSource it will be bound to same model. I need to use different item source in different screens.
Is there a better way to create some type of resource so I can reuse this?
As lvan's opinion, you can set DataTemplate in ContentPage.Resource or ResourceDictionary, Some code like this:
<ContentPage.Resources>
<DataTemplate x:Key="Radtemplate">
<listView:ListViewTemplateCell>
<listView:ListViewTemplateCell.View>
<Grid Padding="2,2,2,5" HorizontalOptions="FillAndExpand">
<StackLayout Padding="5,1,1,5" Grid.Column="0">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<maxline:XfMaxLinesLabel MaxLines="2" Text="{Binding BriefTitle, Mode=TwoWay}" Style="{StaticResource ListViewLabelStyle}" TextColor="Black"/>
</Grid>
<StackLayout Padding="0,0,0,0" HorizontalOptions="Fill">
<BoxView Margin="0"
BackgroundColor="Gray"
HeightRequest=".25" />
<Label Text="{Binding ClosestFacility.Name, Mode=TwoWay}" Style="{StaticResource ListViewLabelStyle}"/>
<BoxView Margin="0"
BackgroundColor="Gray"
HeightRequest=".25" />
</StackLayout>
</StackLayout>
</Grid>
</listView:ListViewTemplateCell.View>
</listView:ListViewTemplateCell>
</DataTemplate>
</ContentPage.Resources>
<StackLayout>
<!-- Place new controls here -->
<dataControls:RadListView x:Name="ItemsListView" ItemTemplate="{StaticResource Radtemplate}"
ItemsSource="{Binding StudyResults,Mode=TwoWay}"
MinimumHeightRequest="70"
HeightRequest="{Binding Height}"
SelectedItem="{Binding SelectedItem,Mode=TwoWay}">
</dataControls:RadListView>
</StackLayout>
About DateTemplate detailed info, you can take a look the following article:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/creating
Update:
Because RadListView is the third party control, I can not install it, so I use ListView as an example, it is the same, you can take a look how to use TapGestureRecognizer.
Please give the Page an x:name=listviewpage firstly, then
<ContentPage
x:Class="demo3.listviewsample.Page2"
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"
x:Name="listviewpage"
mc:Ignorable="d">
<ContentPage.Resources>
<DataTemplate x:Key="datatemplate1">
<ViewCell>
<StackLayout Margin="5" VerticalOptions="FillAndExpand">
<BoxView BackgroundColor="AliceBlue" HeightRequest="30" />
<Label Text="{Binding username}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.command, Source={x:Reference listviewpage}}" CommandParameter="{Binding Email}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<ListView
HasUnevenRows="True"
ItemTemplate="{StaticResource datatemplate1}"
ItemsSource="{Binding models}" />
</StackLayout>
</ContentPage.Content>
Or you can give ViewCell an x:Name viewcell1 firstly, then:
<ContentPage.Resources>
<DataTemplate x:Key="datatemplate1">
<ViewCell x:Name="viewcell">
<StackLayout Margin="5" VerticalOptions="FillAndExpand">
<BoxView BackgroundColor="AliceBlue" HeightRequest="30" />
<Label Text="{Binding username}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Parent.BindingContext.command, Source={x:Reference viewcell}}" CommandParameter="{Binding Email}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ContentPage.Resources>
public partial class Page2 : ContentPage
{
public ObservableCollection<model3> models { get; set; }
public RelayCommand1 command { get; set; }
public Page2()
{
InitializeComponent();
models = new ObservableCollection<model3>()
{
new model3(){username="cherry",Email="cherry#outlook.com"},
new model3(){username="barry",Email="barry#outlook.com"}
};
command = new RelayCommand1(obj => method1((string)obj));
this.BindingContext = this;
}
private void method1(string str)
{
Console.WriteLine("the email is {0}",str);
}
}
public class model3
{
public string username { get; set; }
public string Email { get; set; }
}
Here is the Command that inherit ICommand:
public class RelayCommand1 : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand1(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand1(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_execute(parameter);
}
}
When I tap label, it works fine.
You can set DataTemplate as resource, that would work.

Ui is not updating on realtime

I have a listview for displaying all the items that i getting from api.i have a live event which will modify one field inside the item of type observablecollection
<?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="Dyocense.Views.ManageJobs"
Title="All jobs">
<ContentPage.ToolbarItems>
<ToolbarItem Text="ADD" Clicked="AddJob"></ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<SearchBar x:Name="Search" SearchButtonPressed="SearchBar_SearchButtonPressed"></SearchBar>
<ListView x:Name="JobsListView"
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemAppearing="BrowseJobList_ItemAppearing"
IsPullToRefreshEnabled="true"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10">
<Frame HasShadow="True" >
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
FontSize="Medium"
FontAttributes="Bold"/>
<Label Text="Status"
Grid.Row="1" Grid.Column="0"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding Status}"
Grid.Row="1" Grid.Column="1"
FontSize="16"/>
<Label Text="Goal"
Grid.Row="2" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding Goal}"
Grid.Row="2" Grid.Column="2"
FontSize="16" />
<Label Text="Part"
Grid.Row="3" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding PartName}"
Grid.Row="3" Grid.Column="2"
FontSize="16" />
<Label Text="Assembly"
Grid.Row="4" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding NodeName}"
Grid.Row="4" Grid.Column="2"
FontSize="16" />
<Label Text="GoodCount"
Grid.Row="5" Grid.Column="0"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding GoodCount}"
Grid.Row="6" Grid.Column="0"
FontSize="16"
HorizontalTextAlignment="Center"/>
<Label Text="RejectCount"
Grid.Row="5" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding RejectCount}"
Grid.Row="6" Grid.Column="1"
FontSize="16"
HorizontalTextAlignment="Center"/>
<Label Text="DownTimeCount"
Grid.Row="5" Grid.Column="2"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding DownTimeCount}"
Grid.Row="6" Grid.Column="2"
FontSize="16"
HorizontalTextAlignment="Center"/>
</Grid>
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Footer>
<Grid Padding="6" IsVisible="{Binding IsBusy}">
<!--set the footer to have a zero height when invisible-->
<Grid.Triggers>
<Trigger TargetType="Grid" Property="IsVisible" Value="False">
<Setter Property="HeightRequest" Value="0" />
</Trigger>
</Grid.Triggers>
<!--the loading content-->
<Label Text="Loading..." VerticalOptions="Center" HorizontalOptions="Center" />
</Grid>
</ListView.Footer>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
i want to update the good count on real time. i debugged the code its getting updated in my list but only updating the UI on a user scroll. Here is my viewmodel
public class JobViewModel: INotifyPropertyChanged
{
private ObservableCollection<Job> items;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Job> Items
{
get { return items; }
set
{
items = value;
if (items != value)
{
items = value;
OnPropertyChanged(nameof(Items));
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private void updateJoblistOnEvent(NotificationEntity message)
{
var jobId = message.JobId;
Job job = Items.FirstOrDefault(a => a.JobId.Equals(jobId));
switch (message.EventCode)
{
case MessageTypeEnum.Pulse: //Pulse
job.GoodCount = job.GoodCount + 1;
//job.Status = 'Running';
break;
default:
break;
}
}
i am suspecting two things
CachingStrategy="RecycleElement" i removed this one that its not updating with scroll also
any problem with INotifyPropertyChanged, i tried to remove one item from list its working and updating the ui.i want to update the field inside the item
can any one help me
As deduced from the comments, you need to also implement the INotifyPropertyChanged interface on the Job object. Using the ObservableCollection only helps for changes in that collection, so when you remove or add a Job object, not if something changes inside the Job object.
So, in your Job object do this (code reverse engineered from what you posted):
public class Job : INotifyPropertyChanged
{
private int goodCount;
public int GoodCount
{
get { return goodCount; }
set
{
if (goodCount != value)
{
goodCount = value;
OnPropertyChanged(nameof(GoodCount));
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You could remove the INotifyPropertyChanged from your JobViewModel, but you probably need it there at some point as well