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>();
Related
I am in a Xamarin app I which I have to make a to-do list. I am using listview. I have to set a label text dynamically every time I add a new to-do like so: Number of tasks 2/4 where 2 are the done tasks and 4 are the total ones. Everything goes right but I found some issues when I have to update the label text. I am using the MVVM pattern. In XAML I bind the text value to SetInfoDoneText. In MainPage, I have the bindigContext set to VM(TodoListViewModel). I use INotifyPropertyChanged with OnPropertyChanged. I made a method in which the value of setInfoDoneText is changed. The problem is that the set and get is called only once and when the setInfoDoneText is updated by the method OnPropertyChanged does not fire again. Here is the code.
THE PROBLEM IS WHEN I TRY TO UPDATE lblDoneInfo text (
)
class TodoListViewModel: INotifyPropertyChanged
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public String setInfoDoneText;
public String SetInfoDoneText
{
get => setInfoDoneText;
set
{
setInfoDoneText = value;
OnPropertyChanged("SetInfoDoneText");
}
}
protected void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int doneTask = 0;
int totalTask = 0;
public TodoListViewModel()
{
this.todoItems = new ObservableCollection<TodoItem>();
setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
public ICommand AddTodoCommand => new Command(AddTodoItem);
public String newTodoInputValue { get; set; }
public String selectedItem { get; set; }
public bool highPriority { get; set; }
public bool midPriority { get; set; }
public bool lowPriority { get; set; }
Color newColor = new Color();
public void AddTodoItem()
{
if (highPriority)
{
newColor = Color.Red;
AddNewItem(newColor);
highPriority = false;
}
if (midPriority)
{
newColor = Color.Orange;
AddNewItem(newColor);
midPriority = false;
}
if (lowPriority)
{
newColor = Color.Yellow;
AddNewItem(newColor);
lowPriority = false;
}
}
public TodoItem AddNewItem(Color newColor)
{
TodoItem newItem = new TodoItem(newTodoInputValue,
false,
highPriority,
midPriority,
lowPriority,
newColor);
todoItems.Add(newItem);
UpdateDoneInfo();
return newItem;
}
public ICommand RemoveTodoCommand => new Command(RemoveTodoItem);
public void RemoveTodoItem(object o)
{
TodoItem todoItemBeingRemoved = o as TodoItem;
todoItems.Remove(todoItemBeingRemoved);
}
public ICommand EditTodoCommand => new Command(EditTodoItem);
public void EditTodoItem(object o)
{
TodoItem todoItemBeingEdited = o as TodoItem;
int newIndex = todoItems.IndexOf(todoItemBeingEdited);
todoItems.Remove(todoItemBeingEdited);
TodoItem updatedTodo = AddNewItem(newColor);
//todoItems.Add(updatedTodo);
int oldIndex = todoItems.IndexOf(updatedTodo);
todoItems.Move(oldIndex, newIndex);
}
public String UpdateDoneInfo()
{
totalTask = todoItems.Count;
foreach (TodoItem item in todoItems)
{
if (item.complete) doneTask++;
}
return setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
}
<ContentPage.BindingContext>
<local:TodoListViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Entry
x:Name="inputField"
Text="{Binding newTodoInputValue}"
Placeholder="Enter a todo..."
/>
<Label x:Name="lblDoneInfo" Text="{Binding SetInfoDoneText, Mode=TwoWay }">
</Label>
<FlexLayout AlignItems="Center" JustifyContent="SpaceBetween">
<input:CheckBox x:Name="highP"
IsChecked="{Binding highPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="High Priority" FontSize="Medium"/>
<input:CheckBox x:Name="midP"
IsChecked="{Binding midPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Medium Priority" FontSize="Medium"/>
<input:CheckBox x:Name="lowP"
IsChecked="{Binding lowPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Low Priority" FontSize="Medium"/>
</FlexLayout>
<ListView x:Name="todoList" ItemsSource="{Binding todoItems}" SelectedItem="{Binding selectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="20">
<FlexLayout JustifyContent="SpaceBetween" AlignItems="Center" Padding="20,0">
<ContentView>
<FlexLayout AlignItems="Center">
<input:CheckBox IsChecked="{Binding complete}" Margin="5" />
<Label x:Name="todoText" TextColor="{Binding color}" Text="{Binding todoText}" FontSize="Large"/>
</FlexLayout>
</ContentView>
<ImageButton
Source="editar_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Margin="0,0,20,0"
Command="{Binding Path=BindingContext.EditTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
<ImageButton
Source="basura_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Command="{Binding Path=BindingContext.RemoveTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
</FlexLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
PropertyChanged only fires when you set the value of the public property SetInfoDoneText. Your code is setting the value of the field setInfoDoneText (lowercase).
It is generally best practice to make the field private to prevent this
private String setInfoDoneText;
I have an application where items are added to a collection view and then you can tap on that item where a detail page comes up. With this detail page, you can edit the title and description given and then it should update in the collection view. However, when changes are made to the item, the item is not changed within the observable collection. How do I go about this?
Here is my xaml for the detail page:
<StackLayout Spacing="5" Padding="10" AnchorY="0.5">
<!--<Label Text="Title:" FontSize="Medium" FontAttributes="Bold" TextColor="White"/>-->
<Editor Text="{Binding Text}" FontSize="Title" TextColor="White"/>
<!--<Label Text="Note:" FontSize="Medium" FontAttributes="Bold" TextColor="White"/>-->
<Editor Text="{Binding Description}" FontSize="Small" TextColor="White"/>
<Grid ColumnDefinitions="*,*,*">
<Label Grid.Column="0" Grid.ColumnSpan="2" Text="Due Date:" FontSize="Medium" FontAttributes="Bold" TextColor="White" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Label Grid.Column="0" Grid.ColumnSpan="2" Padding="50,4,0,0" Text="{Binding DueDate}" FontSize="Small" TextColor="White" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"/>
</Grid>
<Button Text="Save"
Command="{Binding SaveChangesCommand}"
VerticalOptions="EndAndExpand"
HorizontalOptions="EndAndExpand"
BackgroundColor="Black"
BorderWidth="1.5"
BorderColor="White"
CornerRadius="50">
</Button>
</StackLayout>
Here is my DataBaseService:
public async Task UpdateItem(Item item)
{
var databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyData3.db");
var db = new SQLiteAsyncConnection(databasePath);
await db.UpdateAsync(item);
OnUpdatedItem?.Invoke();
}
OnUpdateItem?.Invoked();
private void LocalDatabaseService_OnUpdatedItem()
{
_ = ExecuteLoadItemsCommand();
}
and here is the OnSaveChanges
private async void OnSaveChanges(Item item)
{
Text = Text;
Description = Description;
DueDate = DueDate;
await LocalDatabaseService.UpdateItem(item);
await Shell.Current.GoToAsync("..");
}
LoadItems:
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await LocalDatabaseService.GetAllItems();
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
Item Model:
public class Item : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Text
{
get { return Text; }
set
{
Text = value;
if (Text != value)
{
OnPropertyChanged("Text");
Text = value;
}
}
}
public string Description
{
get { return Description; }
set
{ Description = value;
if(Description != value)
{
OnPropertyChanged("Description");
Description = value;
}
}
}
public DateTime DueDate { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
any help is appreciated, Thanks.
From the code you posted, we found the parameter in method OnSaveChanges is useless since it is always null.
You can change your code like this in class ItemDetailViewModel:
public Command SaveChangesCommand { get; }
public ItemDetailViewModel()
{
SaveChangesCommand = new Command(OnSaveChanges);
}
And method OnSaveChanges:
private async void OnSaveChanges()
{
Item item = new Item();
item.Id = Int32.Parse(ItemId);
item.Text = Text;
item.Description = Description;
item.DueDate = DueDate;
await LocalDatabaseService.UpdateItem(item);
await Shell.Current.GoToAsync("..");
}
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 .
public class Zicker : INotifyPropertyChanged
{
public class MyClass
{
public string HeyName { get; set; }
public string HeySurname { get; set; }
public int HeyAge { get; set; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string name = null)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<MyClass> _yourList = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass> YourList
{
get
{
return _yourList;
}
set
{
_yourList = value;
RaisePropertyChanged("YourList");
RaisePropertyChanged("BindMeLabel");
}
}
public int BindMeLabel
{
get { return _yourList.Sum(a => a.HeyAge); }
}
public void WonCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("BindMeLabel");
}
public List<string> heresamplenames = new List<string> { "Mohamed", "Zaran", "Ivan" };
public List<string> heresamplesurnames = new List<string> { "Pakou", "Simmone", "Zagoev" };
public List<int> heresampleages = new List<int> { 17,33,50 };
public Zicker()
{
ObservableCollection<MyClass> vs = new ObservableCollection<MyClass>();
for (int i = 0; i < 3; i++)
{ vs.Add(new MyClass { HeyName = heresamplenames[i], HeySurname = heresamplesurnames[i], HeyAge = heresampleages[i] }); }
YourList = vs; YourList.CollectionChanged += WonCollectionChanged;
}
}
<ContentPage.Content>
<StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center">
<ContentView HorizontalOptions="Fill" VerticalOptions="Fill">
<ListView HorizontalOptions="Center" VerticalOptions="Center" HasUnevenRows="True" ItemsSource="{Binding YourList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Text="{Binding Path=HeyName}" Grid.Column="0" FontSize="12" TextColor="Black"></Label>
<Label HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Text="{Binding Path=HeySurname}" FontSize="12" TextColor="Black" Grid.Column="1"/>
<Entry HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Path=HeyAge}" FontSize="12" Keyboard="Numeric" TextColor="Black" Grid.Column="2"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentView>
<Label Text="{Binding BindMeLabel}" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="40" TextColor="Black"></Label>
</StackLayout>
</ContentPage.Content>
public MainPage()
{
InitializeComponent();
BindingContext = new Zicker();
}
My Problem: In this List, there are three names, surnames, and ages. At the bottom, there is also a label which should be shown as the sum of Ages collection.
When the UI is starting, Label is working well. But, if I try to change any Ages entries, there is a big problem with the binding label.
I want to use MVVM structure but due to this problem, label binding is working just start up.
If you are updating the HeyName property, binding is not updating because the class MyClass does not implement INotifyPropertyChanged.
Try to replace the MyClass class with this code:
public class MyClass : INotifyPropertyChanged
{
private string name;
private string surname;
private int age;
public string HeyName
{
get => name;
set
{
name = value;
RaisePropertyChanged("HeyName");
}
}
public string HeySurname
{
get => surname;
set
{
surname = value;
RaisePropertyChanged("HeySurname");
}
}
public int HeyAge
{
get => age;
set
{
age = value;
RaisePropertyChanged("HeyAge");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string name = null)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
EDIT:
Sorry, the CollectionChanged is not called when you update the HeyAge property, because it is called only if the collection is changed, but not when a property of an item in the collection changes.
Try to add the OnAgeChanged event into the class MyClass and call it when the HeyAge property change:
public class MyClass : INotifyPropertyChanged
{
public event EventHandler OnAgeChanged;
public int HeyAge
{
get => age;
set
{
age = value;
RaisePropertyChanged("HeyAge");
OnAgeChanged?.Invoke(this, EventArgs.Empty);
}
}
...
...
Then, when you add a new MyClass object into the collection, register the event in the ViewModel like this:
public Zicker()
{
ObservableCollection<MyClass> vs = new ObservableCollection<MyClass>();
for (int i = 0; i < 3; i++)
{
var test = new MyClass()
{
HeyName = heresamplenames[i],
HeySurname = heresamplesurnames[i],
HeyAge = heresampleages[i],
};
test.OnAgeChanged += Test_OnAgeChanged;
vs.Add(test);
}
YourList = vs;
YourList.CollectionChanged += WonCollectionChanged;
}
private void Test_OnAgeChanged(object sender, EventArgs e)
{
RaisePropertyChanged("BindMeLabel");
}
Note that the WonCollectionChanged it's not necessary any more.
Note also that the variable vs is not needed, you can work directly into the YourList object instead.
I want to generate ListItemBox using DataTemplate but items are not generating. Please guide me where is the mistake. I have following code in MainWindow.xaml.
<Window x:Class="Offline_Website_Downloader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bd="clr-namespace:Offline_Website_Downloader"
Title="Offline Website Downloader" Background="#f5f6f7" Height="500" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<bd:BindingController x:Key="BindingControllerKey" />
<DataTemplate x:Key="DownloadedWebsitesListBox">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock FontWeight="Bold" FontSize="18" Width="480">
<Hyperlink NavigateUri="http://google.com">
<Label Content="{Binding Path=WebsiteTitle}" />
</Hyperlink>
</TextBlock>
<TextBlock Width="132" TextAlignment="right">
<TextBlock Text="Remaining Time: "/>
<TextBlock Name="TimeRemaining" Text="js"/>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ProgressBar Name="progress1" Maximum="100" Minimum="0" Value="30" Background="#FFF" Width="612" Height="10" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Width="450">Status: <TextBlock Text="{Binding Path=Status}"/></TextBlock>
<TextBlock Width="162" TextAlignment="right">
<TextBlock Text="Downloading Speed: "/>
<TextBlock Name="DownloadingSpeed" Text="{Binding Path=DownloadingSpeed}"/>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
</Grid>
</window>
and MainWindow.xaml.cs
BindingController bc = new BindingController();
public MainWindow()
{
InitializeComponent();
bc.DownloadingSpeed = "40kb/s";
bc.WebsiteTitle = "WebsiteTitle";
bc.Status = "Downloading";
DataContext = bc;
}
and BindingController.cs
public class BindingController
{
public BindingController()
{
}
private string _WebsiteTitle;
public string WebsiteTitle
{
set { _WebsiteTitle = value; }
get { return _WebsiteTitle; }
}
private string _Status;
public string Status
{
set { _Status = value ; }
get { return _Status ; }
}
private string _DownloadStartDate;
public string DownloadStartDate
{
set { _DownloadStartDate = value; }
get { return _DownloadStartDate ; }
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
set { _DownloadingSpeed = value; }
get { return _DownloadingSpeed; }
}
}
Your problem is that you're binding a ListBox to an object that contains several properties, but really only represents a single object/state. The ListBox expects to display a list of items (i.e. IList, IBindingList, IEnumerable, ObservableCollection).
Assuming you want to display more than one download at a time, which I'm assuming given that you're using a ListBox, I would refactor the download properties into a separate class. You will also need to implement INotifyPropertyChanged on your properties so that when the values are changed, they will be shown in the UI.
public class Download : INotifyPropertyChanged
{
private string _WebsiteTitle;
public string WebsiteTitle
{
get { return _WebsiteTitle; }
set
{
if (_WebsiteTitle == value)
return;
_WebsiteTitle = value;
this.OnPropertyChanged("WebsiteTitle");
}
}
private string _Status;
public string Status
{
get { return _Status; }
set
{
if (_Status == value)
return;
_Status = value;
this.OnPropertyChanged("Status");
}
}
private string _DownloadStartDate;
public string DownloadStartDate
{
get { return _DownloadStartDate; }
set
{
if (_DownloadStartDate == value)
return;
_DownloadStartDate = value;
this.OnPropertyChanged("DownloadStartDate");
}
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
get { return _DownloadingSpeed; }
set
{
if (_DownloadingSpeed == value)
return;
_DownloadingSpeed = value;
this.OnPropertyChanged("DownloadingSpeed");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The new BindingController:
public class BindingController
{
public BindingController()
{
this.Downloads = new ObservableCollection<Download>();
}
public ObservableCollection<Download> Downloads { get; private set; }
}
Setting up the bindings in XAML:
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding Downloads}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
Initializing the collection in MainWindow
Download download = new Download();
download.DownloadingSpeed = "40kb/s";
download.WebsiteTitle = "WebsiteTitle";
download.Status = "Downloading";
bc.Downloads.Add(download);
this.DataContext = bc;