im completely new to the wcf service things so im a bit lost on the approach here. I have an operation in the service called GetHoldsJoined. The listbox im binding to is called lbxOpenHolds. I am able to set the result as an itemsource with the following:
public frmHoldsDashBoard()
{
InitializeComponent();
dbServiceClient db = new dbServiceClient();
db.GetHoldsJoinedCompleted +=new EventHandler<GetHoldsJoinedCompletedEventArgs>(db_GetHoldsJoinedCompleted);
db.GetHoldsJoinedAsync();
}
private void db_GetHoldsJoinedCompleted(object sender, GetHoldsJoinedCompletedEventArgs e)
{
lbxOpenHolds.ItemsSource = e.Result;
}
but what I want to do is set the selectedvaluepath to an attribute in one of the result list items. The result is a List collection. There is an attribute in each one of the list objects called a.HoldID, it is composite. I want this to be the selectedvalue member. How do I unbox this from GetHoldsCompletedEventArgs ?? Or is there some other way to do this?
Sorry if the question is asked kind of scatterbrained, I really didn't know how else to explain it.
What about something like this:
lbxOpenHolds.SelectedValuePath = "HoldID";
This should go right after setting the ItemsSource on lbxOpenHolds
Related
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'm playing with ICollectionView right now, and am encountering a problem where I think I understand the "why", but not the "how do I fix it". :)
I have a ComboBox that's databound to an ICollectionView, and it is initially set with the following code:
NameView = CollectionViewSource.GetDefaultView( names); // names is an IEnumerable<string> that comes from a LINQ query
NameView.CurrentChanged += new EventHandler(NameView_CurrentChanged);
Everything works great until I execute a piece of code that generates a new IEnumerable<string> and sets NameView again with the same code as above. Once I do this, CurrentItem is no longer working properly.
I've run into this problem before with ObservableCollection<string> databound to ComboBoxes, and I get around the "unbinding" problem by using Clear() and Add() instead of setting the ObservableCollection<string> property to a new ObservableCollection<string>.
My questions include:
1. If I wanted to be able to just set the property to a new collection, can I re-establish databinding with the new collection somehow? If so, how? If not, can you explain the WPFisms behind why this is fundamentally not possible?
2. What's the best way to deal with changes in an ObservableCollection<string> or ICollectionView? Is my approach of just Clearing and Adding the only way to do it?
When you bind your WPF Controls to ICollectionViews (Happens when the XAML is parsed withing your InitializeComponent-call - You should really define the bindings in XAML!), the Controls subscribe to the required events published by your collection (e.g. CollectionChanged).
Your collection property is just a reference to a memory address. When you bend this to a new collection (i.e. a new address), the DataBinding won't notice. You can't expect the original Collection to publish something like "IAmOuttaHere", and clearly the controls wouldn't listen to a new collection saying "I'm the new guy". But if I see this correctly, your snippet does nothing but add an eventhandler to the CurrentChanged (meaning your observe when some other item in the Combobox is being selected)
Binding is all about notification, so - as long as you don't tell your controls that the collection has been exchanged, they will stick to the initial collection. Please try to implement INotifyPropertyChanged like so:
public class ViewModel : INotifyPropertyChanged
{
private ICollectionView myCollection;
public ICollectionView MyCollection
{
get
{
return this.myCollection;
}
set
{
this.myCollection = value;
this.OnPropertyChanged("MyCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ExchangeCollection()
{
this.MyCollection = new CollectionView(....)
}
}
Any bindings should be made to MyCollection. Although, personally, I don't define ICollectionViews myself, since they are not really as nice to work with as for example a nifty IList and they are auto-wrapped around any collection anyway as soon as a binding is defined.
Hope this helps
Sebi
I have a listbox on my form that looks like this:
<ListBox Name="lbResults" SelectionChanged="lbResults_SelectionChanged"/>
I am binding the following collection to it:
ObservableCollection<Hand> oHands = new ObservableCollection<Hand>();
using the following code:
lbResults.DataContext = oHands;
Binding binding = new Binding();
lbResults.SetBinding(ListBox.ItemsSourceProperty, binding);
The oHands collection gets populated via a background worker that announces via an event whenever a new Hand object is available. The ListBox refreshes perfectly when something is added. The ToString() result of the Hand object is displayed and that is what I want - so far so good. However, when the background worker finishes
void finder_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
oHands = new ObservableCollection<Hand>(oHands.OrderBy(o => o.PotSize));
lbResults.SetBinding(ListBox.ItemsSourceProperty, new Binding());
}
The items in the list are still showing up in the original order. I can confirm that the list is re-ordered but the items are still showing up in the original order. How do I refresh this binding?
you dont want to assign oHands a new collection. just Clear() the collection then add the results from the operation. don't update the binding
instead of replacing the entire observable collection, you could just clear it and add all your new items. that wouldn't affect your binding.
You could also use a CollectionViewSource as your binding, and set the order on that instead of reordering the whole collection.
Wouldn't it be a lot easier to just set the itemsource directly?
lbResults.ItemsSource = oHands;
You're really just supposed to inherit from the INotifyPropertyChanged interface, but heres another way to force an update to a binding:
BindingExpression exp = BindingOperations.GetBindingExpression(lbResults, Listbox.ItemsSourceProperty)
exp.UpdateTarget()
Edit: I also just noticed you aren't setting any binding in the XAML and appear to be doing it programmatically with an empty Binding. I haven't tried that way before, so see if changing your XAML to this might help:
<ListBox Name="lbResults" SelectionChanged="lbResults_SelectionChanged" ItemsSource="{Binding Path=oHands}"/>
Then you set lbResults.DataContext to point to the class that has the member oHands. This is what worked for me in my project (in IronPython, so forgive me if my examples didn't convert to C# perfectly).
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.
I'm just getting started with Linq-to-SQL and data binding in WPF, most of which works like a dream so far!
I've got (what I though was) a common scenario:
a) Query list of records from a table via datacontext and bind to the current user control
this.DataContext = db.ClientTypes;
b) Have the user see a bound ListView and some bound detail controls to make changes to the existing records, with a db.SubmitChanges(ConflictMode.FailOnFirstConflict); to push the changes back to the DB. No problem.
c) User wants to add a new record, so we:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
However at this point I dont want to call db.SubmitChanges as I want the user to be able to update the properties of the object (and even back out of the operation entirely), but I want them to be able to see the new record in the bound ListView control. Thinking I just needed to re-run the query:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
// Rebind the WPF list?
this.DataContext = db.ClientTypes;
listView1.SelectedItem = ct;
listView1.ScrollIntoView(ct);
However this doesn't work, the newly created record is not part of the returned list. I'm not sure if this is because of caching within L2S or if I'm just going about this the wrong way. Is there a better way to accomplish this?
Thanks.
Instead of setting your Control.DataContext = db.ClientTypes, store db.ClientTypes somewhere else and bind to an ObservableCollection that wraps it.
var somewhereElse = db.ClientTypes;
var toBind = new ObservableCollection<ClientType>(somewhereElse);
toBind.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
types.InsertAllOnSubmit<AddressType>(e.NewItems.Cast<AddressType>());
};
this.DataContext = toBind;
Then, when the user wants to add a new item:
ObservableCollection<ClientType> toBind = this.DataContext as ObservableCollection<ClientType>;
System.Diagnostics.Debug.Assert(toBind != null);
ClientType ct = new ClientType();
ct.Description = "<new client type>";
toBind.Add((ct);
Calling toBind.Add will cause the CollectionChanged event handler above to call InsertOnSubmit on the original Table instance, so you can call SubmitChanges() when convenient. Obviously, you'd probably want to do the same with Remove ...
Hope that helps :)
It may be worth looking into the MVVM pattern. In MVVM you have a ViewModel which wraps your Model, so you would have a ClientTypeViewModel class.
public class ClientTypeViewModel : INotifyProperyChanged
{
public ClientTypeViewModel(ClientType dataModel)
{
this.dataModel = dataModel;
}
public string Description
{
get { return this.dataModel.Description; }
set
{
this.dataModel.Description = value;
// Raise PropertyChanged event
}
}
private ClientType dataModel;
}
And something like an ApplicationView model, which would contain an ObservableCollection of ClientTypeViewModels.
public ApplicationViewModel
{
public ObservableCollection<ClientTypeViewModel> ClientTypes { get; private set; }
}
You then bind to ApplicationViewModel.ClientTypes instead of the plain data model. This way, your view will be automatically updated whenever a new item is added to ClientTypes, or a property is changed on the ClientType view model. ApplicationViewModel can listen for changes on the ClientTypes collection and automatically add newly added items to the DataContext.
You may think it's overkill for your application, I don't know - but MVVM is definitely somthing worth learning. If it feels like you're struglling or fighting with WPF, MVVM is likely where to look ;)
Look at CreateBindingList.
I think it's just because you're assigning the same reference to the DataContext. Hence, WPF doesn't see the need to refresh the binding. The easiest way around this is to:
// rebind
this.DataContext = null;
this.DataContext = db.ClientTypes;