I have a ListView with 3 columns (User Name, Active, and Group) The ItemsSource is bound to a staticresource
ItemsSource="{Binding Source={StaticResource SortedUsers}, UpdateSourceTrigger=PropertyChanged}"
the static resource is defined as:
<CollectionViewSource x:Key="SortedUsers" Source="{Binding UsersList, UpdateSourceTrigger=PropertyChanged}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription
Direction="Ascending"
PropertyName="UserName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
The user can select an item in the list and click "Modify" to change information about the user or can click an "Add" button to add a new user to the list. Both the Add and Modify use the same modal dialog to allow setup/modify of a new user. Adding a user works fine, the new user shows in the list once the modal dialog closes. however, modifying an existing user doesn't update the data in the ListView until the window that contains the listview is closed then reopened. What do i need to do to have the ListView update for modify?
Did u try using
((CollectionViewSource)this.FindResource("SortedUsers")).View.Refresh();
Although in MVVM the steps above wont be allowed.
So if the source collection (UsersList) is ObservableCollection collection view source will atomatically refresh on collection changed notifications of UsersList.
Let me know if this helps.
Do your data objects implement INotifyPropertyChanged interface?
See: https://stackoverflow.com/questions/6713288/databinding-fail-after-using-controltemplate/6713334#6713334
INotifyPropertyChanged notifies the UI that something has changed on the class implementing it. Of note: I dont know what kind of collection you are using to hold your classes (which gets passed into the CollectionViewSource), but I almost always use ObservableCollections. They implement CollectionChanged and notify the UI when items are added, removed and the collection is refreshed.
Related
I'm learning WPF and am really trying to drill down on binding until I can do it like a boss. But I'm having a bit of an issue.
In xaml, I have a ListBox like so:
<ListBox Name="AccountsDisplay"
SelectedValuePath="Username"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Accounts}"
/>
And I have a TextBox that's pulling "Username" from said ListBox.
<TextBox Text="{Binding ElementName=AccountsDisplay, Path=SelectedValue}"/>
Note: Accounts is both an ObservableCollection and all objects added to it are of type Account, which is purely a data class that extends INotifyPropertyChanged, and has properties such as Username, Password, etc.
The TextBox is pulling the Username property properly, and updates any time I change selection in the ListBox (which is populated with pretty lil' Account info entries, as intended), but I cannot then click on the TextBox and attempt to update the Username portion of entries in the ListBox.
My gut tells me I'm going about this TextBox the wrong way, since I won't be able to make other TextBoxes and pull any additional Account properties (thanks to SelectedValuePath already having a value), but I'm too new to WPF & XAML to see where the error is in my ways!
Am I barking up the right tree, or is there a more appropriate way to get a TextBox to synchronize with (and edit) the data in another UI Element?
Consider binding to the property of the actual DataContext of the list item selected.
I do not use SelectedValue because I am not sure of it's purpose.
Because of my ignorance regarding the use of that particular property, I just rely on SelectedItem.
I can then specify the property name that I want to bind to relative to the DataContext of the selected list item.
<TextBox Text="{Binding ElementName=AccountsDisplay, Path=SelectedItem.Username}"/>
WPF, MVVM
I'm finding that if I use a CollectionViewSource with my ComboBox, when I close the window, an extra call to the SelectedValue Setter is executing, if SelectedValue is bound to a string property. If I set the ItemsSource binding directly to the VM, this call does not happen. The extra call is causing values to change in the VM, resulting in incorrect data. I have other ComboBoxes setup the same way, but they bind to integer values.
CollectionViewSource definition:
<CollectionViewSource x:Key="AllClientsSource" Source="{Binding AllClients}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ClientName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
ComboBox with CollectionViewSource:
<ComboBox Grid.Column="2"
ItemsSource="{Binding Source={StaticResource AllClientsSource}}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
ComboBox direct to VM (Forgoing sorting):
<ComboBox Grid.Column="2" ItemsSource="{Binding AllClients}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
Can anyone tell me why there is an extra setter call using the CollectionViewSource? What's different about the string binding? Is there a way to properly work around it?
EDIT: I tried changing it up and using the SelectItem property on the ComboBox. Same result. So it seems that if the item is a scalar data type, it works as expected. If it's an object, you get an extra setter call with a null value. Again, if I remove the CollectionViewSource from the equation, it works as expected.
EDIT, AGAIN: I added a link to a sample project that illustrates the issue. Targets .Net 4.5.
Run the project.
Click to display View One
Select a Client and the client's name will display on the right.
Click to display View Two
Go back to View One - Note that the selected client is no longer selected.
Click to display View Three
Select a Region and the region's name is displayed on the right.
Go back to View Two
Go back to View Three - Note that the selected region is still selected.
The only difference between the views is that One and Two use a CollectionViewSource. Three binds directly to the ViewModel. When you move to a new tab from One or Two, the setter for the selected item is getting called with a null value. Why? What's the best work-around?
Thanks.
Apparently this is caused when the CollectionViewSource is removed from the visual tree... I moved the CollectionViewSource to the ViewModel and exposed it as a property and the issue is effectively worked-around.
I'm binging DataGrid to ObservableCollection I update collection on timer and grid updates nicely which is what I want.
However, even though I can sort collection on initial population - when I append new items they go to bottom. User can sort grid and then it looks good.
I would like to "force" grid to sort by specific column when it is initialized. Can it be done in XAML or somehow in MVVM manner?
EDIT
Just to give more details. This is mail client main screen. I've got service that polls for data and if new mail arrived - it get's appended to collection. Sorting at this point is not desirable for couple reasons. First, this is list of object. Second, user may decided to sort by other column using DataGrid
It feels right to set grid to sort by column I initially want (Time Received) and let users sort by any column while preserving this order when new items arrive.
You should wrap your ObservableCollection in a collection view. Generally you will wrap the collection in either a CollectionViewSource, or a PagedCollectionView. The CollectionViewSource can be used declaratively in XAML, but can't be controlled from the ViewModel. You can however wrap your collection in a PagedCollectionView, and expose that from your ViewModel. This MSDN article should help you: http://msdn.microsoft.com/en-us/library/dd833072(v=vs.95).aspx. Collection views are very powerful tools, and will help you elegantly bridge the gap between a View and a ViewModel. Hope this helps.
[Edit] Edited the answer to correspond to the solution
You can try adding SortDescriptions by including the Collection in a collectionView. In example.
<CollectionViewSource Source="{Binding ObservableCollection}" x:Key="Data" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="City" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Then in your ItemsSource set the following binding.
ItemsSource={Binding Source={StaticResource Data}}
Just sort inside your ViewModel, where ever you're updating the collection from the timer sort it. In fact when you sort the collection the view should rebind if you're binding correctly in the View/Xaml to the datacontext(the viewmodel).
Anytime you update an Observable collection in a viewmodel anything that's bound to it will be updated.
In a MVVM WPF application, I have a master-detail View that is used to manage Alarms. In the AlarmDetails View I have several properties that are set through ComboBox controls. One of those is the Alarm's Source, and those sources can be changed in another View (SourcesView), thus changing the combobox content in the AlarmDetailsView.
The way the data flows is relatively straightforward
A source is added to the collection in SourcesView
A message is sent from SourcesView and is received by all AlarmDetailsViewModel
The Sources collection in each AlarmDetailsViewModel is changed accordingly and since it is an ObservableCollection it notifies each binding targets of the change
The AlarmDetailsView has its DataContext set to a selected AlarmDetailsViewModel, thus the ItemsSource property of its Sources ComboBox is updated (I see it using a dummy converter, in debug I can see the updated collection being passed to ItemsSource, and it contains the newly added item)
After all this happened, when I navigate to the AlarmsView/AlarmDetailsView Tab, the items in the Sources ComboBox do not contain the new item.
When I select a new Alarm in the master list and I select the original one back, the combobox displays the new item just fine (when i select an item in the master list, the DataContext property of the AlarmDetailsView gets updated).
I am at a loss with this, I have been poundering this for a while.
Anyone have an idea? Any debugging suggestions?
Thanks,
Alex
EDIT: The ComboBox that does not update is located within the AlarmDetailsView, which means that it is used to set a property of the Alarm object (its Source, namely). Hope this helps.
EDIT 2: In addition to the answer below, another solution was to use an ObservableCollection instead of a List. For some reason, raising a PropertyChange event on the List (as described above) triggered the ComboBox to refresh its ItemsSource, but not to add the new items to its drop-down. Using the ObservableCollection, there is not need to raise the propertychange event since it manages it all by itself.
** EDIT **
Ugly solution but worked for me if someone has a better solution please share :)
Emptied the collection, notified the GUI, added the real collection notified to gui
Try using CollectionViewSource to handle your Master/Detail bindings.
Heres an example
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}" />
<Window.Resources>
DataContext="{Binding CurrentItem, Source={StaticResource data}}"
I have question on the checkbox.
First of all,
I have a usercontrol which has a list box like this and this user control will be switched by 2 button and then the data source is changed and then the the displayed officer status will be changed:
When I check the checkbox, Officers[0].IsOnDuty will be changed to true.
The problem is:
When I click another button and switch to another data source, this checked check box is still checked but the Officers[0].IsOnDuty for this data source is false.
How to solve this?
The data context of the list box item is an item for your officers collection, not the collection itself. And using a one way binding is incorrect, as the data source (the officer) will not be updated. So change the DataTemplate to:
<CheckBox IsChecked="{Binding Path=IsOnDuty, Mode=TwoWay}" />
*Here is the list box xaml:
<ListBox ItemsSource="{Binding OfficersCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Officers[0].IsOnDuty, Mode=OneWay}" />
*
The problem with your approach is that once you change the ItemsSource (by switching to the next page) your chekcbox is still bound to the item of the first collection. I think this happens because you explicitly use an indexer for the binding Path=Officers[0].IsOnDuty
Your samplelist box xaml does not really make sense. the ItemsSoruce is a OfficerCollection and your ItemTemplate binds to a collection of Officers too. Depending on what you are trying to accomplish you should do one of the following:
If your are just interested in the first officer (as your sample suggest), add a DependencyProperty FirstOfficer (or a INotifyPropertyChanged) property to your collection and bind to it: IsChecked="{Binding Path=Officers.FirstOfficer, Mode=OneWay}"
If you however are interested in all Officers and want checkboxes for all of them you should create a DataTemplate for the Officer type and use this as the ItemTemplate.
Generally you can stay out of a lot of trouble if you stick with MVVM and really tailor your ViewModel objects very close to what the View needs so you can bind your View to the ViewModel in the simplest possible way. Think of the ViewModel as the View you want to build but without a visual representation.