WPF CollectionViewSource Data Display Issue - c#

I have a ComboBox setup to display a bound list of ApplicationNames. The list type the ComboBox is bound to is ObservableCollection<Apps>. I am using a CollectionViewSource to do the sorting as so:
<CollectionViewSource
x:Key="Apps"
Source="{Binding Path=Apps}"
>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="AppNames" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
The ComboBox is written as:
<ComboBox
SnapsToDevicePixels="True"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Grid.RowSpan="1"
DataContext="{StaticResource Apps}"
ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource ApplicationItemStyle}"
ItemTemplate="{StaticResource applicationComboTemplate}"
>
</ComboBox>
Now when I open the View that houses this combobox all the application names display properly in the combobox and are sorted. The issue I am having is when I close that view and then re-open the view. When I re-open the view everything loads exactly as it did the first-time, in the way of how I get the ObervableCollection, but this time around nothing shows up in the ComboBox. Also, in the ViewModel I do clear the collection and remove all registered events when the View and ViewModel close.
Here is where I close:
void CleanUpApps()
{
if (this.Apps != null)
{
foreach (var app in this.Apps)
app.Dispose();
this.Apps.Clear();
this.Apps.CollectionChanged -= OnAppsCollectionChanged;
}
}
If I copy {Binding Path=Apps} from the CollectionViewSource and place it over {StaticResource Apps} in the ComboBoxes DataContext, everything works, except the sort now of course, the first time I open the view and every time after.
I am trying to find out what I am doing wrong in the CollectionViewSource, that is causing the second time around loading the view to not have any application names display in the combobox.

Related

WPF ComboBox bind text to selected item

I try to bind a ComboBox to a collection:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding YAxes}"
SelectedItem="{Binding SelectedYAxis, Mode=TwoWay}"
DisplayMemberPath="AxisTitle"
SelectedValuePath="AxisTitle"/>
Everything is fine, except Text of this ComboBox. On selection of item, the setter on SelectedYAxis fires and notifies, that property has been changed:
private IAxis _selectedYAxis;
public IAxis SelectedYAxis
{
get => _selectedYAxis;
set
{
_selectedYAxis = value;
OnPropertyChanged(nameof(SelectedYAxis));
}
}
but the text on ComboBox never changes to the selected items AxisTitle. How to display an AxisTitle of SelectedItem as a text of ComboBox?
UPD: Text is never shown, even if it's set explicitly:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding XAxes}"
SelectedItem="{Binding SelectedXAxis, Mode=TwoWay}"
DisplayMemberPath="AxisTitle"
Text="Asdasd"/>
It doesn't set the text of ComboBox to "Asdasd".
UPD 2: I've changed the things to use DataTemplate, but this didn't work as well:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding YAxes}"
SelectedItem="{Binding SelectedYAxis, Mode=TwoWay}"
ItemTemplate="{StaticResource AxisCBTextTemplate}"/>
And the resource section above:
<DataTemplate x:Key="AxisCBTextTemplate">
<TextBlock Text="{Binding AxisTitle}"/>
</DataTemplate>
UPD 3
An illustration to what do I mean:
The task of displaying some selected text should be trivial, but it has difficulties.
I've found the root cause of this issue. Each IAxis (it is a SciChart axis object) from XAxes and YAxes is already dispayed on the graph (i.e. bound). Binding them to other controls (like ListBox) causes an exception: "Must disconnect specified child from current parent Visual before attaching to new parent Visual.", I found it out while trying to bind them to ListBox.
Seemes like ComboBox catches such exceptions and doesn't output StackTrace for any case. In my case this exception was wrapped into NullReferenceException and occurred only on click on a ComboBox, that has no ItemTemplate set. Though I may not be fully correct in details, replacing XAxes and YAxes with collections of strings solves this issue.

DataTemplate passing incorrect command parameter in WPF

I have the following in a WPF project:
Main Window
<Window x:Class="DataTemplateEventTesting.Views.MainWindow"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
xmlns:vm="clr-namespace:DataTemplateEventTesting.ViewModels"
xmlns:vw="clr-namespace:DataTemplateEventTesting.Views">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions> ... </Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding SubViewModels}"
SelectedValue="{Binding MainContent, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type vm:SubViewModel}">
<TextBlock Text="{Binding DisplayText}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Grid.Column="1" Content="{Binding MainContent}">
<ContentControl.Resources>
<DataTemplate x:Shared="False" DataType="{x:Type vm:SubViewModel}">
<vw:SubView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
SubView (View for SubViewModel)
<UserControl x:Class="DataTemplateEventTesting.Views.SubView"
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Grid>
<ListView ItemsSource="{Binding Models}">
<ListView.View> ... </ListView.View>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
Command="{Binding PrintCurrentItemsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
</Grid>
</UserControl>
The problem is with the SelectionChanged EventTrigger in SubView.
PrintCurrentItemsCommand accepts a ListView as a parameter and prints a count of its items by executing the following method:
private void PrintCurrentItems(ListView listView)
{
System.Diagnostics.Debug.WriteLine("{0}: {1} items.", DisplayText, listView.Items.Count);
}
When I navigate from one SubView (where some items in its ListView are selected) to another SubView, the SelectionChanged event is fired on the ListView of the first SubView. This executes the PrintCurrentItemsCommand on the correct SubViewModel, but passes the new (incorrect) ListView as a parameter. (Either that, or the event is being fired by the new ListView, and the command is using the DataContext from the old ListView.)
Thus, while SubViewModel with DisplayText of "Sub1" has 2 items in its Models collection, and "Sub2" has 3 items, I see the following in the Output window:
Sub1: 2 items. // selected an item
Sub1: 3 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 2 items. // navigated to Sub1
Sub1: 2 items. // selected an item
Sub1: 3 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 2 items. // navigated to Sub1
Obviously the expected behaviour would be that the correct ListView would be passed.
The main confusion is that, for example, the command for "Sub1" is able to access the ListView for "Sub2" at all.
I read something about WPF caching templates, and thought I had found the solution in setting x:Shared = "False" on the DataTemplate, but this didn't change anything.
Is there an explanation for this behaviour? And is there a way around it?
I was able to reproduce the behavior you're seeing: I select an item in the right hand listview, and then change the selection the left hand listview. When the command is invoked, inside the Execute method, ! Object.ReferenceEquals(this, listView.DataContext). I would have expected them to be equal.
With this binding for Command, they were still not equal:
<i:InvokeCommandAction
Command="{Binding DataContext.PrintCurrentItemsCommand, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
I didn't expect much from that experiment, but it didn't take very long to try.
Unfortunately I don't have time at the moment to investigate this in depth. I haven't been able to find the source code for System.Windows.Interactivity.InvokeCommandAction, but it certainly looks as if somewhere in the flurry of events and updates accompanying the change, things happen in the wrong order.
Resolution
The following code is almost unbearably ugly, but it behaves as expected. You could make it less ugly by writing your own behavior. It wouldn't need to be as gloriously generalized as InvokeCommandAction. Being less generalized, it would be less likely to misbehave the same way, and even if it did, you've got the source and can debug it properly.
SubView.xaml
<ListView
ItemsSource="{Binding Models}"
SelectionChanged="ListView_SelectionChanged"
>
<!-- snip -->
SubView.xaml.ds
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
var cmd = listView.DataContext?.GetType().GetProperty("PrintCurrentItemsCommand")?.
GetValue(listView.DataContext) as ICommand;
if (cmd?.CanExecute(listView) ?? false)
{
cmd.Execute(listView);
}
}
Slightly off topic, this would be preferable:
protected void PrintCurrentItems(System.Collections.IEnumerable items)
{
//...
XAML
<i:InvokeCommandAction
Command="{Binding PrintCurrentItemsCommand}"
CommandParameter="{Binding Items, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
Code behind
if (cmd?.CanExecute(listView) ?? false)
{
cmd.Execute(listView.Items);
}
The reasoning is that a command that takes IEnumerable as a parameter is vastly more generally useful than one that expects any collection of items to be packaged in a listview. It's easy to get a collection of items from a listview; it's a real pain to need to have a listview around before you can pass somebody a collection of items. Always accept the least specific parameter you can without shooting yourself in the foot.
And from an MVVM standpoint, it's considered very bad practice for a viewmodel to have any concrete knowledge of UI. What if the UI design team decides later on that it should use a DataGrid or a ListBox instead of a ListView? If they're passing you Items, it's a total non-issue. If they're passing you ListView, they have to shoot you an email asking you to change your parameter type, then coordinate with you about it, then there's extra testing, etc. And all to accommodate a parameter that didn't actually need to be ListView at all.
It turns out that the problem was caused by the persistence of the DataTemplate.
As Ed Plunkett observed, it was the same ListView the whole time, and only the DataContext was changing. I imagine that what was happening was that the navigation took place, then the event was fired, and by this time the DataContext had changed - a simple property change.
In the hoped for behaviour, the old ListView would fire the event, and execute the first ViewModel's command, and this would occur after the navigation, hence, its items would be counted at 0. But with DataTemplate sharing, the first ListView is the second ListView, so its items are not counted at 0, they've been replaced with the items from the second ViewModel. This occurs after the navigation, so it would be expected that the RelativeSource would return the ListView with the second ViewModel as its DataContext.
I have managed to override this default behaviour by using a custom DataTemplateSelector class:
public class ViewSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (container is FrameworkElement element && item is SubViewModel)
{
return element.FindResource("subviewmodel_template") as DataTemplate;
}
return null;
}
}
The DataTemplate is stored in a ResourceDictionary (merged in App.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataTemplateEventTesting.Views"
xmlns:vm="clr-namespace:DataTemplateEventTesting.ViewModels">
<DataTemplate x:Shared="False" x:Key="subviewmodel_template" DataType="{x:Type vm:SubViewModel}">
<local:SubView />
</DataTemplate>
</ResourceDictionary>
It turns out that in a ResourceDictionary, x:Shared="False" has the critical effect that I want it to have (apparently this is effective only in a ResourceDictionary) - it keeps the templates isolated per ViewModel.
The Main Window is now written as:
<Window x:Class="DataTemplateEventTesting.Views.MainWindow"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
xmlns:vm="clr-namespace:DataTemplateEventTesting.ViewModels"
xmlns:vw="clr-namespace:DataTemplateEventTesting.Views">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<vw:ViewSelector x:Key="view_selector" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions> ... </Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding SubViewModels}"
SelectedValue="{Binding MainContent, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type vm:SubViewModel}">
<TextBlock Text="{Binding DisplayText}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Grid.Column="1" Content="{Binding MainContent}"
ContentTemplateSelector="{StaticResource view_selector}" />
</Grid>
</Window>
Interestingly, I found that both of the following need to be in place in this particular example:
One
The DataTemplate is in a ResourceDictionary with x:Shared="False".
Two
The DataTemplateSelector is used.
E.g., when I satisfy the first condition, and use <ContentControl ... ContentTemplate="{StaticResource subviewmodel_template}" />, the issue prevails.
Similarly, when x:Shared="False" is not present, the DataTemplateSelector is no longer effective.
Once these two conditions are in place, the Output window shows me:
Sub1: 2 items. // selected an item
Sub1: 0 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 0 items. // navigated to Sub1
Sub1: 2 items. // selected an item
Sub1: 0 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 0 items. // navigated to Sub1
This is the expected behaviour, which I'd previously observed when switching between ViewModels of different types.
Why DataTemplateSelector?
After reading the documentation for x:Shared, I have at least a theory on why DataTemplateSelector seems to be required for this to work.
As stated in that documentation:
In WPF, the default x:Shared condition for resources is true. This condition means that any given resource request always returns the same instance.
The key word here would be request.
Without using a DataTemplateSelector, WPF has certainty on which resource it needs to use. Therefore, it only needs to fetch it once - one request.
With a DataTemplateSelector, there is no certainty, as there may be further logic within the DataTemplateSelector even for ViewModels of the same type. Therefore, DataTemplateSelector forces a request to be made with each change in Content, and with x:Shared="False", the ResourceDictionary will always return a new instance.

Select an item from checkedlistbox using wpf,mvvm

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?

How to use Pivot Effectively?

I have following data structure in my WP7 app. And I'm generating three PivotItems through databinding, contents of binding. Interesting part is when Binding happens for Pivot items contents(Items) are queried for three time and again selection changes.
Is there anything I'm doing wrong?
Code:
<controls:Pivot Title="{StaticResource ApplicationName}" ItemsSource="{Binding Folders}" SelectedItem="{Binding SelectedFolder, Mode=TwoWay}" Name="_pivot">
<controls:Pivot.ItemTemplate>
<DataTemplate>
<ListBox DataContext="{Binding Source={StaticResource Locator}}" ItemsSource="{Binding ThingsListViewModel.Items}" />
</DataTemplate>
</controls:Pivot.ItemTemplate>
I have three folders items, when Pivot control is created ThingsListViewModel.Items property executed thrice, and once every time selection changes.
I'm expecting ThingsListViewModel.Items to execute only selection chage on Pivot control.
I think you need to listen to the LoadedPivotItem and Loaded events of the pivot. The Loaded event will always load the first PivotItem (LoadedPivotItem) will not be called. The LoadedPivotItem is called when the user swipes to another PivotItem.
Based on these events you should then run your queries for the currently SelectedPage. You may also want a flag to indicate once you have loaded the data for each Pivot to avoid running the queries again.

Control which field is displayed in the textbox part of a databound WPF ComboBox

I have a ComboBox in WPF which is databound, and has data template which controls how each of the items is displayed. I have made it so that each item is displayed with two bits of text (for the Name and Path properties) and one image (for the Icon property).
At the moment when I select an item from the ComboBox the textbox bit of the ComboBox just changes to say "TestWPF.Result" which is the name of the class which I have populated the ComboBox with.
I'm interested in one (or both) of two things:
How do I change it so that it displays the value of one of the fields there (eg. so it shows the value of the Name field rather than the name of the class)?
Is it possible get it to use the same DataTemplate there as in the list of items, so that once I have selected an item it displays in the closed ComboBox the same way as it looks in the list of items. Basically I've got a DataTemplate called ShowResults and a ComboBox which uses that template. I've also added in a separate ContentControl which I've got to show the details of the selected item in the ComboBox, but I want to get that to replace the textbox in the ComboBox.
Update:
Thanks for the first answer. I've tried using a separate ContentControl, as you've described, and it works fine. The question now is how to replace the textbox part of the ComboBox with this ContentControl. Any hints on that would be most welcome.
Also, is it possible to replace the textbox bit of the ComboBox control with a mixture of the ContentControl and a textbox, so that I can still type in the textbox to help select items from the ComboBox, but then when I close the dropdown the rest ContentControl bit will be populated with the rest of the text and the icon. Hope that makes sense - ask questions if it doesn't!
Code:
I've been asked to post my code - so here it is. I've tried to remove things that I know are definitely not relevant, but I'm not sure exactly what is relevant so when in doubt I've left things in.
<Window x:Class="TestWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:TestWPF"
Title="Window1" Height="300" Width="843" Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="ShowResult" DataType="TestWPF.Result">
<StackPanel Margin="5" Orientation="Horizontal">
<Image Width="32" Height="32" Source="{Binding Path=Image}"/>
<StackPanel Margin="5">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Path}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Width="786">
<Button Height="23" HorizontalAlignment="Right" Margin="0,24,166,0" Name="btnTest" VerticalAlignment="Top" Width="75" Click="btnTest_Click">Add</Button>
<ComboBox StaysOpenOnEdit="True" DropDownClosed="comboBox1_DropDownClosed" PreviewTextInput="comboBox1_PreviewTextInput" SelectionChanged="comboBox1_SelectionChanged" ItemTemplate="{StaticResource ShowResult}" Margin="259,109,22,89" Name="comboBox1" IsEditable="True" />
<ContentControl Height="50" Margin="268,0,22,21" Name="contentControl1" VerticalAlignment="Bottom" Content="{Binding ElementName=comboBox1,Path=SelectedValue}" ContentTemplate="{StaticResource ShowResult}"/>
</Grid>
You got the binding part right - binding to the data and using a DataTemplate to display the source the way you want to.
As to your second question, a way to do it would be to use a ComboBox with IsEditable="True" as you have, and withing the TextChanged event handler check if the comboBox.Items contains the new value, if not check use Linq to seach for a match:
if (comboBox.Items.Contains(e.NewValue))
return;
var matches =
with comboBox.Items
select item
where item.BeginsWith(e.NewValue);
if (matches.Count > 0)
comboBox.SelectedItem = matches.First();
Just place the Property Binding expression to the textBox,You dont need to apply template.
Another way to get exact Data template, Place a ContentControl in the place of textBox and assign the same DataTemplate (say x:Name="robinTemplate")
<ContentControl Content="{Binding ElementName=cmbBox,Path=SelectedValue}" ContentTemplate="{StaticResource robinTemplate}"/>
For making the Selected content display in the same way :
Create a copy of the combobox control template and you will find a ContentPresenter there. Replace that with the ContentControl.. This is not the right solution though.

Categories