I'm making a shopping app, it has account for the user, and when the user purchases a product, it should be added to a listview in his account. So I tried to put a static object of the User Class that has a list of Products, and whenever the user clicks the buying button, it should be added to the list. At the same time, the user.xaml is binding to the same object. But it doesn't work. What's the error in my method?
Are there any better ideas to do this?
here's the static field in the App.xaml.cs file
private IDataService _dataService;
public static User TheUser;
public App(IDataService dataService)
{
TheUser = new User();
InitializeComponent();
var unity = new UnityContainer();
unity.RegisterType<IDataService, DataServices>();
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(unity));
_dataService = dataService;
MainPage = new NavigationPage(new MainPage());
}
and here's the User.xaml.cs property
public User User
{
get { return App.TheUser; }
set
{
if(App.TheUser!= null)
App.TheUser = value;
}
//User class
public class User : Base //Base class implements INotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Product> Products = new ObservableCollection<Product>();
}
public class Base : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here's the User.Xaml file
<StackLayout>
<AbsoluteLayout>
<Image AbsoluteLayout.LayoutBounds="1 , 0 ,96 ,96" AbsoluteLayout.LayoutFlags="PositionProportional"/>
<Label AbsoluteLayout.LayoutBounds="0 , 50 , 100 , 20" AbsoluteLayout.LayoutFlags="XProportional" Text="First Name"/>
<Label AbsoluteLayout.LayoutBounds="0 , 100 , 100 , 20" AbsoluteLayout.LayoutFlags="XProportional" Text="Last Name"/>
</AbsoluteLayout>
<ListView x:Name="UserListView"
SelectedItem="{Binding SelectedItemCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="3" FlowDirection="RightToLeft" >
<Image Source="{Binding ProductMainImage}" Aspect="AspectFill" Margin="3" HeightRequest="300" />
<Label Text="{Binding Name ,StringFormat=' الاسم : {0}'}"/>
<Label Text="{Binding Price ,StringFormat=' السعر : {0}'}"/>
<Label Text="{Binding Description ,StringFormat=' الوصف : {0}'}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ok so the property notification doesn't happen automatically. You need to invoke that code to raise the event. This should takes care of the code, but without seeing the xaml, I don't know if the binding is setup correctly.
public User User
{
get { return App.TheUser; }
set
{
if(App.TheUser!= null)
App.TheUser = value;
}
//User class
public class User : Base //Base class implements INotifyPropertyChanged
{
private int _id
public int Id {
get{
return this._id;
}
set{
this._id = value;
OnPropertyChanged("Id");
}
}
private string _name;
public string Name {
get{
return this._name;
}
set{
this._name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get{
return this._products;
}
set{
this._products = value;
OnPropertyChanged("Products");
}
}
}
}
so your listview is not bound to anything...
<ListView x:Name="UserListView"
ItemsSource={Binding Products}
SelectedItem="{Binding SelectedItemCommand}">
Related
I have an article here about showing Data Groups from Preferences . As per everyone's input I switched to CollectionView. I have consulted the article https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/grouping. This is what I have:
public class CartUser
{
public int IDProduct { get; set; }
public string NameProduct { get; set; }
public string SupplierID { get; set; }
}
SupplierIDGrouping.cs
public class SupplierIDGrouping : ObservableCollection<CartUser>
{
public string SupplierID { get; private set; }
public SupplierIDGrouping(string supplierID)
: base()
{
SupplierID = supplierID;
}
public SupplierIDGrouping(string supplierID, IEnumerable<CartUser> source)
: base(source)
{
SupplierID = supplierID;
}
}
PageOne.xaml
<CollectionView ItemsSource="{Binding SupplierList}" IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding NameProduct}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
PageOne.xaml.cs
public ObservableCollection<SupplierIDGrouping> SupplierList { get; private set; } = new ObservableCollection<SupplierIDGrouping>();
List<CartUser> cartUsers = new List<CartUser>();
var mycart = Preferences.Get("CartUserAdds", "_mycart");
var getcart = JsonConvert.DeserializeObject<List<CartUser>>(mycart);
cartUsers = getcart;
foreach (var item in cartUsers)
{
if (!SupplierList.Any(supplierid => supplierid.SupplierID == item.SupplierID))
{
SupplierList.Add(new SupplierIDGrouping(item.SupplierID));
}
SupplierList.Single(supplierid => supplierid.SupplierID== item.SupplierID).Add(item);
}
BindingContext = this;
The data I am taken from Preferences:
[{\"IDProduct\":1,\"NameProduct\":\"Name product 1\",\"SupplierID\":\"22379356\"},{\"IDProduct\":2,\"NameProduct\":\"Name product 2\",\"SupplierID\":\"22379356\"},{\"IDProduct\":3,\"NameProduct\":\"Name product 3\",\"SupplierID\":\"12336544\"}]
However my results are still not grouped by SupplierID
This is what I want:
Looking forward to everyone's help. Thank you very much!
Update
Data corresponds to 2 groups. Group 1: 2 products, group 2: 1 product
foreach (var item in cartUsers)
{
if (!SupplierList.Any(supplierid => supplierid.SupplierID == item.SupplierID))
{
SupplierList.Add(new SupplierIDGrouping(item.SupplierID));
}
SupplierList.Single(supplierid => supplierid.SupplierID== item.SupplierID).Add(item);
}
var getresult = SupplierList;
foreach(var i in getresult)
{
}
BindingContext = this;
Update 2
public class SupplierIDGrouping : ObservableCollection<CartUser>
{
public string SupplierID { get; private set; }
public string Name { get { return SupplierID; } }
public SupplierIDGrouping(string supplierID)
: base()
{
SupplierID = supplierID;
}
public SupplierIDGrouping(string supplierID, IEnumerable<CartUser> source)
: base(source)
{
SupplierID = supplierID;
}
}
PageOne.xaml
<CollectionView ItemsSource="{Binding SupplierList}" IsGrouped="true" Header="{Binding Name}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding NameProduct}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I had to provide a GroupHeaderTemplate to make it work. Not sure why - according to the docs it should not be necessary
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding SupplierID}"
BackgroundColor="LightGray"
FontSize="Large"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
I followed the doc, trying to bind the pin, but failed. The map is always showing the default position Rome. Here is the source code:
In DetailPage.xmal:
<Frame Margin="10,5"
CornerRadius="10"
Padding="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300" />
</Grid.RowDefinitions>
<maps:Map MapType="Street" Grid.Row="0" ItemsSource="{Binding WorkPlace}">
<maps:Map.ItemTemplate>
<DataTemplate>
<maps:Pin Position="{Binding Position}"
Address="{Binding Address}"
Label="{Binding Description}" />
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
</Grid>
</Frame>
In DetailPageModel.cs:
public class DetailPageModel : PageModelBase
{
private Timesheet _detailedTimesheet;
private ObservableCollection<Location> _workPlace;
public ObservableCollection<Location> WorkPlace
{
get => _workPlace;
set => SetProperty(ref _workPlace, value);
}
public ReportDetailPageModel()
{
}
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is Timesheet selectedTimesheet)
{
_detailedTimesheet = selectedTimesheet;
WorkPlace = new ObservableCollection<Location>()
{
new Location(
_detailedTimesheet.ProjectAddress,
"Test Location",
new Position(_detailedTimesheet.ProjectLatitude, _detailedTimesheet.ProjectLongitude))
};
}
await base.InitializeAsync(navigationData);
}
}
In Location.cs:
public class Location : ExtendedBindableObject
{
Position _position;
public string Address { get; }
public string Description { get; }
public Position Position
{
get => _position;
set => SetProperty(ref _position, value);
}
public Location(string address, string description, Position position)
{
Address = address;
Description = description;
Position = position;
}
}
In ExtendedBindableObject.cs:
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
Since the navigationData is correctly received in view model and the page's binding context is also working, I just don't know what could be missing. Any hint would be appreciated!
And actually I have one more confusion, why does the official doc use a custom Location class instead of the Pin class, as Pin inherits from Element/BindableObject/Object?
I have a very basic need, but it seems quite challenging to achieve such a simple thing in Xamarin Forms, especially when I compare it with the way the React Native let us do the same thing.
Anyways, so I am trying to highlight a frame's background color based on the selected Id. For that, I have created a value converter, and passing Id to check and convert to the desired background color.
Below is my XAML Code:
<Frame CornerRadius="6" Padding="10" Margin="5" WidthRequest="110" HeightRequest="80"
BackgroundColor="{Binding TitleId, Converter={StaticResource
selectedGuidelineToColorConverter},ConverterParameter={x:Reference Guidelines}}">
<StackLayout>
<Label Style="{StaticResource MaterialIcons}" Text="" FontSize="20"/>
<Label Text="{Binding Title}" FontSize="13" HorizontalTextAlignment="Start" TextColor="#333d47"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Path=BindingContext.SelectedGuidelineCommand, Source={x:Reference Guidelines}}" CommandParameter="{Binding}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</Frame>
Below is my Converter Code:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var page = parameter as ContentPage;
GuidelinesViewModel model = null;
if(page != null)
{
model = page.BindingContext as GuidelinesViewModel;
}
if(model != null && model.CurrentVisibleGuideline != null && model.CurrentVisibleGuideline.TitleId == (int)value)
{
return "#808080";
}
return "#fff";
}
My Model Code:
public class Guideline
{
public int TitleId { get; set; }
public string Title { get; set; }
public Section Section { get; set; }
public List<Content> Content { get; set; }
public List<QnA> QnA { get; set; }
}
Here is my View Model Code:
Guideline guideline = null;
public Guideline CurrentVisibleGuideline
{
get { return guideline; }
set { SetProperty(ref guideline, value); }
}
public ICommand SelectedGuidelineCommand
{
get
{
return new Command<Guideline>((guideline) => ExecuteSelectedGuidelineCommand(guideline));
}
}
void ExecuteSelectedGuidelineCommand(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
}
async void GetGuidelines()
{
IsBusy = true;
Guidelines = new ObservableCollection<Guideline>();
try
{
var guidelines = await DataStore.GetGuidelinesAsync(CurrentVisibleSection);
foreach (var guideline in guidelines)
{
Guidelines.Add(guideline);
}
CurrentVisibleGuideline = Guidelines[0];
TotalGuidelines = Guidelines.Count;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
"CurrentVisibleGuideline" is a property in my View Model which contains the TitleId and other details of the selected guideline.
Problem is that, the converter code is executed before the CurrentVisibleGuideline = Guidelines[0] in the view model, and therefore, I get null in CurrentVisibleGuideline in the converter.
I believe that once the data is updated in the view model upon command execution, the XAML code should re-render the view and re-run the converter, but in my case it doesn't seem to happen.
Add an IsSelected boolean to you Guideline View Model.
Update the convertor to take in a bool and return a string that will return
your Hex color value.
When you change the CurrentVisibleGuideline Set the IsSelected flag on your Guideline View Model.
ViewModel
public class Guideline
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public int TitleId { get; set; }
public string Title { get; set; }
public Section Section { get; set; }
public List<Content> Content { get; set; }
public List<QnA> QnA { get; set; }
}
ObservableCollection<Guideline> Guidelines { get; set; }
Guideline guideline = null;
public Guideline CurrentVisibleGuideline
{
get { return guideline; }
set { SetProperty(ref guideline, value); }
}
public ICommand SelectedGuidelineCommand
{
get
{
return new Command<Guideline>((guideline) => ExecuteSelectedGuidelineCommand(guideline));
}
}
void ExecuteSelectedGuidelineCommand(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
}
async void GetGuidelines()
{
IsBusy = true;
Guidelines = new ObservableCollection<Guideline>();
try
{
var guidelines = await DataStore.GetGuidelinesAsync(CurrentVisibleSection);
foreach (var guideline in guidelines)
{
Guidelines.Add(guideline);
}
CurrentVisibleGuideline = Guidelines[0];
TotalGuidelines = Guidelines.Count;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
void SetCurrentGuideline(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
foreach (var gl in Guidelines)
gl.IsSelected = gl.TitleId == CurrentVisibleGuideline.TitleId;
}
Converter
public class BooleanToStringConverter : IValueConverter
{
public string TrueValue { get; set; } = "#808080"
public string FalseValue { get; set; } = "#FFF"
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
return TrueValue;
else
return FalseValue;
}
return "";
}
}
XAML
<Frame CornerRadius="6" Padding="10" Margin="5" WidthRequest="110" HeightRequest="80"
BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToString}}">
<StackLayout>
<Label Style="{StaticResource MaterialIcons}" Text="" FontSize="20"/>
<Label Text="{Binding Title}" FontSize="13" HorizontalTextAlignment="Start" TextColor="#333d47"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Path=BindingContext.SelectedGuidelineCommand, Source={x:Reference Guidelines}}" CommandParameter="{Binding}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</Frame>
On a side note you will also need to turn the guideline(s) you get back from your DataStore into a ViewModel version.
I can get datatable after scan barcode but it not show when i bind it to sfDatagrid. what am i doing wrong. I think i call vm.TimSPTonKho.Execute(null); in .cs incorrectly
code xaml and .cs
<ContentPage.BindingContext>
<vm:vmBanHang_get_TTSanPham_ScanCode />
</ContentPage.BindingContext>
<StackLayout>
<Grid VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand">
<zxing:ZXingScannerView x:Name="scanView"
OnScanResult="scanView_OnScanResult"
IsScanning="True"
WidthRequest="200"
HeightRequest="300"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"/>
<zxing:ZXingDefaultOverlay TopText="Align the barcode within the frame"/>
</Grid>
<datagrid:SfDataGrid HorizontalOptions="Center" x:Name="datagrid"
AllowTriStateSorting="True"
ColumnSizer="Star"
ItemsSource="{Binding DataTableCollection}">
</datagrid:SfDataGrid>
</StackLayout>
this my xaml file
private void scanView_OnScanResult(Result result)
{
Device.BeginInvokeOnMainThread(async () =>
{
await DisplayAlert("Scanned result", "The barcode's text is " + result.Text + ". The barcode's format is " + result.BarcodeFormat, "OK");
var vm = new vmBanHang_get_TTSanPham_ScanCode();
vm.MaSanPham = result.Text;
vm.IDCuaHang = 1;
vm.TimSPTonKho.Execute(null);
});
}
my ViewModel.cs
class vmBanHang_get_TTSanPham_ScanCode : INotifyPropertyChanged
{
private ApiServices _apiServices = new ApiServices();
public int IDCuaHang { get; set; }
public string MaSanPham { get; set; }
public vmBanHang_get_TTSanPham_ScanCode()
{
DataTableCollection = _DataTableCollection;
}
public DataTable DataTableCollection
{
get { return _DataTableCollection; }
set
{
_DataTableCollection = value;
OnPropertyChanged();
}
}
public DataTable _DataTableCollection;
public ICommand TimSPTonKho
{
get
{
return new Command(async () =>
{
if (!string.IsNullOrEmpty(MaSanPham))
{
DataTableCollection = await _apiServices.get_TTSanPham_ScanCode(IDCuaHang, MaSanPham, Settings.Accesstoken);
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. My second question, how can i get all values Datagrid send to Datatable ? do i have to implement it in xaml.cs or in viewmodel.
Thanks for helps
I found the solution. bind directly from xaml.cs
datagrid.ItemsSource = await _apiServices.get_TTSanPham_ScanCode(1, result.Text, Settings.Accesstoken);
instead of call Icommand in Viewmodel
public ICommand TimSPTonKho
{
get
{
return new Command(async () =>
{
if (!string.IsNullOrEmpty(MaSanPham))
{
DataTableCollection = await _apiServices.get_TTSanPham_ScanCode(IDCuaHang, MaSanPham, Settings.Accesstoken);
}
});
}
}
I have a UWP Page, containing a form with textboxes and a ListView control. The ListView control is bound to a collection of Products. And I want that the bound textboxes should show the information regarding the product selected in the listview.
public class Product: INotifyPropertyChanged
{
public int ProductID { get; set; }
private string name;
public string Name {
get { return name; }
set
{
if (name==value)
return;
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Product(int pid, string name)
{
ProductID = pid;
Name = name;
}
}
}
The XAML is as below:
<TextBox x:Name="txtProductId" Grid.Row="1" Grid.Column="1"
Text="{x:Bind CurrentProduct.ProductID}"/>
<TextBox x:Name="txtProductName" Grid.Row="2" Grid.Column="1"
Text="{x:Bind CurrentProduct.Name}" />
<ListView x:Name="lstProducts" Grid.Row="3" Grid.ColumnSpan="2"
ItemsSource="{x:Bind ProductList}"
SelectedItem="{x:Bind CurrentProduct, Mode=TwoWay}"
ItemTemplate="{StaticResource lstDataTemplate}"
>
</ListView>
The following code executes on Page_Loaded:
CurrentProduct = Products[0];
DataContext = CurrentProduct;
The ListView is bound to ProductList (Type ObservableCollection). I've noticed by executing the app in single step mode, the value of CurrentProduct is changing, but I think since it is the reference and not the property of the DataContext that changes, the PropertyChanged event is never fired and the TextBox doesn't get updated to show the name of the CurrentProduct.
I don't know how to proceed, any help would be appreciated.
The X:Bind default mode is OneTime, in your case, you need to set the mode to OneWay.
I made a code sample for your reference:
<TextBox x:Name="txtProductId" Grid.Row="1" Grid.Column="1"
Text="{x:Bind CurrentProduct.ProductID,Mode=OneWay}"/>
<TextBox x:Name="txtProductName" Grid.Row="2" Grid.Column="1"
Text="{x:Bind CurrentProduct.Name,Mode=OneWay}" />
<ListView x:Name="lstProducts" Grid.Row="3" Grid.ColumnSpan="2"
ItemsSource="{x:Bind ProductList}"
SelectedItem="{x:Bind CurrentProduct, Mode=TwoWay}"
>
</ListView>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
ProductList.Add(new Product(i, "name " + i));
}
}
public ObservableCollection<Product> ProductList { get; set; } = new ObservableCollection<Product>();
private Product _CurrentProduct = new Product(100,"test");
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
}
public Product CurrentProduct
{
get { return _CurrentProduct; }
set
{
if (_CurrentProduct != value)
{
_CurrentProduct = value;
RaisePropertyChanged("CurrentProduct");
}
}
}
}
public class Product : INotifyPropertyChanged
{
public int ProductID { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;
name = value;
RaisePropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public Product(int pid, string name)
{
ProductID = pid;
Name = name;
}
public override string ToString()
{
return Name;
}
}