I have a ListView bound to a view model which contains an ObservableCollection in a simple example app.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(){}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get
{
return this._items;
}
set
{
if (value != this._items)
{
this._items = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
}
}
}
public class Item
{
public string Name;
}
The following function is bound to the SelectionChanged event of the ListView
private void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
model.Items.Add(new Item { Name = "added item" });
model.Items = new ObservableCollection<Item> { new Item { Name = "new item 1" }};
}
When the event fires, this should happen
new item ("added item") appended to existing ObservableCollection
ObservableCollection set to a new collection [single item, "new item 1"]
What actually happens:
ObservableCollection set to new collection [single item, "new item 1"]
new item ("added item") appended to end of new collection
Can anyone explain why these are happening in the wrong order?
Can anyone explain why these are happening in the wrong order?
My guess is that they're not happening in the wrong (reversed) order but the append is happening twice. Executing
model.Items = ... ;
in the SelectionChanged of those same Items is pretty bold. It will trigger a SelectionChanged again, and only because the Selection then remains at none (Index -1) you do not get into an infinite loop.
observable collection not need inotify, try this:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(){}
public ObservableCollection<Item> Items ;
}
Related
I have a problem. I created a ListView with as itemsource a List called unknownDeviceList from my ViewModel. Here is my ViewModel:
public class VM_AddDeviceList : BindableObject
{
private List<UnknownDevice> _unknownDeviceList;
public List<UnknownDevice> unknownDeviceList
{
get
{
return _unknownDeviceList;
}
set
{
if (_unknownDeviceList != value)
{
_unknownDeviceList = value;
OnPropertyChanged();
}
}
}
public List<UnknownDevice> deviceList_raw;
public VM_AddDeviceList()
{
deviceList_raw = new List<UnknownDevice>();
unknownDeviceList = new List<UnknownDevice>();
MyHandler();
}
private async Task LoadUnknownDeviceList()
{
deviceList_raw = await App.RestService.GetDevices();
foreach (UnknownDevice device in deviceList_raw)
{
bool containsItem = App.knownDeviceList.Any(item => item.MAC == device.MAC);
if (!containsItem)
{
unknownDeviceList.Add(device);
}
}
}
public Task MyHandler()
{
return LoadUnknownDeviceList();
}
}
Now I can see that unknownDeviceList gets filled in the foreach, but on the screen the ListView stays empty. What am I doing wrong?
Something with the async and await?
You are raising PropertyChanged when setting unknownDeviceList to inform the view that the list has changed. Anyway, there is no way for the view to know that there were items added to unknownDeviceList.
The most idiomatic way to solve the issue would be to use an ObservableCollection<string> instead.
private ObservableCollection<string> _unknownDevices = new ObservableCollection<string>();
public ObservableCollection<string> UnknownDevices => _unknownDevices;
Please note that I've used the expression body syntax for read-only properties for UnknownDevices, it's not a field.
Since ObservableCollection<string> implements INotifyCollectionChanged which can be subscribed to by the binding to UnknownDevices the view is informed about the changes in UnknownDevices and will be updated when any items are added or removed.
I have to display all the messages I am receiving from backend into a list box like
10:12:23 Login Successful.
10:13:00 Logout Successful.
How can a bind this string message as listitem with auto updation through INotifyPropertyChange and with the condition that recent item should get inserted at 0th index.
I am quickly going to throw the code in pieces; you'll have to assemble it.
every body is using itemsource to a list which in my case is not
working.
You cannot leave it as {Binding} that works if you set list's data context to collection. But if your collection is part of view-model then you must specify collection name in binding.
View:
<ListBox ItemsSource="{Binding Logs}" />
<Button Click="AddLogEntry_Click" Content="Add log entry" />
View-Model:
public class ViewModel1 : BaseViewModel
{
private ObservableCollection<string> logs;
public ObservableCollection<string> Logs {
get {
if (logs == null)
logs = new ObservableCollection<string>();
return logs;
}
}
// This is added for test
public void AddLogEntry() {
Logs.Insert(0, DateTime.Now.ToString());
}
}
View Code-Behind:
ViewModel1 vm;
public DisplayLatestItemInListbox() {
InitializeComponent();
vm = new ViewModel1();
DataContext = vm;
}
// Use command instead.
private void AddLogEntry_Click(object sender, RoutedEventArgs e) {
vm.AddLogEntry();
}
Key here is to use Insert method on ObservableCollection. And here's what you get:
I tried this sample as follow this will help you,
View Model :
public VM()
{
items = new ObservableCollection<string>();
Items.Insert(0, "The time now is " + DateTime.Now.ToShortTimeString());
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(60) };
timer.Tick += (s, e) =>
{
Items.Insert(0,"The time now is " + DateTime.Now.ToShortTimeString());
};
timer.Start();
}
private ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get { return items; }
}
Bind this item to XAML listview
While working on an Universal App (currently only on the WP8.1-side), I've stumbled upon the following weird thing.
I've got a ComboBox, the UserControl (located in the WindowsPhone-project) it's in is binded to a VM in the Shared project. Both the ItemsSource and SelectedItem are binded to their respective properties in the VM.
When running the application, when you select any item except the first one, it is working perfectly. But, when I select the first item, the string displayed in the ComboBox shows the .ToString()-method of the VM instead...
(Btw, it's a simple List<string>, the selected item is a string. It can't get much more simpler than that :p)
I've created a sample app, containing just this Combobox, and the VM. I was able to reproduce this, the moment I asynchronously fill in property binded to the ItemsSource. When doing it from a synchronous method, it works. But just filling it from an async method provides the above problem.
A few screenshots:
The first one shows the app when it's loaded. When the collection changes, the first element of the list is selected. It is shown here:
When you click on the ComboBox, you get to see its items as usual:
Say you click on any element other than the first, you still get normal behaviour:
So far, so normal. Now click the first item. You get this:
...
I've tried a variety of things like making it a list of an object instead of just strings. Adding a converter to the binded objects, just for debugging purposes, only reveales the actual string-values. I've got no idea how, nor why, the binded SelectedItem suddenly shows the DataContext of the ComboBox...
You can download the sample app here:
http://1drv.ms/1DhklCQ
(contains no binaries, just the code)
Anybody got any ideas?
EDIT: The code required to reproduce this issue:
Create a blank Universal store app (8.1).
In the WindowsPhone project, the file MainPage.xaml:
I've added a simple combobox, and catch the Loaded event.
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
In its code behind. I've assigned the DataContext to the VM. And in the Loaded event I asychronously call the VM.LoadData()
private VM _vm = new VM();
public MainPage()
{
this.InitializeComponent();
this.DataContext = _vm;
}
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
await _vm.LoadDataAsync();
}
The VM object is defined as followed:
public class VM : INotifyPropertyChanged
{
private List<string> _items;
public List<string> Items
{
get { return _items; }
set
{
_items = value;
_selectedItem = _items.FirstOrDefault();
RaisePropertyChanged("Items");
RaisePropertyChanged("SelectedItem");
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public VM()
{
}
public async Task LoadDataAsync()
{
this.Items = new List<string>()
{
"a",
"b",
"c",
"d",
"e",
"f",
};
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Found a workaround cause previous solutions didn't solve my problem.
Just add a pause between binding and selecting an item or index of your combobox.
Code below :
myCombobox.ItemsSource = myList;
await Task.Delay(100);
myCombobox.SelectedIndex = 12;
Hope this helps !
I checked it out and I can't see any problem with your code, so I guess it is a bug in the ComboBox.
Understanding the problem and finding a true fix may take you some time, so I'd suggest you use some workaround that works for you. I tried the following and it seemed to work:
Change the Items property in the VM to be of type ObservableCollection<string>
Initialize the property/field in the VM's constructor to an empty collection.
When loading the items, just fill the collection (add items to it using the Add() method) instead of replacing it.
Edit: example of how I fill tested it.
public class VM : INotifyPropertyChanged
{
private ObservableCollection<string> _items;
public ObservableCollection<string> Items
{
get { return _items; }
set
{
_items = value;
_selectedItem = _items.FirstOrDefault();
RaisePropertyChanged("Items");
RaisePropertyChanged("SelectedItem");
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public VM()
{
this._items = new ObservableCollection<string>();
}
public async Task LoadDataAsync()
{
var items = new List<string>() {
"1",
"b",
"c",
"d",
"e",
"f",
"f",
"f",
"f",
"f",
"f",
};
foreach (var i in items) {
this._items.Add(i);
}
this.SelectedItem = items.FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
This works fine for me.
Not only asynchronously - if you put _vm.Items = new List... in OnLoaded event, instead of await _vm.LoadDataAsync(); - you will get the same issue.
Seems that the issue won't occur once you set your Items before setting the DataContext.
The other thing is that the issue won't appear (as I've tried) if you don't set Selected item from code:
public ObservableCollection<string> Items
{
get { return _items; }
set
{
_items = value;
// _selectedItem = _items.FirstOrDefault();
RaisePropertyChanged("Items");
// RaisePropertyChanged("SelectedItem");
}
}
As for now I've no idea why this happens.
I have an MVVM-based WPF application that relies on Caliburn.Micro.
In one view, I am displaying a DataGrid and a Button. The DataGrid displays a collection of items, where the item class derives from PropertyChangedBase.
The button should be enabled or disabled based on the contents in the editable DataGrid cells. What is the most reliable approach to achieve this with Caliburn.Micro?
Schematically, this is what my code looks right now:
public class ItemViewModel : PropertyChangedBase { }
...
public class ItemsViewModel : PropertyChangedBase
{
private IObservableCollection<ItemViewModel> _items;
// This is the DataGrid in ItemsView
public IObservableCollection<ItemViewModel> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
// This is the button in ItemsView
public void DoWork() { }
// This is the button enable "switch" in ItemsView
public bool CanDoWork
{
get { return Items.All(item => item.NotifiableProperty == some_state); }
}
}
As the code stands, there is no notification to ItemsViewModel.CanDoWork when one NotifiableProperty is changed, for example when the user edits one cell in the ItemsView´s DataGrid. Hence, the DoWork button enable state will never be changed.
One possible workaround is to add an (anonymous) event handler to every item in the Items collection:
foreach (var item in Items)
item.PropertyChanged +=
(sender, args) => NotifyOfPropertyChange(() => CanDoWork);
but then I also need to keep track of when (if) items are added or removed from the Items collection, or if the Items collection is re-initialized altogether.
Is there a more elegant and reliable solution to this problem? I am sure there is, but so far I have not been able to find it.
I think this is a case where INPC works well; to simplify registering/deregistering adds and deletes just add a CollectionChanged handler to your Items collection:
Items.CollectionChanged += OnItemsCollectionChanged;
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null && e.NewItems.Count != 0) {
foreach (ItemViewModel vm in e.NewItems)
vm.PropertyChanged += OnDetailVmChanged;
}
if (e.OldItems != null && e.OldItems.Count != 0) {
foreach (ItemViewModel vm in e.OldItems) {
vm.PropertyChanged -= OnDetailVmChanged;
}
}
}
Josh Smith wrote a PropertyObserver class here that I find more elegant than shotgun INPC tracking, but in a master-detail scenario like yours you would still have to track the adds and deletes.
EDIT by Anders Gustafsson
Note that for the above code to work in the general case requires that Items has been initialized with the default constructor before the event handler is attached. To ensure that OnDetailVmChanged event handlers are correctly added and removed, the Items property setter need to be extended to something like this:
public IObservableCollection<ItemViewModel> Items
{
get { return _items; }
set
{
// If required, initialize empty _items collection and attach
// event handler
if (_items == null) {
_items = new BindableCollection<ItemViewModel>();
_items.CollectionChanged += OnItemsCollectionChanged;
}
// Clear old contents in _items
_items.Clear();
// Add value item:s one by one to _items
if (value != null) foreach (var item in value) _items.Add(item);
NotifyOfPropertyChange(() => Items);
}
}
(And of course, with the above Items setter in place, the topmost Items.CollectionChanged event handler attachment should not be included in the code.)
Ideally, I would have used if (value != null) _items.AddRange(value);, but when the AddRange method triggers the OnItemsCollectionChanged event handler, e.NewItems appear to be empty (or null). I have not explicitly verified that e.OldItems is non-null when the Clear() method is invoked; otherwise Clear() would also need to be replaced with one-by-one removal of the item:s in _items.
Whenever a property changes fire RaiseCanExecuteChanged for the button cummand command?
For Example :
public DelegateCommand<object> MyDeleteCommand { get; set; }
string _mySelectedItem;
public string MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
MyDeleteCommand.RaiseCanExecuteChanged();
}
}
A ObservableCollection
private ObservableCollection<string> _items = new ObservableCollection<string>();
public ObservableCollection<string> Items { get { return _items; } }
is updated on user interactions (a TextBox event).
In a ListBox I'll show the current values
Binding listBinding = new Binding {Source = Items};
listbox.SetBinding(ListBox.ItemsSourceProperty, listBinding);
That works so far: When adding a new value the list is immediately updated.
But now I have to requirements:
Sort values
Add one item at the beginning of the list
I solved as followed:
public IEnumerable<string> ItemsExtended
{
get
{
return new[] { "first value" }.Concat(Items.OrderBy(x => x));
}
}
I changed the binding to that IEnumerable and the list contains a sorted list with "first value" at position one.
Unfortunately when the list should be updated on user interaction it does not work any more. Even changing IEnumerable to ObservableCollection again and directly referring to the private ObservableCollection does not solve the issue:
return new ObservableCollection<string> (new[] { "bla" }.Concat(_items.OrderBy(x => x)));
How to update the list when _items changes?
Off the top of my head. You could implement INotifyPropertyChanged for the class that your collection belongs to . After that add a CollectionChanged handler for your _items collection and fire PropertyChanged("ItemsExtended") in that handler. Also using yield return in the getter will avoid creating a new collection just to add the item at the top.
It should look something like this
public partial class MyClass : INotifyPropertyChanged
{
ObservableCollection<string> _items;
public MyClass()
{
_items = new ObservableCollection<string>();
_items.CollectionChanged += (s, e) => { OnPropertyChanged("Items"); };
}
public IEnumerable<string> Items
{
get
{
yield return "first value";
foreach (var item in _items.OrderBy(x=>x))
yield return item;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}