Button is not clickable in CollectionView - c#

i have a CollectionView and my custom buttom. I want to make a grid with buttons.When I click on button it change a background color.I want to write in void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e) something AND that the text of label is Name(class field) of SELECTED button.When I click on buttons in collectionview it change color but button is not clickable,it does not see it,if I write image it can read data.Please help me make button clickable
<StackLayout>
<Label x:Name="meow1"></Label>
<CollectionView ItemsSource="{Binding Cars}" x:Name="phonesList"
HeightRequest="90"
ItemsLayout="HorizontalList"
BackgroundColor="Transparent"
SelectionMode="Single"
SelectionChanged="OnCollectionViewSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame" CornerRadius="10" BackgroundColor="Black" Padding="0" HeightRequest="90"
WidthRequest="95">
<Grid Padding="0" x:Name="meow">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<controls:CustomButton TintColor="#725762" HeightRequest="90"
WidthRequest="90" CornerRadius="10" HorizontalOptions="Center"
BackgroundColor="White" ImageSource="{Binding ImagePath}" Clicked="Button_OnClicked"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
meow1.Text = (e.CurrentSelection.FirstOrDefault() as Car).NameImage;
}

Although not too much understanding the problem , but there is a suggestion about Button click event in CollectionView. We will use Command and CommandParameter of Button when binding model . And that is the design idea of MVVM.
For example , the Xaml code modeified as follow:
<StackLayout>
<Label x:Name="meow1"
Text="{Binding SelectedCarItem.NameImage}"
FontSize="Large"
VerticalOptions="Start"
HorizontalOptions="CenterAndExpand" />
<CollectionView ItemsSource="{Binding Cars}"
x:Name="phonesList"
HeightRequest="90"
ItemsLayout="HorizontalList"
BackgroundColor="Transparent"
SelectionMode="Single"
SelectedItem="{Binding SelectedCarItem}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame"
CornerRadius="10"
BackgroundColor="{Binding BgFrameColor}"
Padding="0"
HeightRequest="90"
WidthRequest="95">
<Grid Padding="0"
x:Name="meow">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
HeightRequest="90"
WidthRequest="90"
CornerRadius="10"
HorizontalOptions="Center"
BackgroundColor="{Binding BgButtonColor}"
ImageSource="{Binding ImagePath}"
Command="{Binding TapCommand}"
CommandParameter="{Binding Source={x:Reference frame}, Path=BindingContext}" />
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Then need to modify the Car model , adding BgColor,IsSelected and TapCommand property:
public class Car : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string NameImage { get; set; }
public string ImagePath { get; set; }
private Color bgFrameColor;
public Color BgFrameColor
{
set
{
if (bgFrameColor != value)
{
bgFrameColor = value;
OnPropertyChanged("BgFrameColor");
}
}
get
{
return bgFrameColor;
}
}
private Color bgButtonColor;
public Color BgButtonColor
{
set
{
if (bgButtonColor != value)
{
bgButtonColor = value;
OnPropertyChanged("BgButtonColor");
}
}
get
{
return bgButtonColor;
}
}
private bool isSelected;
public bool IsSelected
{
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
get
{
return isSelected;
}
}
public ICommand TapCommand
{
get
{
return new Command((e) =>
{
var item = (e as Car);
// logic on item
if (item.isSelected)
{
item.isSelected = false;
item.BgButtonColor = Color.White;
item.BgFrameColor = Color.Black;
PageCollectionView.SelectedCar.Remove(item);
MessagingCenter.Send<object, Car>(this, "Hi", new Car() {NameImage ="Welcome to the car home!" });
}
else
{
item.isSelected = true;
item.BgButtonColor = Color.Blue;
item.BgFrameColor = Color.Yellow;
if (PageCollectionView.SelectedCar.Count == 0)
{
PageCollectionView.SelectedCar.Add(item);
}
else
{
PageCollectionView.SelectedCar[0].isSelected = false;
PageCollectionView.SelectedCar[0].BgButtonColor = Color.White;
PageCollectionView.SelectedCar[0].BgFrameColor = Color.Black;
PageCollectionView.SelectedCar.Remove(PageCollectionView.SelectedCar[0]);
PageCollectionView.SelectedCar.Add(item);
}
MessagingCenter.Send<object, Car>(this, "Hi", item);
}
});
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Add CarModel class to load data :
public class CarModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<Car> Cars { get; set; }
//public static Car SelectedCarItem { set; get; }
public CarModel()
{
Cars = new List<Car>();
Cars.Add(new Car() { NameImage = "Lexus", ImagePath = "Lexus.png", BgButtonColor = Color.White, BgFrameColor = Color.Black, IsSelected = false }); ;
Cars.Add(new Car { NameImage = "Audi", ImagePath = "Audi.png", BgButtonColor = Color.White, BgFrameColor = Color.Black, IsSelected = false });
// set default text of label
selectedCarItem = new Car() { NameImage = "Welcome to the car home!" };
}
private Car selectedCarItem;
public Car SelectedCarItem
{
get
{
return selectedCarItem;
}
set
{
if (selectedCarItem != value)
{
selectedCarItem = value;
OnPropertyChanged("SelectedCarItem");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in ContentPage , declare a List<Car> to store only one item to keep this collection view is single selected. And use MessagingCenter to update carModel.SelectedCarItem here:
public partial class PageCollectionView : ContentPage
{
public static List<Car> SelectedCar { get; set; }
public PageCollectionView()
{
InitializeComponent();
CarModel carModel = new CarModel();
BindingContext = carModel;
SelectedCar = new List<Car>();
MessagingCenter.Subscribe<object,Car>(this, "Hi", (sender,arg) =>
{
// Do something whenever the "Hi" message is received
carModel.SelectedCarItem = arg;
});
}
}
The effect as follow :
Note: From the sample , you will see that using binding to modify BackgroundColor and Model Data. Therefore, it's not recommanded to use OnCollectionViewSelectionChanged to modify text of Lable .

Related

CheckBox create a string from the values of the selected objects

I have a collection view with a checkbox.
It looks like the following:
[Image][PetName][Checkbox]
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I have tried the following code but I am getting object reference is null in selectedPets.Add(ob) I'm sure Im proably going the wrong way with this but I am new to coding.
public List<PetProfile> selectedPets;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = sender as CheckBox;
var ob = checkbox.BindingContext as PetProfile;
if (ob != null)
{
selectedPets.Add(ob);
}
}
private string CreatePetName()
{
var stringBuilder = new StringBuilder();
var listlenght = selectedPets.Count;
foreach (var pet in selectedPets)
{
if (selectedPets.Count == 0)
{
stringBuilder.Append(pet.PetName);
}
else if (listlenght > 0 && pet == selectedPets[0] )
{
stringBuilder.Append(pet.PetName + " ");
}
else if (pet == selectedPets[listlenght])
{
stringBuilder.Append(" " + pet.PetName );
}
else
{
stringBuilder.Append(" " + pet.PetName + " ");
}
}
return stringBuilder.ToString();
}
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
var Petnames = CreatePetName();
}
XAML:
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}" HeightRequest="200">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" RowDefinitions="80" ColumnDefinitions="120,60,60">
<Image Grid.Column="0"
Grid.Row="0"
x:Name="PetImage"
Source="{Binding imageUrl}"/>
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<CheckBox Grid.Row="0" Grid.Column="2" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" BindingContext="{Binding .}" CheckedChanged="CheckBox_CheckedChanged"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I suggest you don't need to use CheckBox_CheckedChanged event to get selected PetName, you can create one class name Petclass, implementing INotifyPropertyChanged, to notify Selected property changed.
public class Petclass:ViewModelBase
{
public string imageUrl { get; set; }
public string PetName { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
RaisePropertyChanged("Selected");
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then loading some data to test in collectionview. foreach EmptyPetInfo Collectionview ItemsSource="{Binding EmptyPetInfo}" to check Selected property is true or false.
<CollectionView
x:Name="petCollectionView"
HeightRequest="200"
ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid
Padding="10"
ColumnDefinitions="120,60,60"
RowDefinitions="80">
<Image
x:Name="PetImage"
Grid.Row="0"
Grid.Column="0"
Source="{Binding imageUrl}" />
<Label
x:Name="labelpetname"
Grid.Row="0"
Grid.Column="1"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
Text="{Binding PetName}"
VerticalTextAlignment="Center" />
<CheckBox
Grid.Row="0"
Grid.Column="2"
HorizontalOptions="End"
IsChecked="{Binding Selected, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
public partial class Page17 : ContentPage
{
public ObservableCollection<Petclass> EmptyPetInfo { get; set; }
public Page17()
{
InitializeComponent();
EmptyPetInfo = new ObservableCollection<Petclass>()
{
new Petclass(){imageUrl="check.png",PetName="pet 1"},
new Petclass(){imageUrl="delete.png",PetName="pet 2"},
new Petclass(){imageUrl="favorite.png",PetName="pet 3"},
new Petclass(){imageUrl="flag.png",PetName="pet 4"}
};
this.BindingContext = this;
}
private void btn1_Clicked(object sender, EventArgs e)
{
var stringBuilder = new StringBuilder();
foreach (Petclass pet in EmptyPetInfo)
{
if(pet.Selected)
{
stringBuilder.Append(pet.PetName + " ");
}
}
string str = stringBuilder.ToString();
}
}
Using ObservableCollection Class, represent a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
you need to initialize selectedPets before you use it
public List<PetProfile> selectedPets = new List<PetProfile>();

xamarin forms - using '(ProductModel)picker.BindingContext;' causing extra unnecessary post back

Evening All,
Xamarin project using MVVM
Have a ProductModel with Id, Name and quantity.
ProductPage.xaml uses a listview to display a list of products. Picker, is binded to quantity, When a quantity is selected from the picker, OnPickerSelectedIndexChanged() is called and adds the product to an ObservableCollection DrinksToPurchaseList. When the user clicks shopping cart, the DrinksToPurchaseList is added to a global OC which is then iterated through on the shopping cart VM and displayed on screen.
If the user wants to change the quantity on the ShoppingCart page they can use the picker to select a new value. QuantityChanged() is called on the shopping cart page and updates accordingly. This all works.
The problem I am facing is after this all happens OnPickerSelectedIndexChanged() on the productPage is then called again, which has nothing to do with what should be happening.
I have commented out the code until I found the issue.
var item = (ProductModel)picker.BindingContext;
in ProductPage
void OnPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
var item = (ProductModel)picker.BindingContext;
//Dummy Data used to test
//var _productId = 1;
//var _quantity = "4";
//var _productName = "WINE 110011";
//ProductModel item = new ProductModel(){ ProductId = _productId, ProductName = _productName, Quantity = _quantity};
DrinksToPurchaseList.Add(item);
}
I have removed this line and added in DD to test and everything works as it should.
I need var item = (ProductModel)picker.BindingContext; to get the product and add it to the DrinksToPurchaseList.
Why is this happening and does anyone know a work around?
TY
public class ProductModel : INotifyPropertyChanged
{
//Event
public event PropertyChangedEventHandler PropertyChanged;
//Fields
private string _ProductName;
private string _Quantity;
//Constructor
public ProductModel()
{
//Subscription
this.PropertyChanged += OnPropertyChanged;
}
[PrimaryKey, AutoIncrement]
public int ProductId { get; set; }
//Properties
public string Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
OnPropertyChanged();
}
}
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
OnPropertyChanged();
}
}
//OnPropertyChanged
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Quantity))
{
//Do anything that needs doing when the Quantity changes here...
//var time = "hello
if (_Quantity == "0")
{
// Quantity = "6";
}
}
}
// [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ProductPageViewModel : BindableObject
{
public ObservableCollection<ProductModel> WineList { get; set; }
public ObservableCollection<ProductModel> AllDrinksList { get; set; }
public ProductPageViewModel()
{
WineList = new ObservableCollection<ProductModel>();
WineList.Add(new ProductModel
{
ProductId = 1,
ProductName = "Wine 101",
Quantity = "4",
});
AllDrinksList = new ObservableCollection<ProductModel>();
}
}
public partial class ProductPage : ContentPage
{
public ProductPageViewModel productPage_ViewModal;
private bool ZeroQuantitySelected = false;
MainPage RootPage { get => Application.Current.MainPage as MainPage; }
private ObservableCollection<ProductModel> DrinksToPurchaseList = new ObservableCollection<ProductModel>();
private bool isWineListVisible = false;
public ProductPage()
{
InitializeComponent();
productPage_ViewModal = new ProductPageViewModel();
BindingContext = productPage_ViewModal;
}
private async void ShoppingCartClicked(object sender, EventArgs e)
{
App.NewPageToLoad = "Shopping Cart";
MenuPage tempMenu = new MenuPage();
int IdOfMenuClicked = tempMenu.GetIdForNavigationMenu("Shopping Cart");
App.globalShoppingCartOC = DrinksToPurchaseList;
await RootPage.NavigateFromMenu(IdOfMenuClicked);
}
void OnPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
//BELOW LINE IS THE ISSUE CAUSING THE OTHER POST BACK
//var item = (ProductModel)picker.BindingContext;
//DrinksToPurchaseList.Add(item);
//Dummy Data used to test
var _productId = 1;
var _quantity = "4";
var _productName = "WINE 110011";
ProductModel item = new ProductModel() { ProductId = _productId, ProductName = _productName, Quantity = _quantity };
DrinksToPurchaseList.Add(item);
}
}
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScrollApp2.Views.ProductPage">
<ContentPage.ToolbarItems>
<ToolbarItem Name="shoppingCartImg" Icon="xamarin_logo.png" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
<ToolbarItem x:Name="NoItemsInShoppingCart" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<ListView x:Name="producttablelist" IsVisible="True" VerticalOptions="FillAndExpand" HasUnevenRows="True" ItemsSource="{Binding WineList}" HeightRequest="1500">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HeightRequest="120" BackgroundColor="Green" HorizontalOptions="StartAndExpand">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding ProductName}" TextColor="Black" VerticalOptions="Start"></Label>
<Picker Grid.Column="4" Grid.Row="2" VerticalOptions="Start" SelectedIndexChanged="OnPickerSelectedIndexChanged" SelectedIndex="{Binding Quantity, Mode=TwoWay}">
<Picker.Items>
<x:String>0</x:String>
<x:String>1</x:String>
<x:String>2</x:String>
<x:String>3</x:String>
<x:String>4</x:String>
<x:String>5</x:String>
<x:String>6</x:String>
</Picker.Items>
</Picker>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
public class ShoppingCartViewModel
{
public ObservableCollection<ProductModel> ShoppingCartList { get; set; }
public ShoppingCartViewModel()
{
ShoppingCartList = new ObservableCollection<ProductModel>();
}
}
<?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="ScrollApp2.Views.ShoppingCartPage">
<ContentPage.ToolbarItems>
<ToolbarItem Name="shoppingCartImg" Icon="shopping_cart.png" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
<ToolbarItem x:Name="NoItemsInShoppingCart" Priority="0" Order="Primary" Activated="ShoppingCartClicked"/>
</ContentPage.ToolbarItems>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80*"/>
<RowDefinition Height="20*"/>
</Grid.RowDefinitions>
<StackLayout Grid.Row="0">
<ListView ItemsSource="{Binding ShoppingCartList}" HasUnevenRows="True" SeparatorVisibility="None">
<ListView.Footer>
<Label x:Name="TotalForItems" HorizontalTextAlignment="End" VerticalTextAlignment="Start" Margin="20,20" FontAttributes="Bold"/>
</ListView.Footer>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid x:Name="ShoppingCartGrid" RowSpacing="25" ColumnSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Text="{Binding Id}" VerticalOptions="End" IsVisible="False"/>
<Label Grid.Column="2" Grid.Row="1" Text="{Binding ProductName}" VerticalOptions="End"/>
<Picker Grid.Column="4" Grid.Row="2" VerticalOptions="Start" SelectedIndexChanged="QuantityChanged" SelectedIndex="{Binding Quantity, Mode=TwoWay}">
<Picker.Items>
<x:String>0</x:String>
<x:String>1</x:String>
<x:String>2</x:String>
<x:String>3</x:String>
<x:String>4</x:String>
<x:String>5</x:String>
<x:String>6</x:String>
<x:String>7</x:String>
</Picker.Items>
</Picker>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Grid>
</ContentPage>
public partial class ShoppingCartPage : ContentPage
{
ShoppingCartViewModel ShoppingCartViewModel = new ShoppingCartViewModel();
private Dictionary<int, int> PickerUniquieIdDict = new Dictionary<int, int>();
public decimal TotalForAllItems;
public ShoppingCartPage ()
{
InitializeComponent();
if (App.globalShoppingCartOC != null)
{
// ProductModel P = new ProductModel();
//P.Add(new ProductModel { ProductId = 5, ProductName = "Gin", Image = "Gin.jpg", Description = "700ml", Price = 13.99M, Quantity = 0, SubTotalForItem = 0.00M, Genre = "Wine" });
//DUMMY DATA
//App.globalShoppingCartOC.Add(new ProductModel { ProductId = 5, ProductName = "Gin", Quantity="3" });
foreach (ProductModel Model in App.globalShoppingCartOC)
{
var _quantity = Convert.ToDecimal(Model.Quantity);
if (_quantity > 0)
{
ShoppingCartViewModel.ShoppingCartList.Add(Model);
}
}
}
if (App.GlobalWinePickerUniquieIdDict != null)
{
PickerUniquieIdDict = App.GlobalWinePickerUniquieIdDict;
}
NoItemsInShoppingCart.Text = App.NoOfItemsInShoppingCartGlobalVar;
this.BindingContext = this;
BindingContext = ShoppingCartViewModel;
}
void QuantityChanged(object sender, EventArgs e)
{
var x = 1;
}
void ShoppingCartClicked(object sender, EventArgs e)
{
var x = 1;
}
}
Problem is that, product page loads, user selectes quantity and sets it to 3 for example.
User clicks shopping cart image, shopping cart page loads, user can select picker on shopping cart page to change quantity if they do this, the code posts back to
void QuantityChanged(object sender, EventArgs e)
{
var x = 1;
}
on the shoping cart page, and then to the
void OnPickerSelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
//var item = (ProductModel)picker.BindingContext;
//Drink
on he product page.
Code shoudl not post back to OnPickerSelectedIndexChanged on the proudcpage from shopping cart page?
UPDATE
If you seen from my productModel.cs I am using INPC and it is working.
public string Quantity
{
get { return _Quantity; }
set
{
_Quantity = value;
OnPropertyChanged();
}
}
is being hit when the quantity on the picker is changed.
My Question -> Hoever I need to perform additional logic on the VM after this happens which I dont know how to access....(which is the reason I was trying to use OnPickerSelectedIndexChanged()) would the best approach be to override the OnPropertyChanged() from productModel and call it from the ProductPageViewModel?

Xamarin Forms Bind Image from Resources/Drawable folder

I have a problem. What I am trying to do is bind an ImageSource from a ViewModel. The image file name is called ledstrip.png and is located in the Resources/Drawable folder. I am using the following xaml:
<ListView ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<AbsoluteLayout HeightRequest="70" Margin="20,20,20,0">
<StackLayout Opacity="0.3" BackgroundColor="White"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All" />
<StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>
<Image Source="{Binding DeviceImage}" Grid.RowSpan="2" Grid.Column="0" Margin="5" />
</Grid>
</StackLayout>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And in my ViewModel I have the following code:
public class VM_DeviceList : BindableObject
{
private ObservableCollection<DisplayedDevice> _knownDeviceList;
public ObservableCollection<DisplayedDevice> knownDeviceList
{
get
{
return _knownDeviceList;
}
set
{
if (_knownDeviceList != value)
{
_knownDeviceList = value;
OnPropertyChanged();
}
}
}
public VM_DeviceList()
{
knownDeviceList = new ObservableCollection<DisplayedDevice>();
MyHandler();
}
private async Task LoadKnownDeviceList()
{
foreach (KnownDevice device in App.KnownDeviceList)
{
DisplayedDevice displayedDevice = new DisplayedDevice();
displayedDevice.Id = device.Id;
displayedDevice.Name = device.Name;
switch (device.Type)
{
case "ledstrip":
displayedDevice.DeviceImage = "ledstrip.png";
break;
case "triangle":
displayedDevice.DeviceImage = "triangle.png";
break;
}
knownDeviceList.Add(displayedDevice);
}
}
public Task MyHandler()
{
return LoadKnownDeviceList();
}
public class DisplayedDevice
{
public int Id { get; set; }
public string Name { get; set; }
public string DeviceImage { get; set; }
}
}
The problem is that when I type "ledstrip.png" in the xaml ImageSource, the image gets displayed, but when I bind it like the way I show above, no image appears on the screen!
What am I doing wrong and how can I fix this?
Since you are altering the DisplayImage on a separate for loop. You have to Notify the UI that the DisplayImage property value has been changed.
Use INotifyPropertyChanged for notifying that DisplayImage property of DisplayedDevice class has been changed to the UI.
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string deviceImage;
public int Id { get; set; }
public string Name { get; set; }
public string DeviceImage
{
get
{
return deviceImage;
}
set
{
deviceImage = value;
OnPropertyChanged();
}
}
}

ListView is not scrolling with grouping

I simply changed my ListView to use grouping, but now I can't use ScrollTo anymore.
I have create a simple app, so you can see the problem.
The XAML-page looks like (I am not using XAML in my app at the moment, but I will in an upcoming version).
<?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:local="clr-namespace:ScrollListExample"
x:Class="ScrollListExample.ProjectPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView x:Name="ProjectsListView" HasUnevenRows="True" IsGroupingEnabled="True" ItemsSource="{Binding Projects}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Path=Key}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" LineBreakMode="TailTruncation" Text="{Binding Path=ProjectName}" />
<Label Grid.Column="0" Grid.Row="1" Text="{Binding Path=ProjectReference, StringFormat='Sag: {0}'}" />
<Label Grid.Column="0" Grid.Row="2" Text="{Binding Path=CustomerName}" />
<Label Grid.Column="0" Grid.Row="3" Text="{Binding Path=FullAddress}" />
<Label Grid.Column="1" Grid.Row="0" Text="{Binding Path=StartTime}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
And the code-behind file for the example looks like this
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProjectPage : ContentPage
{
public ProjectPage()
{
InitializeComponent();
BindingContext = new ProjectsViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
Acr.UserDialogs.UserDialogs.Instance.ShowLoading();
var projects = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<ProjectDto>>("[{\"ProjectName\":\"Test sag\",\"ProjectReference\":\"10072\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 3\",\"StartDate\":\"2017-02-02T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"aaa\",\"ProjectReference\":\"10077\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 12\",\"StartDate\":\"2017-02-08T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10082\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10085\",\"CustomerName\":\"Testvej boligselskab\",\"FullAddress\":\"Testvej 14\",\"StartDate\":\"2017-02-24T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10086\",\"CustomerName\":\"Testing\",\"FullAddress\":\"Testevej 14\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test1\",\"ProjectReference\":\"10087\",\"CustomerName\":\"Plejecenter testlyst\",\"FullAddress\":\"Testlystvej 11\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test2\",\"ProjectReference\":\"10088\",\"CustomerName\":\"Charlie\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10089\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10090\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10091\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10092\",\"CustomerName\":\"Tester\",\"FullAddress\":\"Testvej 11\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10093\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10094\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"}]");
var viewModel = BindingContext as ProjectsViewModel;
if (viewModel != null)
viewModel.OriginalProjects = projects;
Acr.UserDialogs.UserDialogs.Instance.ShowLoading("Loading");
Task.Delay(5000).ContinueWith((x) =>
{
Device.BeginInvokeOnMainThread(Acr.UserDialogs.UserDialogs.Instance.HideLoading);
Search();
});
}
private void Search(string inputVal = null)
{
var viewModel = BindingContext as ProjectsViewModel;
if (viewModel != null)
{
var projects = viewModel.OriginalProjects.Where(p => !string.IsNullOrEmpty(inputVal) ? p.ProjectName.Contains(inputVal) : true);
var orderedProjects = projects.OrderBy(p => p.StartDate);
Device.BeginInvokeOnMainThread(() =>
{
foreach (ProjectDto project in orderedProjects)
{
var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);
if (coll == null)
viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });
coll.Add(project);
}
var group = viewModel.Projects.LastOrDefault();
if (group != null)
ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);
});
}
}
}
class ProjectsViewModel : INotifyPropertyChanged
{
private ObservableCollection<ObservableCollectionWithDateKey> _projects;
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<ProjectDto> OriginalProjects { get; set; }
public ObservableCollection<ObservableCollectionWithDateKey> Projects
{
get { return _projects; }
set
{
_projects = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Projects)));
}
}
public ProjectsViewModel()
{
Projects = new ObservableCollection<ObservableCollectionWithDateKey>();
}
}
public class ProjectDto : INotifyPropertyChanged
{
public string ProjectName { get; set; }
public string ProjectReference { get; set; }
public string CustomerName { get; set; }
public string FullAddress { get; set; }
public DateTime StartDate { get; set; }
public string StartTime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
class ObservableCollectionWithDateKey : ObservableCollection<ProjectDto>
{
public DateTime Key { get; set; }
}
I use Task.Delay(5000) to simulate a response from the server, but I do not think, it matters.
UPDATE
I figured out, the problem was in my ScrollTo-call, where ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false); was called with the Key instead of just the group.
If you create the grouping first (without adding it to the ViewModel), you have to find the correct model in the ViewModel afterwards. As it otherwise does not find the correct ObservableCollection
I have tested your code and reproduced your issue. The problem is you have passed the wrong parameter to ScrollTo method.
ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);
The group parameter of ScrollTo method is the group from your ListView.ItemsSource. But your passed a group.Key. So the method will not be excited as expect. Please modify the code like following.
Device.BeginInvokeOnMainThread(() =>
{
foreach (ProjectDto project in orderedProjects)
{
var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);
if (coll == null)
viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });
coll.Add(project);
}
var group = viewModel.Projects.Last();
if (group != null)
ProjectsListView.ScrollTo(group.First(), group, ScrollToPosition.Start, false);
});

How to populate a list of custom user controls with the contents of a bound property

Say I have the following on my viewmodel:
Public ObservableCollection<SubItem> SubItems
Now, each SubItem has the following properties:
Public String Part1
Public String Part2
Given this, I want a ListBox which displays Part1 and Part2 in editable text boxes, one ontop of the other, for each SubItem in SubItems.
To do this, I have created a SubItemUserControl, which is currently just those two text boxes:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TextBox/>
<TextBox Grid.Row="1"/>
</Grid>
I have then set the ListBox on the MainWindow control as follows:
<ListBox ItemsSource="{Binding Path=SubItems}" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<local:SubItemUserControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This displays two empty text boxes for each SubItem.
What I don't know, is how to bind the two properties in the SubItem to the two text-boxes in the SubItemUserControl.
1.Create SubItemModel which implement INotifyPropertyChange Interface:
public class SubItem : BindableBase
{
private string _part1;
public string Part1
{
get
{
return _part1;
}
set
{
_part1 = value;
OnPropertyChanged("Part1");
}
}
private string _part2;
public string Part2
{
get
{
return _part2;
}
set
{
_part2 = value;
OnPropertyChanged("Part2");
}
}
}
2.Initialize your ObservableCollection collection in yout ViewModel:
private ObservableCollection <SubItem> _subItems;
public ObservableCollection<SubItem> SubItems
{
get
{
return _subItems;
}
set
{
_subItems = value;
OnPropertyChanged("SubItems ");
}
}
public MainViewModel()
{
_subItems = new ObservableCollection<SubItem>
{
new SubItem{Part1 = "AP1", Part2 = "AP2"},
new SubItem{Part1 = "BP1", Part2 = "BP2"},
new SubItem{Part1 = "CP1", Part2 = "CP2"},
};
}
3.Set your View when Part1 is on top of Part2 and bind your properties:
<ListBox ItemsSource="{Binding Path=SubItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" x:Name="Part1" Text="{Binding Part1}"/>
<TextBox Grid.Row="1" x:Name="Part2" Text="{Binding Part2}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
4.Implement your BindableBase:
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}

Categories