I have a problem. I created a ListView with a ViewModel. In my ListView I have a few Labels with Text that is bound to the objects in the ItemSource. Now when I change a value in the ViewModel of an item in the ObservableCollection, nothing changes on the screen!
Here is my ViewModel:
public class VM_DeviceList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand cmdDeleteDevice
{
get
{
return new Command<int>((x) => DeleteDevice_Handler(x));
}
}
public ICommand cmdTogglePower
{
get
{
return new Command<int>((x) => TogglePower_Handler(x));
}
}
private ObservableCollection<DisplayedDevice> _knownDeviceList;
public ObservableCollection<DisplayedDevice> knownDeviceList
{
get
{
return _knownDeviceList;
}
set
{
if (_knownDeviceList != value)
{
_knownDeviceList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("knownDeviceList"));
}
}
}
}
Here is my class for the ObservableCollection:
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int Id { get; set; }
public string Name { get; set; }
public string State { get; set; }
public string StateShown { get; set; }
public string deviceImage { get; set; }
public string Color { get; set; }
public string PowerStateColor { get; set; }
public string DeviceImageColor { get; set; }
public string DeviceImage
{
get
{
return deviceImage;
}
set
{
deviceImage = value;
OnPropertyChanged();
}
}
}
And here is the xaml:
<ListView ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90" ItemTapped="device_Clicked" x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<AbsoluteLayout HeightRequest="70" Margin="20,10,20,10">
<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>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Name}" Margin="0,3,0,0"
FontAttributes="Bold" FontSize="24" TextColor="White" />
<Label Grid.Row="1" Grid.Column="1" Text="{Binding StateShown}" FontSize="18" TextColor="White" />
<!-- <Image Source="power" Grid.RowSpan="2" Grid.Column="2" Margin="5" /> -->
<controls:IconView x:Name="btnPower" Source="power" Grid.RowSpan="2" Grid.Column="2" Margin="5"
Foreground="{Binding PowerStateColor}">
<controls:IconView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.cmdTogglePower, Source={x:Reference MyListView}}" CommandParameter="{Binding Id}" />
</controls:IconView.GestureRecognizers>
</controls:IconView>
</Grid>
</StackLayout>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now what I expected to happen, was that when I click on the IconView I change the IconView color and the label with the StateShown binding. But nothing changes when I click on the IconView!
What am I doing wrong?
Add OnPropertyChanged method call to every property on DisplayedDevice for those you want the UI to notice.
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged();
}
}
private int id;
public int Id
{
get
{
return id;
}
set
{
Id = value;
OnPropertyChanged();
}
}
private string state;
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged();
}
}
......
}
You can use MVVMHelpers NuGet Package and implement the ObservableObject Class from the NuGet directly to DisplayedDevice class and you need to make all the properties with a reference to the private variable.
public class DisplayedDevice: ObservableObject
{
string _textField = string.Empty;
public string TextField
{
get => _textField;
set => SetProperty(ref _textField, value);
}
bool _isBarChartVisible = false;
public bool IsBarChartVisible
{
get => _isBarChartVisible;
set => SetProperty(ref _isBarChartVisible, value);
}
}
}
Every public property should be backed by a private property of the same type. This is important so that any change in any property will be reflected on the UI using INotifyPropertyChanged
Related
I have two pages: MainPage and FilterPage(modal page).
with their respective Viewmodels: MainViewModel and FilterViewModel.
In MainPage I have a listview that's populated with data from an API. The data is passed to the FilterPage where it is filtered by some specific criteria. In the end a new list is created which is assigned to the binded variable of the listview. What I noticed is that after the modal page closes the listview's items arent updated. What is the proper way to do this?
Model:
public class Multilist
{
public string Title { get; set; }
public string Date { get; set; }
public string Status { get; set; }
public string Customer { get; set; }
}
MainViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private IList<Multilist> mainList = new List<Multilist>();
public IList<Multilist> MainList
{
get => mainList;
set
{
if (value == mainList)
return;
mainList = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public MainViewModel(INavigation navigation)
{
this._navigation = navigation;
Task.Run(async () => await GetData());
GotoFilterPageCommand = new AsyncCommand(GotoFilterPage);
}
private async Task GetData()
{
//Gets data from API
MainList = data;
}
private async Task GotoFilterPage()
{
await this._navigation.PushModalAsync(new FilterPage(MainList.ToList()), true);
}
}
FilterViewModel:
public class FilterViewModel : INotifyPropertyChange
{
public List<Multilist> OldList { get; set; }
private IList<Multilist> mainList = new List<Multilist>();
public IList<Multilist> MainList
{
get => mainList;
set
{
if (value == mainList)
return;
mainList = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public FilterViewModel(List<Multilist> oldlist)
{
Oldlist = oldlist;
SomeCommand = new AsyncCommand(SomeTask);
}
private async Task SomeTask()
{
// Some code here
CreateNewList(OldList);
}
private async Task CreateNewList(List<Multilist> oldlist)
{
//Some code here --> newMainList
pageA.MainList = newMainList;
await App.Current.MainPage.Navigation.PopModalAsync();
}
}
The listview in MainPage:
<ListView x:Name="TestListView"
ItemsSource="{Binding MainList}"
Grid.Row="4" Grid.ColumnSpan="3"
HasUnevenRows="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="0,0,0,1">
<Grid VerticalOptions="Fill" Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding Title}" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="0" />
<Label Text="{Binding Date}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="1" />
<Label Text="{Binding Customer}" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="2" />
<Label Text="{Binding Status}" HorizontalTextAlignment="End" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="3" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You could use Singleton to make a global instance for both MainViewModel and FilterViewModel.
I make a simple example for your reference.
Model:
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public int Age { get; set; }
}
ViewModel:
public class PersonViewModel
{
#region Singleton Pattern
private PersonViewModel()
{
Persons = new ObservableCollection<Person>()
{
new Person(){ Name="A"},
new Person(){ Name="A2"},
new Person(){ Name="A3"},
new Person(){ Name="A4"},
};
}
public static PersonViewModel Instance { get; } = new PersonViewModel();
#endregion
private ObservableCollection<Person> _person;
public ObservableCollection<Person> Persons
{
get { return _person; }
set { _person = value; }
}
}
Page24: //MainPage
<StackLayout>
<ListView ItemsSource="{Binding MainList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Navigate To FilterPage" Clicked="Button_Clicked">
</Button>
</StackLayout>
Page24 Code behind:
public Page24()
{
InitializeComponent();
this.BindingContext = new Page24ViewModel();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new FilterPage());
}
Page24ViewModel://MainViewModel
public class Page24ViewModel : INotifyPropertyChanged
{
private PersonViewModel _personViewModel;
public Page24ViewModel()
{
_personViewModel = PersonViewModel.Instance;
}
private ObservableCollection<Person> mainList;
public ObservableCollection<Person> MainList
{
get { return _personViewModel.Persons; }
set
{
mainList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
FilterPage:
<ContentPage.Content>
<StackLayout>
<StackLayout>
<Label Text="Name:"></Label>
<Entry x:Name="entry"></Entry>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="btn_Add" Text="Add" Clicked="btn_Add_Clicked"></Button>
<!--<Button x:Name="btn_Delete" Text="Delete" Clicked="btn_Delete_Clicked"></Button>-->
</StackLayout>
<ListView ItemsSource="{Binding MainList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
FilterPage code behind:
private PersonViewModel _personViewModel;
public FilterPage()//modal page
{
InitializeComponent();
this.BindingContext = new FilterViewModel();
}
private void btn_Add_Clicked(object sender, EventArgs e)
{
_personViewModel = PersonViewModel.Instance;
_personViewModel.Persons.Add(new Person() { Name = entry.Text });
}
FilterViewModel:
public class FilterViewModel : INotifyPropertyChanged
{
private PersonViewModel _personViewModel;
public FilterViewModel()
{
_personViewModel = PersonViewModel.Instance;
}
private ObservableCollection<Person> newMainList;
public ObservableCollection<Person> MainList
{
get { return _personViewModel.Persons; }
set
{
newMainList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I just began working with the MVVM layout and I cannot seem to display anything in my collectionView List. I believe it's my binding, unfortunately I don't really understand how I am supposed to bind a grouped list + the ViewModel. I read to bind to the path no the source, but I'm pretty sure I am doing this incorrectly. I have checked to see if I am even getting shares to load and I am, they're just not displaying.
Model -- Share
[JsonProperty("iSpottedID")]
public int ID { get; set; }
[JsonProperty("sShoppingList")]
[MaxLength(255)]
public string ShoppingName { get; set; }
[JsonProperty("dtInfoUpdate")]
[MaxLength(20)]
public string CreateDate { get; set; }
[JsonProperty("iProductID")]
public int ProductID { get; set; }
[Indexed]
[JsonProperty("sLocation")]
[MaxLength(255)]
public string LocationName { get; set; }
[JsonProperty("tvCardJson")]
public string JsonString { get; set; }
ViewModel -- SharesViewModel
public class SharesViewModel : BaseViewModel
{
#region Properties
private int _id;
public int ID
{
get { return _id; }
set
{
SetValue(ref _id, value);
OnPropertyChanged(nameof(ID));
}
}
private string _longName;
public string LongName
{
get { return _longName; }
set
{
SetValue(ref _longName, value);
OnPropertyChanged(nameof(LongName));
}
}
private string _date;
public string CreateDate
{
get{ return _date;}
set
{
SetValue(ref _date, value);
OnPropertyChanged(nameof(CreateDate));
}
}
private int _prodID;
public int ProductID
{
get { return _id; }
set
{
SetValue(ref _prodID, value);
OnPropertyChanged(nameof(ProductID));
}
}
private string _json;
public string JsonString
{
get { return _json; }
set
{
SetValue(ref _json, value);
OnPropertyChanged(nameof(JsonString));
}
}
private string _location;
public string LocationName
{
get { return _location; }
set
{
SetValue(ref _location, value);
OnPropertyChanged(nameof(LocationName));
}
}
//ADD-ONS
public string Address
{
get
{
if (!string.IsNullOrEmpty(JsonString))
{
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonString);
if (jsonDict.ContainsKey("address"))
if (jsonDict["address"] != "")
return jsonDict["address"];
}
return null;
}
}
private ImageSource _imageLink;
public ImageSource ImageLink
{
get
{
if(ProductID != 0)
{
...
return ImageSource.FromUri(link);
}
return null;
}
}
#endregion
public SharesViewModel(){}
public SharesViewModel(Share share)
{
ID = share.ID;
ProductID = share.ProductID;
JsonString = share.JsonString;
CreateDate = share.CreateDate;
LocationName = share.LocationName;
}
List View Model -- SharesListViewlModel
public class SharesListViewModel : BaseViewModel
{
private SharesViewModel _selectedShare;
private bool _isDataLoaded;
//grouped list
public ObservableCollection<LocationSpotGroups<string, SharesViewModel>> Shares { get; set; }
...
public ICommand OpenMoreSharesCommand { get; private set; }
public ICommand LoadDataCommand { get; private set; }
public SharesListViewModel(Position NW , Position SE)
{
_nw = NW;
_se = SE;
LoadDataCommand = new Command(async () => await LoadData());
OpenMoreSharesCommand = new Command<SharesViewModel>(async (share) => await OpenMoreShares(share));
public ObservableCollection<SharesViewModel> sList { get; set; }
= new ObservableCollection<SharesViewModel>();
}
private async Task LoadData()
{
if (_isDataLoaded)
return;
var list = await _connection.GetAllRegionShares(_nw, _se);
foreach (var spot in list)
{
sList.Add(new SharesViewModel(spot));
}
var sorted = from item in sList
orderby item.LocationName
group item by item.LocationName into itemGroup
select new LocationSpotGroups<string, SharesViewModel>(itemGroup.Key, itemGroup);
Shares = new ObservableCollection<LocationSpotGroups<string, SharesViewModel>>(sorted);
}
LocationSpotGroups
public class LocationSpotGroups<K, T> : ObservableCollection<T>
{
public K GroupKey { get; set; }
public IEnumerable<T> GroupedItem { get; set; }
public LocationSpotGroups(K key, IEnumerable<T> shares)
{
GroupKey = key;
GroupedItem = shares;
foreach (var item in shares)
{
this.Items.Add(item);
}
}
}
SharesPage XAML
<CollectionView x:Name="CollectionList"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding Shares}"
IsGrouped="True">
<!--HEADER-->
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal"
Padding="5"
BackgroundColor="#f7f7fb">
<Label x:Name="labelname"
Text="{Binding GroupKey}"
HorizontalOptions="Start"
VerticalOptions="Center"
TextColor="gray" />
</StackLayout>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="2" />
</CollectionView.ItemsLayout>
<!--BODY-->
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="5" Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ImageButton Source="{Binding ImageLink}"
WidthRequest="150"
HeightRequest="150"
Grid.ColumnSpan="2"
CornerRadius="15"
Aspect="AspectFill"
Grid.Row="0"
Grid.Column="0"/>
<Label Text="{Binding ShoppingName}"
Grid.Row="1"
Grid.Column="0"/>
<Label Text="More"
Grid.Row="1"
Grid.Column="1"
HorizontalTextAlignment="End"/>
<Label Text="{Binding CreateDate}"
Grid.Row="2"
Grid.Column="0"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
SharesPage CS
public SharesPage( Position NW, Position SE )
{
InitializeComponent();
ViewModel = new SharesListViewModel(NW, SE);
}
public SharesListViewModel ViewModel
{
get { return BindingContext as SharesListViewModel; }
set { BindingContext = value; }
}
protected override void OnAppearing()
{
ViewModel.LoadDataCommand.Execute(null);
base.OnAppearing();
}
Loading the data in the constructor works if the data is not a lot, which is wasn't in my case. Everything loads perfectly.
Good morning everyone, I have a sensor model class that, when the state changes, starts a timer that measures random values at intervals. How can I change the measured values on the main form with the same interval?
This is my window xaml markup
<ListBox Width="100" Name="Lst" Grid.Column="0" Grid.Row="1" SelectionChanged="Lst_OnSelectionChanged"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LavalIntervalValue" Grid.Row="1" Grid.Column="1" Margin="215,18,0,344" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelIdValue" Grid.Row="1" Grid.Column="1" Margin="215,73,0,289" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelStateValue" Grid.Row="1" Grid.Column="1" Margin="215,130,0,232" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelMeasuredValue" Grid.Row="1" Grid.Column="1" Margin="215,188,0,174" Height="40" />
<Label Width="186" Style="{StaticResource MainText}" Content="Interval" x:Name ="LabelInterval" Grid.Row="1" Grid.Column="1" Margin="11,18,663,344" Height="40"/>
<Label Width="187" Style="{StaticResource MainText}" Content="Id" x:Name ="LabelId" Grid.Row="1" Grid.Column="1" Margin="10,73,663,289" Height="40"/>
<Label Width="186" Style="{StaticResource MainText}" Content="State" x:Name ="LabelState" Grid.Row="1" Grid.Column="1" Margin="11,130,663,233" Height="39"/>
<Label Width="186" Style="{StaticResource MainText}" Content="Measured Value" x:Name ="LabelMeasured" Grid.Row="1" Grid.Column="1" Margin="11,188,663,175" Height="39"/>
This is my model
public class Sensor:IObservable
{
private int _measuredValue;
private List<IObserver> _observers = new List<IObserver>();
private IMeasuringState _state = new SimpleState();
public Sensor(int measuringInterval, SensorType sensorType, int uniqueId)
{
MeasuringInterval = measuringInterval;
SensorType = sensorType;
UniqueId = uniqueId;
}
[JsonIgnore]
public IMeasuringState MeasuringState
{
get => _state;
set
{
if (value!=null)
{
_state = value;
}
NotifyObservers();
}
}
public int UniqueId { get; }
public int MeasuredValue
{
get => _measuredValue;
set
{
_measuredValue = value;
NotifyObservers();
}
}
public SensorType SensorType { get; }
public int MeasuringInterval { get; }
public void ChangeState()
{
MeasuringState.Handle(this);
MeasuringState.StartMeasure(this);
}
}
Calibration state
public void StartMeasure(Sensor sensor)
{
_calibrationValue = 0;
_timer = new Timer(SetCalibration, sensor, 0, 1000);
}
public void Handle(Sensor sensor)
{
_timer.Dispose();
sensor.MeasuringState = new WorkState();
}
private void SetCalibration(object obj)
{
var sensor = obj as Sensor;
sensor.MeasuredValue = ++_calibrationValue;
}
This is my interface that I implemented in my main window
In property I set my label content value with presenter.
public interface IMainWindowView
{
List<string> SensorsType { get; set; }
int SelectedSensor { get; set; }
int MeasuredValue { get; set; }
string State { get; set; }
int Id { get; set; }
int MeasuringInterval { get; set; }
event EventHandler<EventArgs> OpenFile;
event EventHandler<StateEventArgs> DeleteSensorById;
event EventHandler<EventArgs> SaveFile;
event EventHandler<EventArgs> SelectedItem;
event EventHandler<StateEventArgs> ChangeStateSensor;
event EventHandler<EventArgs> OpenAddWindow;
void ShowWarning(string message);
}
I have a Grouped ListView bound to MyGroup (see below) that contains some property but when that property is being changed the view is not getting updated although I call OnPropertyChanged
My Class
public class MyGroup : ObservableCollection<Items>, INotifyPropertyChanged
{
private string foo;
public string Foo
{
get => foo;
set
{
foo = value;
OnPropertyChanged(nameof(Foo));
}
}
...
}
My View
<ListView ItemSource="{Binding GroupList}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="#2196F3"
Padding="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label FontSize="Medium" Text="{Binding Header}" Grid.Column="1" FontAttributes="Italic" TextColor="White"/>
<Label Text="{Binding Foo}" TextColor="White" FontSize="Medium" Grid.Column="1" HorizontalOptions="End"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
MyViewModel
public List<MyGroup> GroupList { get => groupList ;set => SetProperty(ref groupList, value); }
According to your description, you bind Foo in ListView Group header, you said that group header don't update when property changed, but I don't see where do you change this property. I've written a sample - I changed the first item group header when button click.
public class PersonList1 : ObservableCollection<Person1>, INotifyPropertyChanged
{
private string _heading;
public string Heading
{
get { return _heading; }
set
{
_heading = value;
RaisePropertyChanged("Heading");
}
}
public ObservableCollection<Person1> Persons => this;
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Person1
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName
{
get
{
return $"{LastName}, {FirstName}";
}
}
}
<StackLayout>
<ListView IsGroupingEnabled="true" ItemsSource="{Binding ListOfPeople}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Heading}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding DisplayName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
x:Name="btn1"
Clicked="Btn1_Clicked"
Text="change header" />
</StackLayout>
public partial class Page15 : ContentPage
{
public ObservableCollection<PersonList1> ListOfPeople { get; set; }
public Page15 ()
{
InitializeComponent ();
var sList = new PersonList1()
{
new Person1() { FirstName = "Sally", LastName = "Sampson" },
new Person1() { FirstName = "Taylor", LastName = "Swift" },
new Person1() { FirstName = "John", LastName = "Smith" }
};
sList.Heading = "S";
var dList = new PersonList1()
{
new Person1() { FirstName = "Jane", LastName = "Doe" }
};
dList.Heading = "D";
var jList = new PersonList1()
{
new Person1() { FirstName = "Billy", LastName = "Joel" }
};
jList.Heading = "J";
ListOfPeople = new ObservableCollection<PersonList1>()
{
sList,
dList,
jList
};
this.BindingContext = this;
}
private void Btn1_Clicked(object sender, EventArgs e)
{
ListOfPeople[0].Heading = "this is test";
}
}
You can see the the first ListView group header property change as "this is test".
Your question is not complete enough. Please add detail of how you are calling/setting values to property.
BTW, instead of using this(a wild guess,as you have not provided enough info):
public List<MyGroup> GroupList { get => groupList ;set => SetProperty(ref groupList, value); }
try this:
public List<MyGroup> GroupList {
get{
return groupList;
}
set{
groupList = value;
OnPropertyChanged(nameof(GroupList));
}
I'm trying to make control to add contacts Which has a TreeView. When I add contacts to the control displays nothing in the treeView. Here I show the code:
<TreeView x:Name="TvContactos" ItemsSource="{Binding Path=Groups}" HorizontalContentAlignment="Stretch" DockPanel.Dock="Left" ScrollViewer.CanContentScroll="True">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModelGroupContact}" ItemsSource="{Binding Children}">
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding GroupName}" Style="{StaticResource BloStyle}" Grid.Column="0"/>
</Grid>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelContact}">
<Grid Height="38">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Height="32" Width="32" Source="Resources/User.jpg" Margin="3" Grid.Column="0"/>
<TextBlock Text="{Binding ContactName}" Style="{StaticResource BloStyle}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</TreeView.Resources>
<TreeView.DataContext>
<local:ViewModelGroups/>
</TreeView.DataContext>
</TreeView.Resources>
In the code behind I have the following classes
public class ViewModelGroups : INotifyPropertyChanged
{
ObservableCollection<ViewModelGroupContact> _groups;
public ViewModelGroups()
{
Groups = new ObservableCollection<ViewModelGroupContact>();
}
public ObservableCollection<ViewModelGroupContact> Groups
{
get { return _groups; }
set
{
_groups = value;
OnPropertyChanged("Groups");
}
}
public void AddGroup(string groupName,RosterItem contact)
{
var newContact = new Contact {Name = contact.Name ?? contact.Jid.ToString(), RosterItem = contact};
var vmc = _groups.FirstOrDefault(item => item.GroupName == groupName);
if (vmc == null)
{
var contGroup = new ContactGroup { Name = groupName };
vmc = new ViewModelGroupContact(contGroup);
}
vmc.AddContactToGroup(newContact);
Dispatcher.CurrentDispatcher.BeginInvoke((new Action(() => Groups.Add(vmc))));
OnPropertyChanged("Groups");
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewModelGroupContact : TreeViewItemViewModel
{
private readonly ContactGroup _contactGroup;
public string GroupName { get; set; }
public ViewModelGroupContact(ContactGroup contactGroup)
: base(null, true)
{
_contactGroup = contactGroup;
GroupName = _contactGroup.Name;
}
protected override void LoadChildren()
{
foreach (Contact contact in _contactGroup.GetContacts())
base.Children.Add(new ViewModelContact(contact, this));
}
public void AddContactToGroup(Contact contact)
{
if (!_contactGroup.GetContacts().Contains(contact))
_contactGroup.AddContactToGroup(contact);
}
}
public class ViewModelContact:TreeViewItemViewModel
{
private readonly Contact _contact;
public ViewModelContact(Contact contact, ViewModelGroupContact group)
: base(group, true)
{
_contact = contact;
}
public string ContactName
{
get { return _contact.Name; }
}
}
When added a contact to treeview nothing is displayed.No show TreeViewItemViewModel class which inherits from INotifyPropertyChanged for not doing longer the post. This class has a property called Childrens.
This is the control class that was missing
public partial class ContactControl : UserControl
{
#region Private
private ViewModelGroups _viewModel;
private const string MDefaultGroupName = "ungrouped";
#endregion
public ContactControl()
{
InitializeComponent();
Init();
}
public ViewModelGroups ViewModel
{
get { return _viewModel; }
}
public void Init()
{
_viewModel = new ViewModelGroups();
TvContactos.DataContext = _viewModel;
}
public void AddContact(RosterItem ritem)
{
string groupname;
if (ritem.GetGroups().Count > 0)
{
var g = (Group)ritem.GetGroups().Item(0);
groupname = g.Name;
}
else
{
groupname = MDefaultGroupName;
}
_viewModel.AddGroup(groupname, ritem);
}
}