I am trying to do data binding in WPF on a data grid using a custom list. My custom list class contains a private data list of type List<T>. I cannot expose this list, however the indexers are exposed for setting and getting individual items.
My custom class looks like this:
public abstract class TestElementList<T> : IEnumerable
where T : class
{
protected List<T> Data { get; set; }
public virtual T Get(int index)
{
T item = Data[index];
return item;
}
public virtual void Set(int index, T item)
{
Data[index] = item;
}
...
}
The data is binded but when I try to edit it, I get 'EditItem' is not allowed for this view error. On doing extensive searching over web, I found that I might need to implement IEditableCollectionView interface also.
Can anybody please help me to either give pointers on how to implement this interface or any suggest any other better way to do databinding on custom list?
Though I am not fully understanding your requirement, do you think using an ObservableCollection will solve your issue?
public abstract class TestElementList<T> : ObservableCollection<T>
where T : class
{
public virtual T Get(int index)
{
T item = this[index];
return item;
}
public virtual void Set(int index, T item)
{
this[index] = item;
}
...
}
I had the same exception. It seems that you have to bind do IList. I was binding to a IEnumerable and this exception was thrown.
Just to add my own observation. I had a datagrid with specifically defined columns in Xaml and its ItemsSource set to a simple dictionary. When I tried to edit the second column, I got this exception referring to the dictionary. I then set the data grid ItemsSource to a list of the Keys (dataGrid.Keys.ToList()). I could then edit the second column. It seems a list view allows an 'EditItem'.
edit: Did a little bit more digging into this. I set up a BeginningEdit handler and started poking around. One thing I noticed was that every single time I got this error, EditingEventArgs.Source was a Border. If I can find the time, I may look into this one down a bit further. Also, on one instance, my converting the dictionary keys to a List did not work. I had to convert it to an Observable collection, despite the fact that a List was suitable in all other places in my code where I was doing essentially an identical type of assignment.
edit again: Ok, I have another fix for those for which using an IList type doesn't work. Attach a BeginningEdit handler to your DataGrid and point to this code:
private void Grid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
//// Have to do this in the unusual case where the border of the cell gets selected
//// and causes a crash 'EditItem is not allowed'
e.Cancel = true;
}
This will only hit if you somehow manage to physically tap down on the border of the cell. The event's OriginalSource is a Border, and I think what my happen here is instead of a TextBox, or other editable element being the source as expected, this un-editable Border comes through for editing, which causes an exception that is buried in the 'EditItem is not allowed' exception. Canceling this RoutedEvent before it can bubble on through with its invalid Original Source stops that error occurring in its tracks.
Glad to have found this as there was, in my case, a DataGrid on which I couldn't use an IList type.
Related
I am working with this sample: http://www.arcgis.com/home/item.html?id=ef6a80c07fb84f84a5fe5192221f582c
specifically with “GraphicsDataSourcesDemo”, which uses Silverlight API.
It allow for loading csv file with Lat Long coordinates, which are displayed as points on the map. In my own application I successfully implemented part about displaying points, but I have problem with a Clear button (which is not implemented in the included sample code). Since INotifyCollectionChanged and StreamReader are used, standard method like:
private void GPSClearButton_Click(object sender, RoutedEventArgs e)
{
GraphicsLayer graphicsLayer = Map.Layers["data"] as GraphicsLayer;
graphicsLayer.ClearGraphics();
}
does not work.
I would appreciate any advice on how to make displayed points disappear from my map (after user decide so by clicking a button Clear).
The graphical elements merely represent data items, so either you want to throw away the data items (that's what I understand from an action called 'Clear') or you want to 'hide' the displayed data items and later be able to 'show' them again. Only the latter one is to be solved in the View/GraphicsLayer. The former one means you have to 'Clear' the list of data items i.e. coordinates, and (if used correctly) the bindings from View to the DataContext will ensure that all corresponding visual elements are removed from the UI.
So you will need something like this:
public class CoordinateCollectionViewmodel
{
public ICommand Clear { get; set; } // setup to call Coordinates.Clear()
public ObservableCollection Coordinates { get; set; }
}
I've got the following question: what's the expected scenario for the logic when I would want to bind some elements inside of ViewModel separatly. What I mean...
http://slodge.blogspot.co.uk/2013/04/n3-kitten-cells-on-iphone-n1-days-of.html
There is a "Kitten" class in the sample provided - this is just a common "DTO" object.
And there is also a modelview class which contains those objects list:
public List<Kitten> Kittens
{
get ...
set { ... RaisePropertyChanged(() => Kittens); }
}
We can bind a grid with cells (which bound to Kitten properties). But what if I would want to be able to activate RaisePropertyChanged on every property of Kitten separatly? I.e.,
if the kitten Title changed, then to call RaisePropertyChanged (and accordingly, change only bound cell value instead of the whole list refreshing) on KittenTitle property (for instance)?
The sample with Kittens is obviously primitive and does not need such implementation, but what if instead of Kittens I would have a list similar to Facebook App menu panel, where there are menu items (amount of which can vary) and those items can have "Notifications Count" label (or can not) so, instead of the complete refresh of the list, how can I initiate that label only refreshing (caused by related property inside the "Kitten" instance changed)?
(That looks like viewModel inside viewModel for me, but not sure how to solve it smarter with MvvmCross).
Thank you!
You can implement Nested INotifyPropertyChanged objects - exactly as you do in Windows binding.
So if one Kitten raises its property changed then only that part of the UI for that kitten will refresh
e.g. a Kitten could be written:
public class DynamicKitten : MvxNotifyPropertyChanged // can use MvxViewModel as base class if preferred
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged(() => Name); }
}
}
For some examples of this - mostly using Linq to wrap static objects - see:
https://github.com/slodge/MvvmCross-Tutorials/tree/master/InternetMinute
https://github.com/slodge/MvvmCross-Tutorials/tree/master/MonoTouchCellTutorial (discussed pre-v3 in http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html)
https://github.com/slodge/MvvmCross-Tutorials/tree/master/Sample%20-%20CirriousConference
One of my favorite StackOverflow libraries took this INPC approach all the way back to the Json layer - take a look at all the INPC entities in https://stacky.codeplex.com/SourceControl/latest#trunk/source/Stacky/Entities/Answer.cs
May be it's a silly (or more than trivial) kinda question, but it seems i just don't know the answer. Here's the case -
I assigned a UserList as the ItemsSource of a combobox. So what i did essentially is assigning a reference type to another.
I cleared the UserList. So now i get the Count of the ItemsSource 0 as well.
I still get the items present in my combobox. And i also can cast the SelectedItem of the combobox to a User object.
Here's the complete code -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
private List<User> _userList;
public MainWindow()
{
InitializeComponent();
_userList = new List<User>()
{
new User() {Id = 1, Name = "X"},
new User() {Id = 2, Name = "Y"},
new User() {Id = 3, Name = "Z"}
};
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBox1.ItemsSource = _userList;
this.comboBox1.DisplayMemberPath = "Name";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_userList.Clear();
/* ItemsSource is cleared as well*/
IEnumerable userList = this.comboBox1.ItemsSource;
/*I can still get my User*/
User user = this.comboBox1.SelectedItem as User;
}
}
So, where the items are coming from? What actually happens under-the-hood when i make such binding? Does the control have some kind of cache? It's a royal pain to realize not having such basic ideas. Can anybody explain the behind-the-scene detail?
EDIT : I wrote the code in WPF, but i have the same question for WinForms Combobox.
EDIT : Doesn't a combobox display its items from it's in-memory Datasource? When that datasource contains 0 items, how does it display the items?
When you set an ItemsSource of any ItemsControl it copies the ref to the list into its Items property. Then it subscribes to the OnCollectionChanged event, and creates a CollectionView object. So, on the screen you can see that collectionView.
as I have found in source code ItemCollection holds two lists:
internal void SetItemsSource(IEnumerable value)
{
//checks are missed
this._itemsSource = value;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
}
How could you get SelectedItem?
This is my assumption from quick look into the source code:
ItemsControl has a collection of "views" and each View sholud store a ref to the item (User instance), because it has to draw data on the screen. So, when you call SelectedItem it returns a saved ref.
Upd about references
Assume there is an User instance. It has the adress 123 in memory. There is a list. It stores references. One of them is 123.
When you set an ItemsSource ItemsControl saves a reference to the list, and creates a Views collection. Each view stores a references to an item. One view stores an address 123.
Then you cleared a list of users. Now list doesn't contains any references to Users. But in memory there is an adrress 123 and there is an instance of User by this adress. Garbage Collector doesn't destroy it, because View has a reference to it.
When you get SelectedItem it returns User instance from the 123 adress.
var user = new User();
var list = new List<User>();
list.Add(user);
list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty
Console.WriteLine(user == null); //prints false. - user instance is sill exists;
In answer to your comment to #GazTheDestroyer ("... why it doesn't get cleared, and how it holds the items?")
In WPF, when you set the ItemsSource property of an ItemsControl, the control will wrap the list of items in a CollectionView, which is a collection type optimised for use by the UI framework. This CollectionView is assigned to the Items property of the control and is what the display-drawing code actually works from. As you see, this collection is entirely separate of the object you originally assigned to ItemsSource, and so there is no propogation of changes from one to the other. This is why the items are still in the control when you clear the original list: the control is ignoring the original list, and has its own list that contains your objects.
It's for this reason that an ItemsSource value needs to raise events - specifically INotifyCollectionChanged.NotifyCollectionChanged - so that the control knows to refresh the Items list. ObservableCollection implements this interface and raises the correct event, and so the functionality works as expected.
It's hugely important to note that this is nothing like what happens in WinForms, which is why I've been pressing you for the clarification.
EDIT: To clarify, there is no "deep copy." The code that is happening is similar in principle to the following:
private List<object> myCopy;
public void SetItemsSource(List<object> yourCopy)
{
myCopy = new List<object>();
foreach (var o in yourCopy)
{
myCopy.Add(o);
}
}
Once this code has run, there's only one copy of every item in your list. But each of the items is in both of the lists. If you change, clear or otherwise manipulate yourCopy, myCopy knows nothing about it. You cannot "destroy" any of the objects that are within the list my clearing yourCopy - all you do is release your own reference to them.
Assuming you are using WPF:
List<User> doesn't fire any event that the UI will recognise to refresh itself. If you use ObservableCollection<User> instead, your code will work.
The key difference is that ObservableCollection implements INotifyCollectionChanged, which allows the UI to recognise that the content of the collection has changed, and thus refresh the content of the ComboBox.
(Note that this does not work in WinForms. In WinForms you can set the DataSource property of the control, but the same ObservableCollection trick does not work here.)
When you set a collection reference to ItemsControl, all the combo gets is a reference, that it knows is enumerable.
It will enumerate the reference and display the items. Whether it does a deep copy or shallow copy is irrelevant, all it has is a reference (memory address effectively).
If you change your collection in some way, the combo has no way of knowing unless you tell it somehow. The reference (address) hasn't changed, everything looks the same to the combo. You seem to be thinking that the object is somehow "live" and the combo can watch the memory changing or something? This isn't the case. All it has is a reference that it can enumerate over. The contents can change but without some trigger the combo doesn't know that, and so will sit doing nothing.
ObservableCollection is designed to overcome this. It implements INotifyCollectionChanged that fires events when it changes, so the Combo knows that it must update its display.
I have a DataTemplate that needs to set the IsSelected property on an ItemsControl's container (such as TreeViewItem, ListViewItem or ComboBoxItem). However, it doesn't know the type of the container until it's passed in to it. Since IsSelected isn't part of a common base class or interface, nor is it a common dependency property registered with AddOwner to the various classes (Duh, MS!!! WTF not?!!), I ended up with this mess...
if (container is TreeViewItem) {
(container as TreeViewItem).IsSelected = true;
return;
}
if (container is ListBoxItem) {
(container as ListBoxItem).IsSelected = true;
return;
}
if (container is ComboBoxItem) {
(container as ComboBoxItem).IsSelected = true;
return;
}
...which not only is verbose, but requires me to modify it if I ever use a different ItemsControl that uses different container types! Not good!
Sure I could enhance it a little by putting this logic in extension methods (damn C# for not having extension properties!!) called IsContainerSelected and SetContainerSelected and putting them on UIElement, then moving the above code inside there, but it's just making the outside neater. The inside is still a mess.
My only other thought is to use reflection and look for an IsSelected property and use that if found, but I'm always leery of doing things like that. However, since there isn't a common interface or base class, I'm not really sure I have a choice here.
For context, I'm sharing a complex data template between several different ItemsControls and the template itself has controls that can receive focus such as checkbox and textbox. However, when those controls receive focus via the mouse, the underlying container item doesn't get selected and whatever was selected before remains so.
My workaround is to use an attached behavior that utilizes the preview events to intercept the focus before it happens and set the underlying item accordingly, which works great when I've hard-coded TreeViewItem or ListBoxItem, etc., but I don't want to hard-code the type since the control shouldn't really care. So that's the part that breaks down.
Ugh!!! Why didn't MS just register the same attached property or at least create an ISelectableContainer interface?!!
I have read your answer, and it does make sense - in your case, IsSelected may obviously be part of the ViewModel, and that seems to be the best solution in your case.
But you asked for further explanation about C# dynamic features. C# 4.0 now has some dynamic functionalities, which allow us to create code that would only be possible in languages like Python, Ruby or JavaScript. This, of course, has its cost - a dynamic abuse would not only make code slower, but also more confusing - because you would lose compile-time errors and IntelliSense.
I have written a simple example so you may understand it better:
public class ClassOne
{
public int SameProperty { get; set; }
}
public class ClassTwo
{
public int SameProperty { get; set; }
}
public class ClassThree
{
public string SameProperty { get; set; }
}
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
dynamic wrapper = new ClassOne();
wrapper.SameProperty = 5;
wrapper = new ClassTwo();
wrapper.SameProperty = 15;
wrapper = new ClassThree();
wrapper.SameProperty = "Now it is a string!";
// And now a run-time error...
wrapper.AnotherProperty = "And this won't work...";
}
}
As you can see, wrapper has no definite type whatsoever - a dynamic reference will allow any kind of method or property invocation, since the actual binding will only be made during run-time, not compile-time.
Of course, this example is very naive, but sometimes dynamic code may be useful - it is a good option to avoid explicit reflection, or to avoid long if...else statements based on type (like your snippet above).
I'm not sure that I fully understand your problem, but you could try adding an IsSelected boolean to your model and then binding that property against the Item control it's contained in. That way, you just have to worry about setting that property in the model, regardless of the container.
Per #mdm20's answer, he suggested modifying the ViewModel, which is of course normally what you want to do. However this is a purely view-related issue (keyboard navigation-related) and isn't reflected in the ViewModel at all, nor in this case should it be.
But that gave me an idea! Since I'm using a custom control to render the item in whichever items control (via its data template) it's being added to, that control naturally does have multiple instances (all of which are pointing to the same ViewModel instance), which is what I want!
Therefore, rather than adding the IsSelected to the ViewModel, I added it to the user control itself, then I just bind to that within the data template for the respective ItemsControl which I do know about. I can then set the IsSelected property in the code-behind for the user control as needed (i.e. during the preview mouse events, etc.) and the underlying ItemsControl responds appropriately! Works great and keeps the ViewModel clean since neither the model, nor the viewmodel need to know about it. The IsSelected remains purely in the UI which is where in this particular case it should be!
MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.