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.
Related
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.
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>
I am trying to create a custom Action Sheet using CollectionView, but I have a problem with auto resizing the height. When I insert the CollectionView into the page, it takes up all the free space. What needs to be done so that the height of the CollectionView changes depending on the height of its elements?
<?xml version="1.0" encoding="utf-8"?>
<StackLayout
Margin="60, 0">
<CollectionView
BackgroundColor="White"
x:Name="CollectionViewControl">
<CollectionView.Header>
<Label
x:Name="HeaderLabel"
TextTransform="Uppercase"
HorizontalTextAlignment="Center"
FontAttributes="Bold" />
</CollectionView.Header>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0, 10" x:DataType="modalDialogs:ActionSheetItem">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" />
<Image Grid.Column="1" Source="arrow_down" IsVisible="{Binding IsSelected}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Footer>
<Button
BackgroundColor="Transparent"
TextColor="Green"
Text="Cancel"
HorizontalOptions="End" />
</CollectionView.Footer>
</CollectionView>
</StackLayout>
Current UI
When I insert the CollectionView into the page, it takes up all the free space. What needs to be done so that the height of the CollectionView changes depending on the height of its elements?
From Jason's opinion, little rows in Collectionview, you can set a value to collectionView.HeightRequest. when item increase, the height will increase as well.
I create a property called.RowHeigth. If I add item in the CollectionView(with AddCommand), RowHeigth will increase.
<StackLayout Margin="60,0">
<CollectionView
x:Name="CollectionViewControl"
BackgroundColor="Blue"
HeightRequest="{Binding rowHeigth}"
ItemsSource="{Binding items}">
<CollectionView.Header>
<Label
x:Name="HeaderLabel"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
TextTransform="Uppercase" />
</CollectionView.Header>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" />
<Image
Grid.Column="1"
HeightRequest="30"
IsVisible="{Binding IsSelected}"
Source="a11.png" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Footer>
<Button
BackgroundColor="Transparent"
HorizontalOptions="End"
Text="Cancel"
TextColor="Green" />
</CollectionView.Footer>
</CollectionView>
<Button
x:Name="btnadd"
Command="{Binding AddCommand}"
Text="add item" />
</StackLayout>
public partial class Page5 : ContentPage
{
public Page5()
{
InitializeComponent();
this.BindingContext = new itemviewmodel();
}
}
public class itemviewmodel:ViewModelBase
{
public ObservableCollection<ActionSheetItem> items { get; set; }
private int _rowHeigth;
public int rowHeigth
{
get { return _rowHeigth; }
set
{
_rowHeigth = value;
RaisePropertyChanged("rowHeigth");
}
}
public ICommand AddCommand { protected set; get; }
public itemviewmodel()
{
items = new ObservableCollection<ActionSheetItem>();
for(int i=0;i<5;i++)
{
ActionSheetItem item = new ActionSheetItem();
item.Name = "name " + i;
item.IsSelected = true;
items.Add(item);
}
rowHeigth = items.Count * 60;
AddCommand = new Command<ActionSheetItem>(async (key) => {
items.Add(new ActionSheetItem() { Name = "test letter ", IsSelected=true });
rowHeigth = items.Count * 60;
});
}
}
The ViewModelBase is class that implementing INotifyPropertyChanged interface.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In my case BindableLayaout attached to StackLayout was best solution.
https://learn.microsoft.com/en-us/dotnet/maui/user-interface/layouts/bindablelayout
On my main page I have an ActivityIndicator running when IsBusy is true, this works fine, but when I use the same configuration on another page it does not run.
IsBusy resides in my BaseViewModel which is then initiated in the ViewModelBase, my code as follows...
public class BaseViewModel : BaseSession
{
//Some other properties .....
public bool IsConnected
{
get { return _IsConnected; }
set { SetProperty(ref _IsConnected, value); }
}
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
}
ViewModelBase...
public class ViewModelBase : BaseViewModel
{
//Lots of other Properties
}
page properties...
class BaseNetWork : ViewModelBase
{
public BaseNetWork(Grid grid, Image img, Label lbl)
{
BaseImages Images = new BaseImages();
NetworkShares NetWorkData = new NetworkShares();
img.GestureRecognizers.Add((new TapGestureRecognizer((view) => OpenShares())));
void OpenShares()
{
if (IsConnected)
{
grid.Children.Clear();
img.Source = Images.GetImages(4);
lbl.Text = "Connect";
IsConnected = false;
} else {
IsBusy = true; // Set the IsBusy to true for the ActivityIndicator.
NetWorkData.DeploySharesToGrid(grid, null, ConnectToShares);
IsConnected = true;
if (IsConnected)
{
img.Source = Images.GetImages(3);
lbl.Text = "Disconnect";
};
IsBusy = false; // Set the IsBusy back to false.
}
}
}
}
View.xaml.cs...
public partial class FolderView : ContentPage
{
BaseImages Images = new BaseImages();
BaseNetWork viewModel;
public FolderView()
{
InitializeComponent();
BindingContext = viewModel = new BaseNetWork(GridFolders, btnConnect, lblNet);
}
void CloseEntries(object sender, EventArgs e)
{
if (viewModel.IsConnected)
{
GridFolders.Children.Clear();
btnConnect.Source = Images.GetImages(4);
lblNet.Text = "Connect";
viewModel.IsConnected = false;
}
}
}
View.xaml...
<ScrollView>
<StackLayout Padding="{StaticResource PagePadding}" Spacing="{StaticResource PageSpacing}">
<Grid RowDefinitions="{StaticResource BrowseShareRows}" ColumnDefinitions="{StaticResource BrowseShareColumns}">
<Label x:Name="lblNet" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalTextAlignment="Start" Text="Connect"/>
<Image x:Name="btnConnect" Grid.Row="1" Grid.Column="0" HorizontalOptions="Start" Grid.ColumnSpan="2" Source="folder_darkblue.png"></Image>
<StackLayout Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2">
<controls:Checkbox x:Name="cbHello" Text="{Binding ViewMessage}" Checked="{Binding ConnectToShares}" PropertyChanged="CloseEntries"/>
</StackLayout>
<ScrollView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5">
<StackLayout>
<Grid x:Name="GridFolders" Style="{StaticResource FolderStyle}" IsVisible="{Binding AllowViewGrid}"></Grid>
</StackLayout>
</ScrollView>
<ActivityIndicator Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="5" Color="red" IsRunning="{Binding IsBusy}"/>
</Grid>
</StackLayout>
</ScrollView>
viewthatworks.xaml.cs
public MainPage()
{
InitializeComponent();
//ConnectivityTest();
BindingContext = new ViewModelBase();
}
viewthatworks.xaml
<ScrollView>
<StackLayout Padding="{StaticResource PagePadding}" VerticalOptions="CenterAndExpand" Spacing="{StaticResource PageSpacing}">
<Grid x:Name="controlGrid" RowDefinitions="{StaticResource MainRowStyle}" ColumnDefinitions="{StaticResource MainColumnStyle}" Style="{StaticResource MainGridStyle}">
<Grid RowDefinitions="{StaticResource GridRowLayout3}" ColumnDefinitions="{StaticResource GridColumnLayout3}" >
<Entry x:Name="txtLocation" Text="{Binding GetFolder}"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1"
Style="{StaticResource MainEntryStyle}"
Placeholder="Name"/>
<Button x:Name="btnSave" Text="Save Folder"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1"
Style="{StaticResource MainButtonStyle}"
IsEnabled="{Binding IsNotBusy}"
Command="{Binding SaveContactCommand}"/>
<Button x:Name="btnCreate" Text="Create"
Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1"
Style="{StaticResource MainButtonStyle}"
IsEnabled="{Binding IsNotBusy}"
Command="{Binding CreateDir}"/>
<Button x:Name="btnGrid" Text="App"
Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="1"
Style="{StaticResource MainButtonStyle}"
Clicked="Get_Grid"/>
</Grid>
</StackLayout>
<ActivityIndicator IsRunning="{Binding IsBusy}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="6" Color="red"/>
</Grid>
</StackLayout>
</ScrollView>
Can anyone please say why the ActivityIndicator is not running?
Sorry for the misunderstanding, I didn't place the question as answered, I think maybe I didn't press save after updating some code doh :-) because the object is updated and the page is getting the updated property
In Xamarin Forms, I want to implement a horizontal listview (like shown in the image below). Via Rotation this is possible, but the I cannot change the row width. Is there also a possibility the let the second layout start under the first one?
Thanks in advance!
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Recipe.Pages.SearchPage"
Title="Search">
<ContentPage.Content>
<StackLayout Spacing="5" x:Name="layout" Orientation="Vertical" >
<StackLayout x:Name="layoutButtons" HorizontalOptions="FillAndExpand" Orientation="Horizontal" Spacing="5" BackgroundColor="Red">
<Button x:Name="btn1" BackgroundColor="White" Image="#drawable/scan" />
<Button x:Name="btn2" BackgroundColor="White" Image="#drawable/menu" />
<Button x:Name="btn3" BackgroundColor="White" Image="#drawable/search" />
</StackLayout>
<StackLayout x:Name="layoutList" >
<ListView x:Name="listView" Rotation="270" RowHeight="75" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee" Orientation="Vertical" >
<StackLayout Orientation="Horizontal" >
<Button BackgroundColor="White" Rotation="90" Image="{Binding Recipe}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
EDIT
I also tried with a grid in the listview. Having the same issue.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Recipe.Pages.SearchPage"
Title="Search">
<ContentPage.Content>
<StackLayout Spacing="5" x:Name="layout" Orientation="Vertical" >
<StackLayout x:Name="layoutButtons" HorizontalOptions="FillAndExpand" Orientation="Horizontal" Spacing="5" BackgroundColor="Red">
<Button x:Name="btn1" BackgroundColor="White" Image="#drawable/scan" />
<Button x:Name="btn2" BackgroundColor="White" Image="#drawable/menu" />
<Button x:Name="btn3" BackgroundColor="White" Image="#drawable/search" />
</StackLayout>
<StackLayout x:Name="layoutList" >
<ListView x:Name="listView" Rotation="270" RowHeight="75" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" BackgroundColor="White" Rotation="90" Image="{Binding Recipe}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
I have also facing the same issue. I used below xaml code to manage the height and width of ListView.
<?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="VCRoom.HorizontalScroll">
<ContentPage.Content >
<RelativeLayout>
<ListView x:Name="TestListView"
RowHeight="80"
Rotation="270"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=0.5, Constant=-40}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=-0.5, Constant=40}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=Constant, Constant=80}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=1}"
>
</ListView>
</RelativeLayout>
</ContentPage.Content>
</ContentPage>
Change RowHeight and Constant in XConstraint and YConstraint to manage Width and height of horizontal ListView accordingly.
Just for reference Below is custom cell I used to populate ListView items. I displayed vertical labels in each list item.
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="VCRoom.TestCell">
<ViewCell.View>
<StackLayout Orientation="Horizontal" HorizontalOptions="End" VerticalOptions ="Center">
<Label Rotation="90" Text="{Binding Day}" TextColor="#000000" HorizontalOptions="StartAndExpand" VerticalOptions="Start"/>
<Label Rotation="90" Text="{Binding mDate}" TextColor="#000000" HorizontalOptions="StartAndExpand" VerticalOptions="Start"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
Hope this will help future users.
The best solution that i found to resolve this problem is making a CustomScrollView with properties from Listview, like is showed in the tutorial from the Fabio Cozzolino.
Pay attention that he has a updated version in the comments.
He created a custom scrollview:
public class TLScrollView : ScrollView
{ public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(CustomScrollView), default(IEnumerable),
BindingMode.Default, null, new BindableProperty.BindingPropertyChangedDelegate(HandleBindingPropertyChangedDelegate));
private static object HandleBindingPropertyChangedDelegate(BindableObject bindable, object value)
{
throw new NotImplementedException();
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(CustomScrollView), default(DataTemplate));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public event EventHandler<ItemTappedEventArgs> ItemSelected;
public static readonly BindableProperty SelectedCommandProperty =
BindableProperty.Create("SelectedCommand", typeof(ICommand), typeof(CustomScrollView), null);
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
public static readonly BindableProperty SelectedCommandParameterProperty =
BindableProperty.Create("SelectedCommandParameter", typeof(object), typeof(CustomScrollView), null);
public object SelectedCommandParameter
{
get { return GetValue(SelectedCommandParameterProperty); }
set { SetValue(SelectedCommandParameterProperty, value); }
}
static void HandleBindingPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue)
{
var isOldObservable = oldValue?.GetType().GetTypeInfo().ImplementedInterfaces.Any(i => i == typeof(INotifyCollectionChanged));
var isNewObservable = newValue?.GetType().GetTypeInfo().ImplementedInterfaces.Any(i => i == typeof(INotifyCollectionChanged));
var tl = (CustomScrollView)bindable;
if (isOldObservable.GetValueOrDefault(false))
{
((INotifyCollectionChanged)oldValue).CollectionChanged -= tl.HandleCollectionChanged;
}
if (isNewObservable.GetValueOrDefault(false))
{
((INotifyCollectionChanged)newValue).CollectionChanged += tl.HandleCollectionChanged;
}
if (oldValue != newValue)
{
tl.Render();
}
}
private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Render();
}
}
Created a method to render the scrollview:
public void Render ()
{ if (ItemTemplate == null || ItemsSource == null)
{
Content = null;
return;
}
var layout = new StackLayout();
layout.Orientation = Orientation == ScrollOrientation.Vertical ? StackOrientation.Vertical : StackOrientation.Horizontal;
foreach (var item in ItemsSource)
{
var command = SelectedCommand ?? new Command((obj) =>
{
var args = new ItemTappedEventArgs(ItemsSource, item);
ItemSelected?.Invoke(this, args);
});
var commandParameter = SelectedCommandParameter ?? item;
var viewCell = ItemTemplate.CreateContent() as ViewCell;
viewCell.View.BindingContext = item;
viewCell.View.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = command,
CommandParameter = commandParameter,
NumberOfTapsRequired = 1
});
layout.Children.Add(viewCell.View);
}
Content = layout; }
Then, a customRenderer to each platform (Here iOS):
[assembly: ExportRenderer(typeof(TLScrollView), typeof(TLScrollViewRenderer))]
namespace TitiusLabs.Forms.iOS.Controls
{
class CustomScrollViewRenderer : ScrollViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var element = e.NewElement as CustomScrollView;
element?.Render();
}
} }
You can use the FlowListView plugin to archive horizontal listview easily on xamarin forms.
NuGet - DLToolkit.Forms.Controls.FlowListView