Cannot bind to ListBox child of ListBox - c#

I'm binding to a ListBox, and within the ListBox, another ListBox.
My model looks like
public interface ICriteriaDetail
{
string Title { get; }
int NumberOfEvents { get; }
ICriteriaDetail ChildCriteria { get; }
}
So, I'm using a recursive approach for the children (not a list).
When I bind to my ListView, I get the items in a list, one under the other.
The issue is, the child item is not showing at all!
The dummy data
public static IEnumerable<ICriteriaDetail> GetCriteriaList()
{
var list = new List<CompoundCriteria.Ui.Model.Interfaces.ICriteriaDetail>();
list.Add(GetCriteriaDetail("My Title", 5, false));
list.Add(GetCriteriaDetail("Other", 3, false));
list.Add(GetCriteriaDetail("Biggy", 8, true));
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgain", 43, null);
var child2 = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgainAgain", 13, child);
list.Add(new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("Really big", 86, new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("smaller", 15, child2)));
return list;
}
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, null);
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("child" + title, 13 + events, null);
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, child);
}
The ViewModel
public MainWindowViewModel()
{
this.Criterias = DatasourceMockup.GetCriteriaList();
}
private IEnumerable<ICriteriaDetail> _criterias;
public IEnumerable<ICriteriaDetail> Criterias
{
get { return _criterias; }
set { _criterias = value; }
}
And the ListBox in the XAML
<ListBox ItemsSource="{Binding Criterias}" >
<ListBox.Resources>
<ControlTemplate x:Key="con">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<ListBox ItemsSource="{Binding ChildCriteria} Visibility="{Binding ChildCriteria, Converter={StaticResource HideIfNull}}">">
<ListBox.Resources>
<DataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
</StackPanel>
</ControlTemplate>
<HierarchicalDataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<Control Template="{StaticResource con}" />
</HierarchicalDataTemplate>
</ListBox.Resources>
</ListBox>
After reading TreeView, HierarchicalDataTemplate and recursive Data it appears that HierarchicalDataTemplate will suffice (no need for a DataTemplate as well) but, I'm lost as to why I'm not seeing the result I expected (even if the children are not in the desired place, I'd still hope to see them)
EDIT
I have just added a converter which makes the ListBox (the one inside the ControlTemplate) hidden if the bound data is null. I have an outline (an empty ListBox) for just 1 of the 3 (which is correct) but it doesn't bind any of the content (the Title). The output window shows nothing of any use...
Please note, there is no limit of chidren. In the example above, there is a parent child relationship, but it could be parent-child-child-child etc

1) It is hard to understand your purpose , why ListBox, if you have ChildCriteria in model, not IEnumerable<ICriteriaDetail>?
2) Also you have binding errors, i changed xaml to
<ListBox ItemsSource="{Binding Criterias}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<TextBox Text="{Binding Path=DataContext.ChildCriteria.Title,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem},
AncestorLevel=1}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and got
EDIT1:
1) I changed data model to
public class CriteriaDetail : ICriteriaDetail
{
public CriteriaDetail(string title, int numberOfEvents, IEnumerable<ICriteriaDetail> childCriteria)
{
Title = title;
NumberOfEvents = numberOfEvents;
ChildCriteria = childCriteria;
}
public string Title { get; set; }
public int NumberOfEvents { get; set; }
public IEnumerable<ICriteriaDetail> ChildCriteria { get; set; }
}
2) Changed xaml to
<TreeView ItemsSource="{Binding Criterias}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildCriteria}">
<TextBlock Foreground="Red" Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
3) Changed GetCriteriaDetail to
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CriteriaDetail(title, events, null);
var child3 = new CriteriaDetail("child3" + title, 13 + events, null);
var child2 = new CriteriaDetail("child2" + title, 13 + events, new ICriteriaDetail[] { child3 });
var child1 = new CriteriaDetail("child1" + title, 13 + events, new ICriteriaDetail[] { child2 });
return new CriteriaDetail(title, events, new ICriteriaDetail[] {child1});
}
and got this. I think it looks more like you want

Related

What's the proper way to build a data template for a TreeView node that contains multiple collections as children?

I have a class that contains multiple collections of properties:
class Foo{
public ObservableCollection<Bar> Bars {get; set;}
public ObservableCollection<Baz> Bazzes {get; set;}
}
I'm trying to display this in a TreeView, where the Foo node is at the root, and then under it is a node for the Bars collection containing each of the Bar elements as subnodes, and the same for the Bazzes collection. But I can't seem to get the data template right. The closest I've managed to get is like so:
<HierarchicalDataTemplate DataType="{x:Type local:Foo}">
<TreeViewItem Header="Root">
<TreeViewItem Header="Bars" ItemsSource="{Binding Path=Bars}"/>
<TreeViewItem Header="Bazzes" ItemsSource="{Binding Path=Bazzes}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Bar}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" (" Foreground="Blue" />
<TextBlock Text="{Binding Type}" Foreground="Blue" />
<TextBlock Text=")" Foreground="Blue" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Baz}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
This displays a hierarchical tree with nodes I can open to display sub-items by clicking the little triangle, but when I try to click on any of the items, it selects the entire Foo with all of its sub-items as one big selection. I'm assuming this is because the nodes containing the collections are integrated into the template for Foo and so it's treating them as all being one big node somehow? But I don't know how to get the collections to show up as sub-nodes without doing it that way.
What's the correct way to do the type of setup I'm looking for, since this is obviously not quite right?
There are a couple of fundamental issues with your implementation. The first is that the Tree is simply a map onto the bound data structure which is expected to be a tree. First we have to make your Foo class a tree...
public class BarBazBase
{
public string Name { get; set; }
public string Type { get; set; }
}
public class Bar : BarBazBase
{
public string BarSpecial { get; set; }
}
public class Baz : BarBazBase
{
public string BazSpecial { get; set; }
}
public class Foo : ObservableCollection<ObservableCollection<BarBazBase>>
{
public ObservableCollection<BarBazBase> Bars { get; set; } = new ObservableCollection<BarBazBase>();
public ObservableCollection<BarBazBase> Bazzes { get; set; } = new ObservableCollection<BarBazBase>();
public Foo()
{
Add(Bars);
Add(Bazzes);
}
}
Next we need a different template for each type of tree node. We therefore need a data template selector
public class BasBazTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement fe = container as FrameworkElement;
if(item is Foo)
{
return fe.FindResource("TreeHeader") as DataTemplate;
}
if (item is ObservableCollection<BarBazBase> baseCollection)
{
if (baseCollection.Count > 0 && baseCollection[0] is Bar)
return fe.FindResource("BarHeader") as DataTemplate;
else if (baseCollection.Count > 0 && baseCollection[0] is Baz)
return fe.FindResource("BazHeader") as DataTemplate;
else
return null;
}
else if (item is Bar)
{
return fe.FindResource("BarItemTemplate") as DataTemplate;
}
else if (item is Baz)
{
return fe.FindResource("BazItemTemplate") as DataTemplate;
}
else
{
return null;
}
}
}
Finally we are ready to pull everything together in XAML...
<TreeView x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Beige"
ItemsSource="{Binding Root}" ItemTemplateSelector="{StaticResource BasBazTemplateSelector}">
<TreeView.Resources>
<HierarchicalDataTemplate x:Key="TreeHeader" ItemsSource="{Binding}" >
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="BazHeader" ItemsSource="{Binding}">
<Label>Baz</Label>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="BarHeader" ItemsSource="{Binding}">
<Label>Bar</Label>
</HierarchicalDataTemplate>
<DataTemplate x:Key="BarItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" (" Foreground="Blue" />
<TextBlock Text="{Binding Type}" Foreground="Blue" />
<TextBlock Text=")" Foreground="Blue" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BazItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
A few loose ends...
<Window.Resources>
<local:BasBazTemplateSelector x:Key="BasBazTemplateSelector"/>
</Window.Resources>
public Foo Root { get; set; }
Root = new Foo();
Root.Bars.Add(new Bar() { Name = "a", Type = "a0", BarSpecial = "a bar" });
Root.Bars.Add(new Bar() { Name = "b", Type = "b0", BarSpecial = "another bar" });
Root.Bazzes.Add(new Baz() { Name = "c", Type = "c0", BazSpecial = "a baz" });
Root.Bazzes.Add(new Baz() { Name = "d", Type = "d0", BazSpecial = "another baz" });
This is the result...

WPF ItemsSource binding show only last item of list

How to show only last item of the list in itemssource binding ?
below is my current code.
xaml
<ItemsControl ItemsSource="{Binding UpgradeTicketStorage}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Width="800">
<TextBlock Text="{Binding TicketTitle}" Style="{StaticResource TicketSelectionSubTitle}" TextAlignment="Left" />
<TextBlock Text="{Binding TicketDescription}" TextWrapping="Wrap" Style="{StaticResource TicketSelectionSubTitle2}" FontSize="19" TextAlignment="Left"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
from class binding i do have added 2 records which is ticketA and ticketB, how can i just display ticketB information ? instead of A and B
class
public class UpgradeTicketDescription : ViewModelBase
{
public string TicketTitle { get; set; }
public string TicketDescription { get; set; }
}
List<UpgradeTicketDescription> _UpgradeTicketStorage;
public List<UpgradeTicketDescription> UpgradeTicketStorage
{
get { return _UpgradeTicketStorage; }
set { _UpgradeTicketStorage = value; OnPropertyChanged("UpgradeTicketStorage"); }
}
UpgradeTicketStorage.Add(new UpgradeTicketDescription { TicketTitle = "TicketA", TicketDescription = "Observation DeckA (Single Ticket)"});
UpgradeTicketStorage.Add(new UpgradeTicketDescription { TicketTitle = "TicketB", TicketDescription = "Observation DeckB (Single Ticket)"});
If you want to bind to a specific item in a list you can go about it by creating a public variable you will bind to. Using what you have provided I've created an example that works for the last item in the list, all I did is create a new variable called LastItem and changed how the binding is in the project. This is just one of many ways of going about this.
xaml
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Width="800">
<TextBlock Text="{Binding LastItem.TicketTitle}" TextAlignment="Left" />
<TextBlock Text="{Binding LastItem.TicketDescription}" TextWrapping="Wrap" FontSize="19" TextAlignment="Left"/>
</StackPanel>
class
public UpgradeTicketDescription LastItem
{
get { return UpgradeTicketStorage.Last(); }
}
This provides this output:

Simple Nested TreeView Xaml structure?

I am trying to build a WPF TreeView with three layers. CountryReportTitle is a string property and ArticleCategoryTitlesList is a collection, both exposed from my ViewModel. There is no class hierarchy defined. This is the structure I'm looking for:
This is my attempted Xaml but I'm getting an exception in the Xaml at runtime:
{"Item has already been added. Key in dictionary: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)' Key being added: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'"}
Xaml:
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCategoryTitlesList}">
<TextBlock Text="{Binding CategoryTitle}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCatagoryTypesList}">
<TextBlock Text="{Binding ArticleTitle}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ReportViewModel}">
<TextBlock Text="{Binding ArticleTitle}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</TreeView>
Local: is a namespace to my ViewModel:
xmlns:local="clr-namespace:MyApp.ViewModels"
What am I doing wrong, what is the best approach for this problem?
Here's my go-to example for treeviews.
Use a HierarchicalDataTemplate for elements in the tree. Note that there are three layers, and each layer is its own type. This is for convenience, but you could define one type and use one template or any mix of types for your tree. Having different types represent different things in the tree makes using templates extremely convenient.
The data classes
public class ViewModel
{
public ObservableCollection<ItemA> ItemsA { get; set; }
public ViewModel()
{
ItemsA = new ObservableCollection<ItemA>(new[]{
new ItemA{Name = "A one"},
new ItemA{Name = "A Two"},
new ItemA{Name = "A Three"},
});
}
}
public class ItemA
{
public ObservableCollection<ItemB> ItemsB { get; set; }
public string Name { get; set; }
public ItemA()
{
ItemsB = new ObservableCollection<ItemB>(new[]{
new ItemB{Name = "B one"},
new ItemB{Name = "B Two"},
new ItemB{Name = "B Three"},
});
}
}
public class ItemB
{
public ObservableCollection<ItemC> ItemsC { get; set; }
public string Name { get; set; }
public ItemB()
{
ItemsC = new ObservableCollection<ItemC>(new[]{
new ItemC{Name = "C one"},
new ItemC{Name = "C Two"},
new ItemC{Name = "C Three"},
});
}
}
public class ItemC
{
public string Name { get; set; }
}
And the UI
<TreeView ItemsSource="{Binding ItemsA}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type t:ItemA}"
ItemsSource="{Binding ItemsB}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type t:ItemB}"
ItemsSource="{Binding ItemsC}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type t:ItemC}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
gives you a simple treeview
You need to bind the TreeView Resources inside your TreeView:
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:FirstLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:SecondLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
I think you'll need to modify your project to bind your Treeview with your ViewModel instead of a list of string
Here is a good Example

TreeViewItem expaning like a chain-event

In my WPF application, if I open one of the treeviewitems to display all the child items, it goes on a chain event and slowly starts opening all the parent items. For example if I opened the Red, it'd slowly open the blue's until they are all open;
My TreeView Code;
<TreeView x:Name="tvTagList" Margin="15, 40, 15, 50" SelectedItemChanged="tvTagList_SelectedTagChanged" ItemsSource="{Binding}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="{x:Null}" BorderBrush="{DynamicResource ExtryzeAccentBrushSecondary}" BorderThickness="2" ScrollViewer.CanContentScroll="True" Foreground="White"
VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagClass}" ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TagClassMagic}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagEntry}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding TagFileName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
My classes;
public class TagClass
{
public string TagClassMagic { get; set; }
public ITagClass RawClass { get; set; }
public List<TagEntry> TagEntries = new List<TagEntry>();
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = TagEntries }
};
}
}
}
public class TagEntry
{
public string TagFileName { get; set; }
public ITagEntry RawTag { get; set; }
}
I think this is because Children every time returns a new CompositeCollection which is propably expanded by default. Try returning TagEntries directly or return a new CompositeCollection at first time only.
Turns out we had to change;
VirtualizingStackPanel.VirtualizationMode="Recycling"
to
VirtualizingStackPanel.VirtualizationMode="Standard"

How can I data bind a list of strings to a ListBox in WPF/WP7?

I am trying to bind a list of string values to a listbox so that their values are listed line by line. Right now I use this:
<ListBox Margin="20" ItemsSource="{Binding Path=PersonNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But I don't know what I am supposed to put into the textblock, instead of Id, since they are all string values, not custom classes.
Also it complains not having to find the PersonNames when I have it inside MainPage, as MainPage.PersonNames.
I set the data context to:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I am doing it wrong?
If simply put that your ItemsSource is bound like this:
YourListBox.ItemsSource = new List<String> { "One", "Two", "Three" };
Your XAML should look like:
<ListBox Margin="20" Name="YourListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Update:
This is a solution when using a DataContext. Following code is the viewmodel you will be passing to the DataContext of the page and the setting of the DataContext:
public class MyViewModel
{
public List<String> Items
{
get { return new List<String> { "One", "Two", "Three" }; }
}
}
//This can be done in the Loaded event of the page:
DataContext = new MyViewModel();
Your XAML now looks like this:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The advantage of this approach is that you can put a lot more properties or complex objects in the MyViewModel class and extract them in the XAML. For example to pass a List of Person objects:
public class ViewModel
{
public List<Person> Items
{
get
{
return new List<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And the XAML:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You should show us the code for PersonNames, and I am not sure I understand your question, but maybe you want to bind it like this:
<TextBlock Text="{Binding Path=.}"/>
or
<TextBlock Text="{Binding}"/>
This will bind to the current element in the list (assuming PersonNames is a list of strings). Otherwise, you will see the class name in the list.
If the items source is enumerable as string-entries, use the following:
<TextBlock Text="{Binding}"></TextBlock>
You can use this syntax on any object. Generally, the ToString() -method will then called to get the value. This is in many cases very handy. But beware that no change notification will occur.
You can do this without having to explicitly define the TextBlock control as a part of your ListBox (unless you want better formatting). The trick to getting the binding to trigger is using an ObservableCollection<string> instead of List<string>
Window1.xaml
<ListView Width="250" Height="50" ItemsSource="{Binding MyListViewBinding}"/>
Window1.xaml.cs
public Window1()
{
InitializeComponent();
DataContext = this;
// Need to initialize this, otherwise you get a null exception
MyListViewBinding = new ObservableCollection<string>();
}
public ObservableCollection<string> MyListViewBinding { get; set; }
// Add an item to the list
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
// Custom control for entering a single string
SingleEntryDialog _Dlg = new SingleEntryDialog();
// OutputBox is a string property of the custom control
if ((bool)_Dlg.ShowDialog())
MyListViewBinding.Add(_Dlg.OutputBox.Trim());
}

Categories

Resources