I am creating a Xamarin Forms mobile app using VS 2019 latest version. Xamarin Forms and Essentials packages are also updated to latest.
I have following viewmodel, but the LoadHouses() method is not called via
24. LoadHousesCommand = new AsyncCommand(LoadHouses);
Any idea why? I also get "The breakpoint will not currently be hit" warning. Thanks
Edit:
My xaml page as follows,
<?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="HomeInventory.Views.HousesPage"
xmlns:viewmodels="clr-namespace:HomeInventory.ViewModels"
xmlns:models="clr-namespace:Shared.Models;assembly=Shared"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:DataType="viewmodels:HouseViewModel"
>
<ContentPage.BindingContext>
<viewmodels:HouseViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ListView ItemsSource="{Binding Houses}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:House">
<TextCell Text="{Binding Name}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
When I add houses manually as follows in the constructor it works fine,
public HouseViewModel()
{
Houses = new ObservableRangeCollection<House>();
LoadHousesCommand = new AsyncCommand(LoadHouses);
House h1 = new House();
h1.Name = "House 01";
Houses.Add(h1);
House h2 = new House();
h2.Name = "House 02";
Houses.Add(h2);
}
You can bind the Command with a button or something can execute to call the method,
like:
<StackLayout>
<Button Text="Load"
Command="{Binding LoadHouseCommand}"/>
<CollectionView x:Name="col">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is the ViewModel:
public class HouseViewModel
{public ObservableRangeCollection<House> Houses { set; get; }
public IAsyncCommand LoadHouseCommand { set; get; }
public HouseViewModel()
{
Houses = new ObservableRangeCollection<House>();
LoadHouseCommand = new AsyncCommand(async()=> {
House H1 = new House();
H1.Name = "House 1";
Houses.Add(H1);
House H2 = new House();
H2.Name = "House 2";
Houses.Add(H2);
Console.WriteLine("done");
});
}
result:
If you don't want to bind your command to button or anything. You can simply execute your command from your xaml.cs file like below.
From OnAppearing before your page load. It is the best way for web api.
protected override void OnAppearing()
{
base.OnAppearing();
var vm = BindingContext as HouseViewModel;
vm.LoadHousesCommand.Execute(null);
}
Or, You can simply run it inside your xaml.cs constructor
Task.Run(() =>
{
var vm = BindingContext as HouseViewModel;
vm.LoadHousesCommand.Execute(null);
});
Related
I have a case of using a CarouselView that is displayed based on certain data brought from an API, the point is that I need to see a certain view or at least text while the API data is being downloaded and another one in case That there is no data.
I tried to get to this using RefreshView and EmptyView but I cannot achieve the required behavior, I can make an EmptyView appear immediately the data begins to load since at that moment the ItemSource is null, then when the data reaches the app the Carousel appears , which seems to me quite ugly, the ideal would be to show some view that next to the RefreshView indicator shows that the data is loading and then in case of not bringing any data show a view that of the feedback that API data did not return .
I hope I have made myself understood and I hope someone can give me an idea on how to achieve this behavior.
MyViewModel:
public MyViewModel()
{
IsRefreshing = true;
Things = new ObservableCollection<Things>();
var t = Task.Run(async () =>
{
await LoadThings();
});
Task.WhenAll(t);
IsRefreshing = false;
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetThingsAsync(Id));
}
My IsRefreshing property is linked to the IsRefreshing property in the RefreshView that encompasses my CarouselView
I think you could use two empty view and switch between them when the refreshing status changes, and here is the code:
add two content view in in XAML and set default empty view to LoadingData:
<ContentPage.Resources>
<ContentView x:Key="LoadingData">
<StackLayout>
<Label Text="Loading data..."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="NoDataLoaded">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CarouselView x:Name="carouselView"
EmptyView="{StaticResource LoadingData}">
... ...
and in code, show different empty view accordingly:
public partial class HorizontalPullToRefreshPage : ContentPage
{
AnimalsViewModel viewModel;
public HorizontalPullToRefreshPage()
{
InitializeComponent();
viewModel = new AnimalsViewModel();
this.BindingContext = viewModel;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsRefreshing"))
{
if (viewModel.IsRefreshing && viewModel.Animals.Count==0)
{
carouselView.EmptyView = Resources["LoadingData"];
}
else if (!viewModel.IsRefreshing && viewModel.Animals.Count == 0)
{
carouselView.EmptyView = Resources["NoDataLoaded"];
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
carouselView.ItemsSource = viewModel.Animals;
}
}
then, every time the property IsRefreshing changed, you got a chance to switch the empty view.
Hope it helps.
I've created a customer specific task management app with tasks placed on specific dates (and sometime hours), but here the date is important.
I'm using a listView and have a DatePicker setting for selected other dates than today. So far so good.
I would like to implement a week quick-filter option so that e.g., the dates of the current week is displayed at the top of the list view and a click on a certain date would filter the listView accordingly. Kind of a standard outlook-like week view.
How would I do this in the best way?
CustomControl that I put above the listView?
ViewPager control?
Any ideas or suggestions much appreciated.
P.S. I need to be able to target both Android and iOS.
Set two Properties in the ViewModel one for containing all the Items EntireCollection and another to store the Filtered Items FilteredCollection. On button click derive the Filtered item from entire list using Where.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListItem> filteredCollection;
public ObservableCollection<ListItem> FilteredCollection
{
get
{
return filteredCollection;
}
set
{
filteredCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<ListItem> entireCollection;
public ObservableCollection<ListItem> EntireCollection
{
get
{
return entireCollection;
}
set
{
entireCollection = value;
OnPropertyChanged();
}
}
public ViewModel()
{ ...
this.FilterCollection = this.EntireCollection;
...
}
}
Button clicked
void Button_Clicked(System.Object sender, System.EventArgs e)
{
DateTime selectedDate = ((DateTime)((sender as VisualElement).BindingContext)).Date;
viewModel.FilteredCollection = new ObservableCollection<ListItem>(viewModel.EntireCollection.Where(x =>
{
if (DateTime.Equals(x.DateAdded, selectedDate))
{
var asd = x.DateAdded.Day;
return true;
}
return false;
}));
}
XAML
<StackLayout>
<ScrollView
x:Name="calender"
Orientation="Horizontal">
<StackLayout
BackgroundColor="Blue"
BindableLayout.ItemsSource="{Binding Dates}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button
TextColor="White"
BackgroundColor="Blue"
Clicked="Button_Clicked"
Text="{Binding Day}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<ListView
ItemsSource="{Binding FilteredCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding DateAdded}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope it helps!!
There are two listviews, one of them is grouped and other is not. I have to drag items from one list and drop into other. The drag and drop should be visual like item should actually be seen dragging and dropping on the UI. After some search, I found this tutorial TouchTrackingEffect Demos and thought to apply the same logic to Listview controls. I have somehow managed to apply the touch effects to the ListViews and it is firing events such as pressed, moved and released. I also managed to add items to the grouped listview. However, it is not showing dragging and dropping on the screen. I am assuming, I am doing something wrong but I am new to Xamarin so trying my best to get it working. The attached images show the UI, 2 shows the startup screen, 3 shows the X and Y coordinates in "Moved" effect, and 4 shows "New Word" is added in Grouped ListView on Released event. Below is the code, I would appreciate if someone can help me in this regard.
Page2Grid.Xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TouchTrackingEffect.Models;
using System.Collections.ObjectModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TouchTracking;
using SkiaSharp;
using SkiaSharp.Views.Forms;
namespace TouchTrackingEffect
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Page2Grid : ContentPage
{
//public ObservableCollection<GroupedStructureModel> grouped { get; set; }
ObservableCollection<GroupedStructureModel> grouped = new ObservableCollection<GroupedStructureModel>();
public ObservableCollection<GroupedStructureModel> grouped1 { get { return grouped; } }
// Drag and Drop Class
class DragInfo
{
public DragInfo(long id, Point pressPoint, AbsoluteLayout absoluteLayout)
{
Id = id;
PressPoint = pressPoint;
absoluteLayoutID = absoluteLayout;
}
public long Id { private set; get; }
public AbsoluteLayout absoluteLayoutID { set; get; }
public Point PressPoint { private set; get; }
}
// dictionary for ListViews
Dictionary<ListView, DragInfo> lstStructDragDictionary = new Dictionary<ListView, DragInfo>();
Dictionary<ListView, DragInfo> lstWordsDragDictionary = new Dictionary<ListView, DragInfo>();
Random random = new Random();
public Page2Grid()
{
InitializeComponent();
populateRhymeListView();
// adding effects to ListViews
// Main List
TouchEffect touchEffect1 = new TouchEffect();
touchEffect1.TouchAction += OnTouchEffectAction;
lstViewMain.Effects.Add(touchEffect1);
// Word List
TouchEffect touchEffect2 = new TouchEffect();
touchEffect2.TouchAction += OnTouchEffectAction;
lstViewWords.Effects.Add(touchEffect2);
}
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
lstViewWords = sender as ListView;
switch (args.Type)
{
case TouchActionType.Pressed:
// don't allow a second touch on an already touched boxview
if (!lstWordsDragDictionary.ContainsKey(lstViewWords))
{
lstWordsDragDictionary.Add(lstViewWords, new DragInfo(args.Id, args.Location, absLayout));
// set capture property to true
TouchEffect toucheffect = (TouchEffect)lstViewWords.Effects.FirstOrDefault(e => e is TouchEffect);
toucheffect.Capture = true;
}
break;
case TouchActionType.Moved:
if (lstWordsDragDictionary.ContainsKey(lstViewWords) && lstWordsDragDictionary[lstViewWords].Id == args.Id)
{
//lstViewMain.TranslateTo
headerLbl.Text = " listWords is moved";
Rectangle rect = AbsoluteLayout.GetLayoutBounds(lstViewWords);
Point initialLocation = lstWordsDragDictionary[lstViewWords].PressPoint;
rect.X += args.Location.X - initialLocation.X;
rect.Y += args.Location.Y - initialLocation.Y;
AbsoluteLayout.SetLayoutBounds(lstViewWords, rect);
headerLbl.Text = "X = " + args.Location.X + " Y = " + args.Location.Y;
}
break;
case TouchActionType.Released:
if (lstWordsDragDictionary.ContainsKey(lstViewWords) && lstWordsDragDictionary[lstViewWords].Id == args.Id)
{
grouped1.FirstOrDefault().Add(new StructureModel { word = "New Word" });
}
break;
}
}
// Create a Grouped ListView from a Rhyme Description
void populateRhymeListView()
{
// defining two lines
GroupedStructureModel list1Group = new GroupedStructureModel() { LongName = "Line1" };
GroupedStructureModel list2Group = new GroupedStructureModel() { LongName = "Line2" };
// words for List 1
//List<WordList> wordLst1 = new List<WordList>();
list1Group.Add(new StructureModel { word = "first"});
// words for List
list2Group.Add(new StructureModel { word = "second1" });
grouped1.Add(list1Group);
grouped1.Add(list2Group);
// binding list itemsource
lstViewMain.ItemsSource = grouped1;
}
async void BtnNext_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new Page3());
}
}
}
****Page2Grid.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="TouchTrackingEffect.Page2Grid">
<ContentPage.Content>
<AbsoluteLayout x:Name="absLayout">
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand">
<Grid x:Name="gridLayout">
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
<ColumnDefinition Width="300"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label x:Name="headerLbl" Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<Button x:Name="btnNext" Grid.Row="0" Grid.Column="2" Text="Next" Clicked="BtnNext_Clicked"></Button>
<!--Main ListView containing structure -->
<ListView x:Name ="lstViewMain" Grid.Row="1" Grid.Column="0" IsGroupingEnabled="true" GroupDisplayBinding="{Binding LongName}" WidthRequest="200" BackgroundColor="Azure">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="lblItem" Text="{Binding word}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--2nd Column-->
<BoxView x:Name="test" Grid.Row="1" Grid.Column="1"></BoxView>
<!-- Word List Views-->
<ListView x:Name="lstViewWords" Grid.Row="1" Grid.Column="2" BackgroundColor="AliceBlue">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</Grid>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
I'm using Xamarin.Forms MVVM to develop my app, and don't found what I'm doing wrong, I have an ObservableCollection with the values from web API, and when I set a break point all the values are good even in the view when I see the values of the binding source everything have the value, but the values are not showing up in my ListView.
Here is the ViewModel
class DatosMedicosViewModel : BaseViewModel
{
private ApiService apiService;
private ObservableCollection<Land> land;
private bool isRefreshing;
public ObservableCollection<Land> Lands
{
get { return this.land; }
set { SetValue(ref this.land, value); }
}
public bool IsRefreshing
{
get { return this.isRefreshing; }
set { SetValue(ref this.isRefreshing, value); }
}
public DatosMedicosViewModel()
{
this.apiService = new ApiService();
this.LoadLand();
}
private async void LoadLand()
{
this.IsRefreshing = true;
var connection = await this.apiService.CheckConnection();
if (!connection.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
connection.Message,
"Accept");
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
var response = await this.apiService.GetList<Land>(
"url Base",
"prefix",
"Controller");
if (!response.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept"
);
return;
}
var list = (List<Land>)response.Result;
this.Lands = new ObservableCollection<Land>(list);
this.IsRefreshing = false;
}
public ICommand RefreshCommand
{
get
{
return new RelayCommand(LoadLand);
}
}
}
Here is the View
<?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="ARLAPP.Views.ConsultaPage"
BackgroundColor="White"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
Title="Lands">
<ContentPage.Content>
<StackLayout
BindingContext="{Binding Lands}"
Padding="5">
<StackLayout>
<Image
VerticalOptions="Center"
WidthRequest="300"
Source="UserIcon"
BackgroundColor="Transparent"/>
<Label Text="Mark"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
FontAttributes="Bold"
FontSize="Medium"/>
</StackLayout>
<StackLayout>
<ListView
SeparatorVisibility="Default"
FlowDirection="LeftToRight"
BackgroundColor="White"
ItemsSource="{Binding Lands}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
Grid.Column="2"
VerticalOptions="Center"
TextColor="Black"
Text="{Binding Currency}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here how I call the view
if (this.PageName == "Lands")
{
MainViewModel.GetInstance().Lands= new LandViewModel();
Application.Current.MainPage = new LandMasterPage();
}
Check your BindingContext. I think you are setting it wrong in your view.
In your top-level StackLayout you set the the BindingContext to your property: BindingContext="{Binding Lands}". And in your ListView you set the ItemsSource also to this property: ItemsSource="{Binding Lands}". That won't work because the ListView is trying to bind to a property Lands inside your BindingContext, which is also set to Lands.
Remove the BindingContext from your top-level StackLayout, because you don't need it.
Ensure the BindingContext of your page ConsultaPage is set to your view-model DatosMedicosViewModel.
Sample of setting the bindingcontext (abstract code):
var mypage = new ConsultaPage();
mypage.BindingContext = new DatosMedicosViewModel();
await Navigation.PushAsync(mypage);
// Load your data in OnAppearing() of the page-event
This should solve your binding-problem.
Side-Note: As Abdul Gani said in the comments: Ensure you implement the INotifyPropertyChanged interface, but I assume you do this already in your BaseViewModel and call the NotifyChanged-Event in your SetValue-Method.
I'm developing a cross platform application with Xamarin. The framework used for MVVM is Prism.
In my user interface there is an image, I need that it raise an action when it's tapped.
I tried with TapGestureRecognizer but it doesn't work. Where is the error? There is another way to do that?
XAML:
...<Frame Grid.Row="0" Grid.Column="0" OutlineColor="Black" Padding="5">
<Image x:Name="imgSynch" Source="synch.png" >
<Image.GestureRecognizers>
<TapGestureRecognizer Command="Binding TapCommand" />
</Image.GestureRecognizers>
</Image>
</Frame>...
ViewModel:
... ICommand tapCommand;
public ICommand TapCommand
{
get { return tapCommand; }
}
public MainPageViewModel()
{
var tapImageSynch = new TapGestureRecognizer();
tapCommand = new Command(Synch);
void Synch()
{
_pageDialogService.DisplayAlertAsync("Title", "It works!", "OK");
} ...
Thanks!
You've got a syntax error in your code:
Command="Binding TapCommand"
should be:
Command="{Binding TapCommand}"
if your Binding is specified as an instance of MainPageViewMode