Binding a nested List in XAML - c#

My ViewModel looks like this:
public class MainViewModel : BaseViewModel
{
public List<Paragraph> Paragraphs { get; set; }
. . .
}
public class Paragraph
{
public List<ParagraphElement> Elements;
. . .
}
And my XAML looks like this:
<StackPanel Grid.Row="1">
<ItemsControl ItemsSource="{Binding Paragraphs}">
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource ParagraphElements}" />
</ItemsControl>
</StackPanel>
I get the following error:
"XamlParseException"
and the Additional information:
'Add value to collection of type 'System.Windows.Controls.ItemCollection'
threw an exception.'
How can I bind this nestes structure in XAML ?

you have to set ItemTemplate for outer ItemsControl. Exception is thrown because you set ItemsSource for outer ItemsControl and added an inner ItemsControl to Items collection at the same time
<StackPanel Grid.Row="1">
<ItemsControl ItemsSource="{Binding Paragraphs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Green">
<ItemsControl ItemsSource="{Binding Elements}"
ItemTemplate="{StaticResource ParagraphElements}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>

Related

Add a TextBox as the last element of an ItemsControl with WrapPanel

I am trying to implement the following scanarios
APPROACH SO FAR
Tried to implement it with an ItemsControl (with WrapPanel) and a TextBox wrapped inside a WrapPanel, but it does not have a desired output as there are two WrapPanels wrapping separately
<toolkit:WrapPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding someThing}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<TextBlock Text="somesomething" />
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBox/>
</toolkit:WrapPanel>
I am thinking if I can add the TextBox at the END of the ItemsControl, but failed to do so. Please specify if there is any other workaround/ solution to any of my approaches
You need to use DataTemplateSelector for the ItemsControl and specify different templates for different list items.
public class BlockItem
{
// TODO
}
public class BoxItem
{
// TODO
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate BlockTemplate { get; set; }
public DataTemplate BoxTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is BlockItem) return BlockTemplate;
else if (item is BoxItem) return BoxTemplate;
return base.SelectTemplateCore(item);
}
}
XAML:
<ItemsControl ItemsSource="{Binding someObject}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector>
<local:MyTemplateSelector.BlockTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BlockTemplate>
<local:MyTemplateSelector.BoxTemplate>
<DataTemplate>
<Grid>
<TextBox Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BoxTemplate>
</local:MyTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
And you then add different types of objects to your items source:
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BoxItem());
If you want the TextBox to be the last element, then you need it to be the last item in your ItemsSource list.

Binding in ListBox using MVVM

I have a problem binding all array entries to my ListBox in XAML.
XAML:
<ListBox ItemsSource="{Binding ResultFlag}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TypeInfo}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ResultFlag property in my ViewModel (which is the DataContext of the XAML file):
private ObservableCollection<DataField> _resultFlag;
public ObservableCollection<DataField> ResultFlag
{
get { return _resultFlag; }
set
{
_resultFlag = value;
OnPropertyChanged();
}
}
TypeInfo in the DataField class:
public string[] TypeInfo { get; set; }
I would like to show all string entries from above array in the ListBox - how should I do this? I've tried several things including nested Listbox and binding the ItemsSource of the ListBox directly to the array (didn't work, BTW)
Cheers!
What you have in your scenario is a List of List. In order to show that in your list box you need to have nested ListBoxes like this.
<ListBox ItemsSource="{Binding ResultFlag}" >
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding TypeInfo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF DataTemplate Selector is not working

I am working on a Treeview control. The items control should display a set of textbox and combobox dynamically depending on the value of the data structure.
The ArgumentTypeTemplateSelector 's convert code is executed. however, no Textbox and combo is display. Please would someone kindly help. thank you.
The tree View (xaml)
<ItemsControl x:Name="argumentTexts" ItemsSource="{Binding ArgumentDetailsCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type structures:ArgumentDetails}">
<ItemsControl x:Name="items" ItemsSource="{Binding DefaultValue}"
ItemTemplateSelector="{Binding DefaultValue, Converter={StaticResource ArgTypeTemplateSelector}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public class ArgumentTypeTemplateSelector : IValueConverter
{
private DataTemplate comboboxDataTemplate;
private DataTemplate textboxDataTemplate;
public DataTemplate ComboBoxDataTemplate
{
get
{
return this.comboboxDataTemplate;
}
set
{
this.comboboxDataTemplate = value;
}
}
public DataTemplate TextBoxDataTemplate
{
get
{
return this.textboxDataTemplate;
}
set
{
this.textboxDataTemplate = value;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = (string)value;
if (str.Contains("1"))
{
return this.ComboBoxDataTemplate;
}
return this.TextBoxDataTemplate;
}
In the resourcedictionary(xaml)
<DataTemplate x:Key="TextBoxDataTemplate">
<TextBox Text="{Binding DefaultValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Width="Auto"
Margin="5,0,0,0"
Padding="0"
Style="{StaticResource GridEditStyle}"
IsEnabled="True"/>
</DataTemplate>
<DataTemplate x:Key="ComboBoxDataTemplate">
<ComboBox HorizontalAlignment="Stretch" IsEnabled="True"/>
</DataTemplate>
<columnConfiguratorControls:ArgumentTypeTemplateSelector x:Key="ArgTypeTemplateSelector" ComboBoxDataTemplate="{StaticResource ComboBoxDataTemplate}" TextBoxDataTemplate="{StaticResource TextBoxDataTemplate}"/>
</ResourceDictionary>
DataTemplateSelectors work in a different way than converters. Your DataTemplateSelector will retrieve the item of your list as argument and depending on the criteria you define, you can choose a DataTemplate that should be returned.
Try the following:
In your xaml file define your DataTemplateSelector as static resource and set it in your ItemsControl:
<ItemsControl x:Name="argumentTexts" ItemsSource="{Binding ArgumentDetailsCollection}">
<ItemsControl.Resources>
<!-- define your template selector as static resource to reference it later -->
<ns:ArgumentTypeTemplateSelector x:Key="ArgumentTypeTemplateSelector"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type structures:ArgumentDetails}">
<!-- use your template selector here -->
<ItemsControl x:Name="items" ItemsSource="{Binding DefaultValue}"
ItemTemplateSelector="{StaticResource ArgumentTypeTemplateSelector }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In your code behind file, you need to implement your data template selector for example in the following way:
// derive from base class DataTemplateSelector
public class ArgumentTypeTemplateSelector : DataTemplateSelector
{
// override SelectTemplate method
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// get access to the resources you need, for example
// by accessing the UI object where your selector is placed in
var frameworkElement = (FrameworkElement) container;
// return a data template depending on your custom logic,
// you can cast "item" here to the specific type and check your conditions
if(item has condition)
return (DataTemplate) frameworkElement.FindResource("YourDataTemplateKey");
else
// ...
return base.SelectTemplate(item, container);
}
}

WPF Binding on Nested ItemControls with Sub Collection

I have the following xaml:
<ItemsControl ItemsSource="{Binding ResearchLanguageViewModel.Filters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Values}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
With the following objects/collection(s):
public class FilterViewModel
{
public string Type { get; set; }
public ObservableCollection<string> Values { get; set; }
}
public class ResearchLanguageViewModel
{
public int FirmCount { get; set; }
public ObservableCollection<FilterViewModel> Filters { get; set; }
}
I'm trying to bind on the Filters property and the Type property comes out fine. However, I am having trouble getting the Values collection of strings to show as a group of checkboxes. Not sure what I'm doing wrong here...
Values in your View Model is an array, and you are trying (I'm assuming) to create an array of Checkboxes with the values of each checkbox being the index value into that array.
In your model, you are passing in the array of values into the content of one single checkbox control. What you need to do is bind the array to the parent's ItemsSource and bind the new value to the Checkbox control's content. Something like this:
<ItemsControl ItemsSource="{Binding ResearchLanguageViewModel.Filters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl>
<ItemsControl.ItemTemplate ItemsSource={Binding Path=Values}>
<DataTemplate>
<CheckBox Content="{Binding Mode=TwoWay}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Bind your item controls, ItemSource property to values.
For a string, you won't even need a template. In your model, you have a collection of strings, so binding to a checkbox template does provide some UI enhancement but my betting is you want the checked status represented in the view model too.
You might want to consider creating an object to encapsulate your label and Checked status and defining your collection based on that.
public class CheckModel
{
public string Label {get; set;}
public bool Checked { get; set; }
}
Then bind your checkbox to:
<CheckBox IsChecked="{Binding Checked}" Content="{Binding Label}" />
I hope this helps.
You will want to bind Values to the ItemsControl not the Checkbox,
<StackPanel>
<TextBlock Text="{Binding Path=Type}"></TextBlock>
<TextBox Text="Search...."></TextBox>
<ItemsControl ItemsSource="{Binding Values}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"></CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>

Grouping with strongly type group key in WPF

Every example and I article I found, is about grouping items by one property and displaying it. But what I have, is a strongly type group key, which I want to display. Here is models and grouping logic:
The item interface
public interface IItem {
string Title { get; }
string ToolTip { get; }
object Icon { get; }
Type GroupType { get; }
}
IItem has many implementations like this:
public class Item : IItem {
public string Title { get; private set; }
public string ToolTip { get; private set; }
public object Icon { get; private set; }
// I have many implementation of IGroup which I will use them in GroupType properties.
public Type GroupType { get { return SomeGroupTypeHere; } }
}
And here is the group interface:
public interface IGroup {
string Name { get; }
object Icon { get; }
}
and it has many implementations too.
I collect them in my view model (by getting help from Autofac):
public class MyViewModel {
private readonly IEnumerable<IGrouping<IGroup, IItem>> _items;
public MyViewModel(IEnumerable<IGroup> groups, IEnumerable<IItem> items){
_items = items.GroupBy(t => {
var g = groups.First(u => u.GetType() == t.GroupType);
return g;
});
}
public IEnumerable<IGrouping<IGroup, IItem>> Items {
get { return _items; }
}
}
Now, the problem is, how to display this grouped items, in a ItemsControl?
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<!-- here is my template that uses IItem properties, example: -->
<Button Content="{Binding Title}"
ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
The code, just only displays the first item in each group, and I have no idea what to do to show group headers (that use IGroup properties) and also show all items in each group. Any suggestion please? Any article or blog-post will be very useful. Thanks in advance.
You want to use HierarchicalDataTemplate when you want to display Grouped data, Change your ItemsControl.ItemTemplate to a `HierarchialDataTemplate'.
Sample (and Untested) HierarchialDataTemplate:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Refer to this blogpost for step-by-step approach to HierarchialDataTemplates.
UPDATE
I have tried the above HierarchialDataTemplate with your code and it doesnt seem to work. However, if I replace ItemsControl with TreeView, it does work as expected.
So, I guess, you might want to have nested ItemControl for your case, Tested sample code as below:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
<ItemsControl ItemsSource="{Binding}" Margin="15,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the screen shot with this nested ItemsControls.
UPDATE 2
I think you need to update the template to display Group, the current template doesnt do that.
Updated Template is below:
<ItemsControl ItemsSource="{Binding Items}" Margin="20 20 20 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Key.Name}" />
<ItemsControl ItemsSource="{Binding}" Margin="15,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Content="{Binding Title}" ToolTip="{Binding ToolTip}"/>
</DataTemplate >
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the resulting Screenshot:

Categories