I have created a user control containing a ListBox which is bound through a CollectionViewSource. The ListBox has CheckBoxes for the user to do multiple selection on. I would like the list to be sorted with the selected CheckBoxes at the top. I am hoping for this to work as soon as the user selects or unselects something. I can't seem to find anything thing that does this through xaml. What am I doing wrong?
In my xaml
<CollectionViewSource x:Key="SortedItems"
Source="{Binding Items, ElementName=Selector}"
IsLiveSortingRequested="True">
<CollectionViewSource.LiveSortingProperties>
<System:String>IsSelected</System:String>
</CollectionViewSource.LiveSortingProperties>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="IsSelected"
Direction="Descending" />
<scm:SortDescription PropertyName="CodeDescriptionText" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
and my list box
<ListBox x:Name="ItemsControl"
SelectionMode="Multiple"
ItemsSource="{Binding Source={StaticResource SortedItems}}"
ItemTemplate="{Binding ItemTemplate, ElementName=Selector}"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
Grid.Row="1"
Grid.ColumnSpan="3">
I also had the same issue, and eventually I found out the problem is with the data source, and I suspect that your issue is the same as mine.
While CollectionViewSource can work with a number of different types of data sources, not all of them will work with live sorting. To ensure everything work smoothly, it is best to use an ObservableCollection of items that implement INotifyPropertyChanged for the data source.
But if you have to use a custom collection class instead of ObservableCollection, then make sure that class implements IList, not just the generic IList<>. If you don't, live sorting will most likely be disabled. And to ensure all other areas work smoothly, I strongly advise you to also implement INotifyCollectionChanged and INotifyPropertyChanged for it as well.
No matter which collection class you use, the items contained in it still have to implement INotifyPropertyChanged. There are no other ways around it.
In your ItemTemplate, are you binding bool properties to the Checkbox.IsChecked properties? If you are, then you should be able to set that property as the SortDescription.PropertyName property as it seems that you are doing. If not, then that is what you need to do.
Related
CollectionViewSource class is supposed to provide a so-called "view" over data. This includes data filtering, sorting and grouping.
Now, one may think, that this is a responsibility of a View in MVVM architecture, but very often it is user, who decides, what kind of grouping, sorting or filtering he wants. These are business decisions, which belongs to viewmodel. Moreover, filtering is performed by taking an element and deciding, whether it matches filter or not, and that is definitely viewmodel's responsibility (possibly then passed to a service).
It seems then, that CollectionViewSource is a good candidate to be directly exposed from the viewmodel, because the latter then have full control over its properties (eg. GroupDescriptions). But even though this class resides in Windows.Data namespace, it is still in PresentationFramework assembly and I feel really uncomfortable to reference it from my BusinessLogic assembly, because I'm hardly tying it to a specific (nomen omen) presentation framework.
From what I saw, the most common place for CollectionViewSource are control's (window's) resources from which (surprisingly) I can bind to the viewmodel. But then manipulating filtering, sorting and grouping is a mess, because you have to push information manually between view and viewmodel. Just for one example:
<CollectionViewSource x:Key="SuggestionItems" Source="{Binding Suggestions}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="{Binding SuggestionGroupByProperty}"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
This will not work, because PropertyGroupDescription.PropertyName is not a DependencyProperty. So I'd probably have to design my own Xaml extension to be able to bind such information from viewmodel to a view.
I failed to find any tutorial or documentation on how is ColletionViewSource supposed to fit into MVVM/WPF framework. I actually got an impression, that this is not really too much thought-through, because, for instance, placing the CollectionViewSource anywhere outside resources requires some really nasty shenanigans to simply push collection inside.
For example (mind the SelectedItem):
<DataGrid x:Name="grid"
AutoGenerateColumns="False"
ItemsSource="{Binding}"
SelectedItem="{Binding Path=DataContext.SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay}">
<DataGrid.DataContext>
<CollectionViewSource x:Name="myViewSource" Source="{Binding MyCollection}" Filter="CollectionViewSource_OnFilter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Container" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</DataGrid.DataContext>
<!-- (...) -->
</DataGrid>
So how the CollectionViewSource should canonically fit into the MVVM pattern, so that viewmodel has enough control over it? And if it shouldn't, then how this scenario (collection grouping, sorting, filtering) should actually look in MVVM?
I have a Model with one of it property being Order with is a type of int. The model is put inside an ObservableCollection say for example ModelList is bound to a listbox.
Using this code
<CollectionViewSource Source="{StaticResource ModelList}" x:Key="SortedItems">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Order"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Then this list is Bound to a listbox using
<ListView ItemsSource="{Binding Source={StaticResource SortedItems}}" />
Works Fines. But I want to change the order of the list by its Order property, that is when i change the Order via code I want the listbox to reflect the change.
How can I achieve this.
In order to resort a CollectionViewSource, you need to update the SortDescriptions property
To access your CollectionViewSource from code, declare it in your viewmodel and bind to it (instead of declaring it in XAML). For items in the visual tree, you can use x:Name, though this does not seem to work from a Resources tag, at least via initial testing
I have a TreeView that I am binding to an ItemsSource that creates a CheckBox for each item. Here is the xaml:
<TreeView x:Name="ReasonTreeView" Height="Auto" Background="Transparent"
BorderThickness="0" IsTabStop="False"
ItemsSource="{Binding Path=AnswerOptions}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type QSB:Answer}" ItemsSource="{Binding Path=AnswerOptions}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5"
IsChecked="{Binding Path=IsSelected}"
IsEnabled="{Binding Path=Value,
Converter={StaticResource ReasonValueToEnabledConverter}}"
Visibility="{Binding Path=AnswerOptions,
Converter={StaticResource ParentNodeVisConverter}}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
In my application I then create multiple instances of these. Depending on the instance of the TreeView, certain CheckBoxes need to be disabled so the user can not select them, however I'm uncertain of how I can access the individual items in the HierarchicalDataTemplate in the code.
After looking around for a while the only thing I can think of is to build the whole TreeView in the code behind instead of the xaml, but I would rather not have to resort to that. Is there anything else that I can do?
To help clarify my point and for illustrative purposes, this is essentially what I want to be able to do (in pseudocode): ReasonTreeView.ItemsSource[5].IsEnabled = false;
Which would disable the CheckBox (and any other controls in that HierarchicalDataTemplateItem) at index 5 of the TreeView's ItemsSource
Let me know if more information is needed
I meant that binding on the checkbox's isenabled property Path=Value. That Value member has to be bool and implement INotifyPropertyChanged then you can control IsEnabled from your model. Dont forget to add Mode=Twoway to your binding
Instead of accessing the CheckBox through Control.ItemsSource property you should make the change in your underlying collection (that is itemssource of your control). After making the change notify the View (your Control) that data has been changed so update the control.
Implement INotifyPropertyChanged in your underlying class and after changing the Property (which is responsible for Enabled/Disabled) value Notify the View.
If you are not familiar with concepts of Data Binding and INotifyPropertyChanged, I would suggest you to read some basic tutorials about it. It is one of the major feature of WPF which makes life very easy for doing things like yours
I am new to MVVM, I have a checkedlistbox in a view with the list of titles(have bound the exposed property in ViewModel to this checkedlistbox control)...
Here is my XAML code that populates the ListCheckBox -
<ListBox x:Name="lstCode" ItemsSource="{Binding Code,Mode=TwoWay}" Grid.Row="1" Style="{StaticResource ListBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="chkBox" IsChecked="{Binding IsChecked,Mode=TwoWay}" Content="{Binding Code_Name}" Margin="0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This control shows the correct list of items with checkboxes for each item in the listbox...
What should be the code in viewmodel to make it work in two way - while getting the codes from database, it should automatically selected the code from the listcheckedbox and when the user selects one or more codes, the viewmodel should be able to know the items selected...
In general, for TwoWay binding, you will need to implement the INotifyPropertyChanged interface on the ViewModel you want to bind to.
In this case, your ViewModel will have to provide a property that returns a collection that your view can bind to, e.g. an ObservableCollection.
This ObservableCollection already allows you to add, update, and delete items in that list in a way that automatically communicates the changes between View and ViewModel.
For the rest I suggest to start digging into MVVM depths. To fully take advantage of WPF's capabilities, you will need to understand the basics for yourself. A great starting point is this SO thread: MVVM: Tutorial from start to finish?
What is the difference between:
<DataGrid
ItemsSource="{Binding MyCollection}"
/>
and...
<CollectionViewSource x:Key="CollectionData" Source="{Binding MyCollection}"/>
...
<DataGrid
DataContext="{StaticResource CollectionData}"
ItemsSource="{Binding}"
/>
They both seem to work. The only difference is that the second snippet, I can't bind to the SelectedItem. So why would someone pick one strategy over the other? Why wouldn't someone just use the first snippet? Thanks.
MSDN states...
CollectionViewSource has a View
property that holds the actual view
and a Source property that holds the
source collection.
CollectionViewSource seperates the actual collection from the view that is representing the collection. This give you the ability to change the visual structure of the visible collection (think filtering out certain items as you type) without actually changing the underlying collection. It is a wrapper around the actual collection containing the objects needing a visual representation. Bea has a great article about it.
In addition you'll notice the explicit wrapping taking place in the CollectionViewSource in your second example...
Source="{Binding MyCollection}"
Then the CollectionViewCource is now being bound to via the DataGrid providing the seperation I mentioned earlier; whereas the collection was being bound to directly in your first example.
A CollectionViewSource has more features that an ObservableCollection or whatever IEnumerable you use for your ItemsSource. For example, it has SortDescriptions that can allow you to group data. An example can be found here.
TLDR; its a more powerful data structure.
As an aside, provided IsSynchronizedWithCurrentItem is true on the DataGrid, you can bind to the SelectedItem by appending a slash i.e. {Binding /}