Binding a CollectionViewSource to a ListBox - c#

For some reason I cannot get my ListBox to display data from my CollectionViewSource. Here is the code...
public class AppTest
{
public int Priority { get; set; }
public string TestName { get; set; }
}
public class AppTestProvider
{
public List<AppTest> GetAppTests()
{
return new List<AppTest>()
{
new AppTest() { Priority=1, TestName = "Application Setup" },
new AppTest() { Priority=2, TestName = "File System Permissions" }
};
}
}
... and now the Xaml...
<Window.Resources>
<ObjectDataProvider x:Key="AppTests" ObjectType="{x:Type Application:AppTestProvider}" MethodName="GetAppTests" />
<CollectionViewSource x:Key="cvs" Source="{Binding AppTests}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Priority" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListBox x:Name="TestList" ItemsSource="{Binding Source={StaticResource cvs}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TestName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
If I change the ItemsSource of the ListBox to look like this (getting data from the ObjectDataSource and not the CVS) it displays the data albeit not sorted...
<ListBox x:Name="TestList" ItemsSource="{Binding Source={StaticResource AppTests}}">
I'm sure this must be something pretty simple. I just cannot seem to get it to work!

Replace this <CollectionViewSource x:Key="cvs" Source="{Binding AppTests}">
with <CollectionViewSource x:Key="cvs" Source="{StaticResource AppTests}">.
You are referring to resource defined in XAML so you need to use StaticResource instead of Binding to refer to ObjectDataProvider just like you are doing in later approach to set ItemsSource of your listBox.

Related

How to fill each tab of the tab control's itemslist with one user control dynamically from the mainviewmodel

My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.

How to pass a CollectionViewSource to a converter?

I have something like this:
<UserControl.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="filteredSymbols" Source="{Binding Symbols ,RelativeSource={RelativeSource AncestorType=UserControl}}"
Filter="Symbols_CollectionViewSource_Filter" IsLiveFilteringRequested="True" >
<CollectionViewSource.LiveFilteringProperties>
<sys:String>DisplayPage</sys:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
</ResourceDictionary>
</UserControl.Resources>
...
<Border Background="{Binding Source={StaticResource filteredSymbols}, Converter={StaticResource MultiThresholdToReturnValueConverter}}" >
I am trying to pass the ListCollectionView to the converter in the border background property, but it will keep passing null.
I have also tried adding Path=. to the border background binding which made no difference.
Am I missing something?
Edit:
I've just tried an identical binding on a ListView's ItemsSource and it will pass the ListCollectionView object to the converter fine, using this code:
<ListView ItemsSource="{Binding Source={StaticResource filteredSymbols}, , Converter={StaticResource MultiThresholdToReturnValueConverter}}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="item" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Whats going on?
I have found a workaround to this problem by setting the resource to a DependencyProperty in the code behind and binding to that instead like so:
Code behind:
...
public static readonly DependencyProperty FilteredSymbolsProperty = DependencyProperty.Register(nameof(FilteredSymbols), typeof(CollectionViewSource), typeof(SymbolSummaryControl));
public CollectionViewSource FilteredSymbols
{
set { SetValue(FilteredSymbolsProperty, value); }
get { return (CollectionViewSource)GetValue(FilteredSymbolsProperty); }
}
public ctor()
{
InitializeComponent();
FilteredSymbols = (CollectionViewSource)this.Resources["filteredSymbols"];
Debug.Assert(FilteredSymbols != null);
}
...
And then using the binding:
Background="{Binding Path=FilteredSymbols.View, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource MultiThresholdToReturnValueConverter}}"

How to bind enum into combobox inside data template

I have an enum that I need to bind into a ComboBox. The ComboBox is located inside the data template tag. How can I bind the enum into the ComboBox?
This is the enum:
public enum Status
{
Enable,
Disable
}
This is the xaml:
<Window.Resources>
<cv:StatusToBooleanConverter x:Key="statusToBooleanConverter"/>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="enum:Status"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<DataTemplate>
<StackPanel>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}},
Path=DataContext.Statusstring}" x:Name="cbProductionLineStatus"
FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
Here is the viewmodel code:
public List<Status> status;
public List<Status> Statusstring
{
get
{
foreach (List<Status> iColor in System.Enum.GetValues(typeof(Status)))
{
status = iColor;
}
return status;
}
}
I tried implementing the Find ancestor method half way and got stucked.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}},
Path=?}" SelectedValue="{Binding ProductionLineStatus,Mode=TwoWay}" SelectedValuePath="ProductionLineStatus" DisplayMemberPath="ProductionLineStatus" x:Name="cbProductionLineStatus" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Center" />
I am trying to populate the enum status into my combobox but it is failing. However, now I am trying to implement as the solution stated by Steven but still its not working.
I able to solve this problem by implementing view model like this:
public Status status = Status.Enable;
public List<string> Statusstring {
get
{
return System.Enum.GetNames(typeof(Status)).ToList();
}
}
This is my xaml:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}},
Path=DataContext.Statusstring}" SelectedValue="{Binding ProductionLineStatus, Converter={StaticResource statusToBooleanConverter}, Mode=TwoWay}" x:Name="cbProductionLineStatus" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
I think I would have solved it by writing a property, and using that property in the Combobox:
Something similair like this:
public Status status = Status.Enable;
public string Statusstring
{
get
{
if (status == Status.Enable)
return "Enable";
else
return "Disable";
}
}
I think the best solution would be to implement the view model as answered by anonymous_apple. Then in the combo box you could further have a data template that contains a TextBlock. And in the Textblock you'd set the Text as so: Text="{Binding }".
^^This solved my issue, it should solve yours as well..

How to add a DataTemplate to CollectionContainer?

Here is my specific scenario.
The Window resources code:
...
<Window.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="AdditionalStringData" Source="{Binding ViewModelObservableCollection_String}"/>
<CollectionViewSource x:Key="AdditionalCustomObjectData" Source="{Binding ViewModelObservableCollection_CustomObject}"/>
<ResourceDictionary.MergedDictionaries>
...
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
...
The part where I need to display the Collection:
...
<StackPanel>
<ItemsControl>
<ItemsControl.ItemsSource>
<CompositeCollection>
<TextBlock Text="{Binding ViewModelTextProperty}"/>
<Button Command="{Binding ViewModelRelayCommand}">Command</Button>
<CollectionContainer Collection="{Binding Source={StaticResource AdditionalStringData}}" />
<CollectionContainer Collection="{Binding Source={StaticResource AdditionalCustomObjectData}}" />
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</StackPanel>
...
The ViewModel (assume that it is binded correctly)
...
private string ViewModelTextProperty { get; set; } = "Sample Text";
public RelayCommand ViewModelRelayCommand { ... }
private ObservableCollection<string> ViewModelObservableCollection_String { get; set; } = new ObservableCollection<string>();
private ObservableCollection<CustomObject> ViewModelObservableCollection_CustomObject { get; set; } = new ObservableCollection<CustomObject>();
...
The Class CutomObject (it may not be needed to show):
...
public class CustomObject
{
public string firstString;
public string secondString;
public CustomObject()
{
...
}
...
}
...
Assume that the ObservableCollections has proper contents.
My question is: How can I display the collection properly?
Here is the criteria:
On the first line, there will be a TextBlock with a Text inside it that says "Sample Text"
Next is a button with a label "Command"
Next lines (as many as ViewModelObservableCollection_String items) are TextBlocks. Its text should be the value of the individual item of ViewModelObservableCollection_String.
Next lines (as many as ViewModelObservableCollection_CustomObject items) are TextBoxes. Its text should be the value of the individual item of ViewModelObservableCollection_CustomObject (concatenation of firstString and secondString).
As you can see, the content of the StackPanel is a merge of more than one Collection with different DataTemplate.
Please ask for clarification if something is not clear enough.
Use DataTrigger inside ItemTemplate to change ControlTemplate of Control used, while comparing Type. For this use a converter which would return the type.
or,
Use ContentControl as ItemTemplate.
Define DataTemplate specifying DataType in it. ContentControl will automatically pick appropriate DataTemplate for its ContentTemplate.
Second Approach (Recommended)
<Window.Resources>
<ResourceDictionary>
...
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Background="ForestGreen" Text="{Binding .}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomObject}">
<StackPanel Orientation="Horizontal">
<TextBlock Background="Red" Text="{Binding firstString}"/>
<TextBlock Background="Red" Text="{Binding secondString}"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<ItemsControl>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding .}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
...
</ItemsControl>

ItemSource in Tree View MVVM Style... I can't get it to bind properly

I can't seem to use the ItemSource Tag for my treeview. I don't unerstand the problem..
I am trying a simple tree view before i actually bind to my database.. I a looking for an MVVM Style solution
Here is My view Model
public class TreeViewVM : ViewModelBase
{
public class Topic
{
public string Title { get; set; }
public int Rating { get; set; }
private ObservableCollection<Topic> childTopicsValue = new ObservableCollection<Topic>();
public ObservableCollection<Topic> ChildTopics {
get
{
return childTopicsValue;
}
set
{
childTopicsValue = value;
}
}
public Topic() {}
public Topic(string title, int rating)
{
Title = title;
Rating = rating;
}
}
static public ObservableCollection<Topic> Users = new ObservableCollection<Topic>();
public TreeViewVM()
{
Users.Add(new Topic("Using Controls and Dialog Boxes", -1));
Users.Add(new Topic("Getting Started with Controls", 1));
Topic DataGridTopic = new Topic("DataGrid", 4);
DataGridTopic.ChildTopics.Add(
new Topic("Default Keyboard and Mouse Behavior in the DataGrid Control", -1));
DataGridTopic.ChildTopics.Add(
new Topic("How to: Add a DataGrid Control to a Page", -1));
DataGridTopic.ChildTopics.Add(
new Topic("How to: Display and Configure Row Details in the DataGrid Control", 1));
Users.Add(DataGridTopic);
Topics = Users;
}
private ObservableCollection<Topic> _Topics { get; set; }
public ObservableCollection<Topic> Topics
{
get
{
return _Topics;
}
set
{
if (_Topics != value)
{
_Topics = value;
OnNotifyPropertyChanged("Topics");
}
}
}
}
}
Here is my Xaml
xmlns:converter="clr-namespace:TestTree"
xmlns:viewModel="clr-namespace:TestTree.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<UserControl.Resources>
<viewModel:TreeViewVM x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource Topics}}">
<StackPanel x:Name="LayoutRoot2" Background="White">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=Title}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=ChildTopics}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Title}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView Width="400" Height="300" DataContext="{Binding Path=Topics}" ItemTemplate="{StaticResource NameTemplate}" x:Name="myTreeView" />
</StackPanel>
First of all you set DataContext of the "LayourRoot" grid to a resource with key "Topics" which does not exist. That probably should be the "ViewModel" resource. Second of all, why can't you use the ItemsSource property on the TreeView? Setting DataContext property on the TreeView alone will not work. Here is the correct XAML:
<UserControl x:Class="MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<UserControl.Resources>
<viewModel:TreeViewVM x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource ViewModel}}">
<StackPanel x:Name="LayoutRoot2" Background="White">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=Title}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=ChildTopics}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Title}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView Width="400" Height="300" ItemsSource="{Binding Path=Topics}" ItemTemplate="{StaticResource NameTemplate}" x:Name="myTreeView" />
</StackPanel>
</Grid>
</UserControl>

Categories