Hierarchical Data Structure WPF TreeListView DataTriggers - c#

I am trying to make a tree list view that displays different information compared to the parent.
From what I have now, the Children display the same information as the parent. I was wondering, how I would do this. The last post I had didn't really quite make sense to me, it didn't work really well. I was wondering if anyone can explain this a little more in depth for me.
This is the last post:
Hierarchical Data Structure WPF TreeListView
<r:TreeListView x:Name="TimeSheetsTreeListView" Margin="0,-18.312,0,0"
Style="{DynamicResource TreelistStyle}" Width="Auto" MinHeight="150"
Grid.Row="0" Background="#00FFFFFF"
ItemsSource="{Binding TimeSheetItems, Mode=Default}"
HorizontalContentAlignment="Center"
VerticalAlignment="Top" Height="207.446" Foreground="White"
Grid:GridViewSort.AutoSort="True" >
<r:TreeListView.Columns>
<GridViewColumn DisplayMemberBinding="{Binding ClientMatterName}" Width="200"
Grid:GridViewSort.PropertyName="ClientMatterName" >
<GridViewColumnHeader HorizontalContentAlignment="Center" Foreground="White"
Content="Client Name/Matter Name"
Grid:GridViewSort.PropertyName="ClientMatterName"
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding ClientMatterCode}" Width="200"
Grid:GridViewSort.PropertyName="ClientMatterCode" >
<GridViewColumnHeader HorizontalContentAlignment="Center" Foreground="White"
Content="Client No./Matter No." FontSize="10.667"/>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding TimeCode.Code}" Width="100"
Grid:GridViewSort.PropertyName="TimeCodeCode" >
<GridViewColumnHeader HorizontalContentAlignment="Center" Foreground="White"
Content="Time Code" FontSize="10.667"/>
</GridViewColumn>
<GridViewColumn Header="Hours" Width="100" Grid:GridViewSort.PropertyName="Hours">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Left"
Text="{Binding Duration, Converter={StaticResource BillableHoursConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="300" DisplayMemberBinding="{Binding Description}"
Grid:GridViewSort.PropertyName="Description">
<GridViewColumnHeader HorizontalContentAlignment="Center" Foreground="White"
Content="Description" FontSize="10.667"/>
</GridViewColumn>
</r:TreeListView.Columns>
<r:TreeListView.Resources>
<HierarchicalDataTemplate DataType="{x:Type data:TimeSheet}"
ItemsSource="{Binding Path= TimeRecords}"/>
</r:TreeListView.Resources>
</r:TreeListView>

if you want to display a parent-child relationship in a treeview. (only one level deep, so the children are all leaf nodes). you bind the treeview to the collection of parent elements. then you do a hierarchical data template for the parent, and a standard data template for the child
in the example you give you have a Parent of object type information with a collection of children object type detail and the collection of detail (children) on the parent is called child
so we do a template for the children, and then one for the parent
<!--Child (detail) DataTemplate-->
<DataTemplate
DataType="{x:Type local:detail}">
<TextBox Text="{Binding Path=Some child binding}"/>
</DataTemplate>
<!--Parent (information) Hierarchical Template-->
<HierarchicalDataTemplate
DataType="{x:Type local:information}"
ItemsSource="{Binding Path=Child}">
<TextBox Text="{Binding Path=Some parent binding}"/>
</HierarchicalDataTemplate>
because the parent and child are of a different object type, the tree view will grab the parent template, which has an items source and when it comes to draw the children it will use th child data template. note none of the data templates have keys, they are keyed on their object type.

your problem stems from the fact that both your parent and children are of the same type. the same template is used for both, what you need here is a data structure that is hierarchical.
if you had a parent task that had a collection of children and the children were of a different type than the parent then you would be cool. (because you could provide a different template for the children than the parent)
the other way to do it is to build a view model, with two different types, one for task overviews and one for sub tasks.
however i have found a hack solution using triggers based on collection sizes (when count is 0, i assume it is a leaf node) a better way would be to have a property on the object which states which type it is, parent or child, something you could easily do with a view model, or you could put the property on your business objects. go here and download the TreeViewTest.zip sample solution.
The above project is not an ideal solution. I create the view model every time. it gives you flexibility and control beyond what you have in straight wpf.
the project contains this code which is the tricky bit (note the string format on the bindings, they are really cool)
<HierarchicalDataTemplate
DataType="{x:Type local:Assignment}"
ItemsSource="{Binding Path=AssignmentCollection}">
<Grid>
<TextBlock
x:Name="parentTextBox">
<TextBlock.Text>
<MultiBinding
StringFormat="{}{0} - {1} - {2:F2}">
<Binding
Path="ClientName" />
<Binding
Path="Task" />
<Binding
Path="Hours" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock
x:Name="childTextBox" Visibility="Collapsed">
<TextBlock.Text>
<MultiBinding
StringFormat="{}{0:hh:mm tt} - {1:hh:mm tt} /{2}">
<Binding
Path="StartTime" />
<Binding
Path="EndTime" />
<Binding
Path="SubTask" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
<!--this is the trigger that chooses which text box to display-->
<HierarchicalDataTemplate.Triggers>
<DataTrigger
Binding="{Binding AssignmentCollection.Count}"
Value="0">
<Setter
TargetName="parentTextBox"
Property="Visibility"
Value="Collapsed" />
<Setter
TargetName="childTextBox"
Property="Visibility"
Value="Visible" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>

Related

Remove ListItem from nested ListView on Button Click

I have a nested Listbox (collection of objects in the main object list) that needs to delete the underlying items.
When the item is removed, I could reset the ItemsSource of the main list, but the main list will have tons of items and each time a item is removed from its underlying collection the main scrollbar would be also reset, making users willing to kill me in a very painful way.
My question: How can i find the container of the item which button has been clicked and how can i find the item itself so i can kill the #&!$*&#$# (cursing onomatopoeia)?
Here's the XAML exemple of my lists:
<ListView Name="mainList">
<ListView.View>
<GridView>
<GridViewColumn Header="Column 1" />
<GridViewColumn Header="Column 2" />
<GridViewColumn Header="Column 3" />
<GridViewColumn Header="Collection column">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding BindingCollectionProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Grid.Column="0" Text="{Binding Item.Property}" />
<TextBlock Grid.Column="1" Text="{Binding Item.AnotherProperty}" />
<Button Content="remove" Grid.Column="2" Click="DeleteClickEvent" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
How's my DeleteClickEvent(object sender, RoutedEventArgs e) should be like?
You could have a command and pass it your item as parameter instead of a click handler:
<Button ... Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type YOURCUSTOMCONTROL}}, Path=DataContext.YOURCOMMAND}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" />
If you want to keep your event handler you could use VisualTreeHelper.GetParent twice on the sender.
ListBoxItem item = VisualTreeHelper.GetParent(VisualTreeHelper.GetParent((DependencyObject)sender)) As ListBoxItem;
BindingCollectionProperty.Remove(item);

How to access element (Combo Box) in XAML from code behind

How do I access a control in XAML that is nested in a GridViewColumn.CellTemplate? By accessing the combo box I want to set its ItemsSource in the code behind.
Code:
<GridViewColumn Width="80">
<GridViewColumnHeader Content="UseCLUT"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=UseCLUT}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox x:Name="combTrueFalse" SelectedItem="{Binding Path=UseCLUT}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I have named the comboBox as combTrueFalse and tried to referenced it in the code behind but it could not be found.
I find a work around to the problem.
I have set the collection of the combo box to a class which contains the collection for the combox's selection.
The mainWindow class contains the data class's variable.
ItemsSource="{Binding ElementName=mainWindow, Path=data.comboxTFSmall}"
Meaning to say I have set the combox's item towards a Class which contains the collection and not from the code behind.
<GridViewColumn Width="80" >
<GridViewColumnHeader Content="UseCLUT"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=UseCLUT, Mode=TwoWay}" Style="{StaticResource GridBlockStyle}"/>
<ComboBox ItemsSource="{Binding ElementName=mainWindow, Path=data.comboxTFSmall}" SelectedValue="{Binding Path=UseCLUT}" Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Please do correct me if you find that my explanation is misleading thanks.
regards

Can't use bindings in this context?

So I have this code and I can't use the bindings!
<ListView.View>
<GridView>
<GridViewColumn Header="File name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<Image Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Left">
<Image.Source>
<MultiBinding Converter="{StaticResource fic}">
<Binding Path="FileName" />
</MultiBinding >
</Image.Source>
</Image>
<TextBlock Margin="16,0,0,0">
<Binding Path="FileName" />
</TextBlock>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Long boring exception removed
<TextBlock Margin="16,0,0,0">
<TextBlock.Text>
<Binding Path="FileName" />
</TextBlock.Text>
</TextBlock>
... Seems to work!
Things inside <TextBlock> tags are items contained inside the text block, which can be a whole lot of spans and other text fragments.
If you need to bind the text in the text block, you need to bind to the Text property, as you've done in your question. It is a DependencyProperty that supports this binding.
The items inside the text block does not support direct binding, nor does it support directly putting binding objects in there. However, you may put in another control with a DependencyProperty that is a binding though.
You cannot bind the text of a textblock through its children.

ListView + MultipleSelect + MVVM =?

If I were to say "the heck with it!", I could just give my ListView with SelectionMode="Multiple" a name, and be able to get all of the selected items very easily. But I'm trying to stick to MVVM as much as possible, and I want to somehow databind to an ObservableCollection that holds the value from the Name column for each selected item. How in the world do you do this? Single selection is simple, but the multi selection solution is not obvious to me with my current WPF / MVVM knowledge.
I read this question on SO, and while it does give me some good insight, I don't know how to add the necessary binding to a row, because I am using a ListView with a GridView as its View, not a ListBox.
Here's what my XAML basically looks like:
<ListView DockPanel.Dock="Top" ItemsSource="{Binding ClientPreview}" SelectionMode="Multiple">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Address">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Address}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
It sounds like the right thing to do is to databind each row's IsSelected property to each object stored in the ObservableCollection I'm databinding to. I just haven't figured out how to do this.
Write ItemContainerstyle on the ListView and put a Setter to do the binding to your ViewModel 'IsSelected' property
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected,Mode=OneWayToSource}"/>

Bind WPF control to SQL server through repository pattern

How can I bind a wpf control to a SQL Server property. Like say a Listbox
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn Header="Date Assigned">
<GridViewColumn Header="Date Due">
</GridView>
</ListView.View>
<!-- iterate through all the Names in the database and output under GridViewColumn Name -->
<!-- iterate through all the DateAssigned in the database and output under GridViewColumn Date Assigned -->
<!-- iterate through all the DateDue in the database and output under GridViewColumn Date Due -->
</ListView>
I am using the entity framework and the repository pattern. So I'd call all the names to a list by _repository.ToList();
Try this: listViewName.ItemsSource = _repository.ToList();
I'd also simplify the Xaml, like this:
<ListBox x:Name="listViewName">
<ListBox.Resources>
<DataTemplate>
<Grid Height="22" Width="Auto">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding DateAssigned}" />
<TextBlock Text="{Binding DateDue}" />
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
Where the text after {Binding is the name of the property of the item in the collection you return from _repository.

Categories

Resources