this.Teams = new ObservableCollection<TeamViewModel>(_teams); - silverlight

I'm new to mvvm and I can't seem to find the answer to the following:
I have a datagrid in Silverlight which binds to a observablecollection Teams of a viewmodel:
First I fetch a couple of team objects in a List and then I feed them to my observablecollection
(Public ObservableCollection Teams)
Now I want to add a row in the Datagrid so I created a button in the view and hooked up an Icommand which adds more or less an empty team to my collection:
private void Add()
{
Team _team = new Team();
_team.recid = 1;
_team.teamid = "";
_team.ruleset = "";
_team.name = "";
this.Teams.Add(new TeamViewModel(_team));
}
I think the notifypropertychanged doesn't fire (only when I set the collection), what do I have to do to notify the view of my changed viewmodel?
Thanks in advance,
Michael

Adding to an ObservableCollection does not raise PropertyChanged, but instead raises a collectionchanged event.
The easiest way is to manually RaisePropertyChanged for the Team property at the end of your add function.

If you databound your Teams property (which is of the type ObservableCollection), your Grid should automatically update.
There's no reason to call RaisePropertyChanged("Teams") when adding or removing items from a ObservableCollection.
So in short, it should already work. If it doesn't, you need to post more code.

I guess it didn't show up in the grid because I added empty strings for teamid / name. When I do it like this the row is visible in the grid: _team.recid = 1; _team.teamid = "test"; _team.ruleset = "test"; _team.name = "test"; _team.rowstate = GlobalVariables.Rowstate.added; Thanks Claus / Brandorf for your comments!
Regards, Mike

Related

Where the combobox bound items are coming from?

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.

Observable Collection is not updating the datagrid

I am using a Dim All_PriceLists As System.Collections.ObjectModel.ObservableCollection(Of BSPLib.PriceLists.PriceListPrime) where PriceListPrime implements Inotify for all properties in it.
I bound the All_PriceList to a datagrid as DataGrid1.ItemsSource = All_PriceLists but when I do All_PriceLists=Getall() where Getall reads and gets the data from the DB, the datagrid is not updating.
It updates only when I hack it this way:
DataGrid1.ItemsSource = Nothing
DataGrid1.ItemsSource = All_PriceLists
Could you please tell me where I have gone wrong or what I should implement. Thank you.
You have several solutions to your problem
Update the ItemsSource directly (instead of replacing the local member variable)
DataGrid1.ItemsSource = new ObservableCollection(Of PriceListPrime)(GetAll())
Update the ObservableCollection (as mentioned in another answer)
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
Set your DataContext to a view model and bind to a property of the view model
Dim vm as new MyViewModel()
DataContext = vm
vm.Items = new ObservableCollection(Of PriceListPrime)(GetAll())
The view model will implement INotifyPropertyChanged and raised the PropertyChanged event when the Items property is changed. In the Xaml your DataGrid's ItemsSource will bind to the Items property.
The problem is that you are not updating the collection, you are replacing it, which is different.
The datagrid remains bound to the old list and the updated data is stored in a new unbound collection. So, you are not hacking a solution, your are binding the datagrid to the new collection, which is correct.
If you want a more automatic solution, you should bind your datagrid to a dataset/datatable, which is totally different code.
You should update ObservableCollection instead of creating new one if you want the app to react on your changes.
So, clear All_PriceList collection and add new items into it. Example:
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
ObservableCollection doesn't support AddRange, so you have to add items one by one or implement INotifyCollectionChanged in your own collection.

How to programmatically select a TabItem in WPF TabControl

I would like to know how to select a specific TabItem in a WPF TabControl.
I tried these bellow but nothing work!
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabControl.SelectedValue = MyTabItem
MyTabItem.IsSelected = True
As #Chris says, any of the first three things should work and as #Phyxx says, it doesn't always really work. The problem is some subtle thing about the order of property changes. To work around it you need to let the WPF invoke your tab-selection code in its own time:
Dispatcher.BeginInvoke((Action)(() => MyTabControl.SelectedIndex = x));
This does just what Phyxx' timer does, but in a slightly less extreme way.
All your examples except the third one are correct and will work. The problem must be at another location. Maybe you reset the item after setting or your code never is called?
Valid
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabItem.IsSelected = True
Invalid
MyTabControl.SelectedValue = MyTabItem
Loop through the TabItems and for the tab to be selected, set
tabItem.IsSelected = true
If there are any other place due to binding changing you will see problem. Otherwise, the above code should work.
One thing which hasn't been mentioned above:
The main reason something like this won't work is that the tab items do not have the "Name" property set. Each tab item of the tab control which you want to navigate to programmatically must have its name property set for any of the above code to work.
<tabItem Name="tab1"></tabItem>
I have implemented a small MVVM bindings based solution for selecting tab panels pragmatically.
define a property in your view model - Selected int type
bind the property in your view
<TabControl
x:Name="TabsCandidate"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
SelectedIndex="{Binding Selected}"
private int _selected;
public int Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
Set the value to Select property, simply the binding will activate the tab panel.
if you want to navigate from tab panel inside parent tab panels, this solution will simply works, All you need to do is, access the data context of your control and set it
// set the property value of the view model which points the index of the tab controller.
((CandidateViewModel)((System.Windows.FrameworkElement)candidateTab.Content).DataContext).Selected = CandidateLogTabIndex;
Try to set the MyTabControl.SelectedIndex = x in the event handler of DataContextChanged or Loaded of your UI. Hope this will work.
I tried all the methods that should have worked, but like you nothing actually changed the selected tab. In the end I got it to work by putting the tab selection code in a DispatcherTimer tick.
DispatcherTimer switchTabTimer = new DispatcherTimer();
switchTabTimer.Interval = new TimeSpan(0);
switchTabTimer.Tick += (object timerSender, EventArgs timerE) =>
{
myTabControl.SelectedIndex = 0;
switchTabTimer.Stop();
};
switchTabTimer.Start();
if you don't know the index of the tab (hint its not TabIndex) use:
private async Task ChangeTabTo(TabItem wantedTab) {
int index = 0;
for (var i = 0; i < TabControl.Items.Count; i++) {
var tab = TabControl.Items[i];
var t = tab as TabItem;
if (t == null) continue;
if (t == wantedTab) {
index = i;
break;
}
}
await Dispatcher.BeginInvoke((Action)(() => TabControl.SelectedIndex = index));
}
or modify it to search by name if you don't want to keep a reference to the tab
I'm throwing my 2 cents on the topic, since it might help someone out. I'm using WPF with Prims framework.
I was unable to select a tab by binding to SelectedItem or SelectedIndex - it didn't work. I was also unable to set TabItem.Name value from within TabControl.ItemTemplate or TabControl.ContentTemplate.
Instead I implemented event-based solution:
Add Name value for my TabControl.
Create an event - in Prism that means define a class that derives from PubSubEvent<T> (T is the type of parameter - in my case that was the ViewModel object bound to the TabItem>.
Publish that event whenever I want to a tab to be selected.
Subscribe to the event within my View.cs class and set the TabControl.SelectedItem programmatically using FindName.

Make a Dialog ViewModel binding ready, call Dialog and return data from it in MVVM

Do you see a better way how I can call/contstruct a Dialog from a Controller/ViewModel return data from it and set the DocumentViewModel as DataContext of the Dialog?
The problem is I can not use View first approach in the DocumentDetailWindow and its belonging UserControl because I can not set the Model to the DocumentViewModel`s Document Property in XAML!
How would you solve that scenario? Make Dialog properly bindable, call dialog and return data from it to the LessonPlannerController so the new Document can be saved on database and added to the bound ObservableCollection of Documents so the GUI is refreshed with one more Document.
LessonPlannerController/ViewModel:
private void OnAddDocument()
{
DocumentDetailWindowaddDocumentWindow = new DocumentDetailWindow();
DocumentViewModeldocumentViewModel = new DocumentViewModel();
documentViewModel.Document = new Document();
documentViewModel.Repository = new LessonPlannerRepository();
documentViewModel.SaveDocumentDelegate += new Action<Document>(OnSaveDocument);
addDocumentWindow.DataContext = documentViewModel;
addDocumentWindow.ShowDialog();
}
UPDATE:
I have even thought about not making this => documentViewModel.Document = new Document();
because why do I need a Model in a in a ViewModel when I can just do this:
IN REALITY those properties have a NotifyPropertyChange...
public string DocumentName {get;set;}
public string Keywords {get;set;}
then I could create a Document instance with the above properties in the DocumentViewModel, when the Save command is executed and then pass the Document via Callback to the LessonPlannerControl etc... it seems View first is not working when you have to subscribe your event to a method. Only ViewModel first works then.
What do you think? Should I not use ocumentViewModel.Document = new Document();
and create those 2 properties in the DocumentViewModel. Hm... but why recreate if they are already in the Document Model?...
Do these answer your question?
WPF MVVM dialog example
or
http://www.codeproject.com/KB/architecture/MVVM_Dialogs.aspx

Inserting a new object into L2S table and databinding to it prior to SubmitChanges() in WPF

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;

Resources