What Does this MSDN Sample Code Do? - ItemsControl.ItemTemplate - wpf

This is a XAML code sample taken from the MSDN library article for the ItemsControl.ItemTemplate property:
<ListBox Width="400" Margin="10" ItemsSource="{Binding Source={StaticResource myTodoList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm looking for an explanation of the usage of the <StackPanel> element is this example.
->
Where is this panel going to exist in the ListBox?
What is its purpose in the ItemTemplate?
Can any System.Windows.Controls.Panel be used in its place, specifically a Grid?
How would I go about using a <Grid> element as the template for each item in the ListBox?
Here is the concept I am going for:
http://img151.imageshack.us/img151/7960/graphconcept.png
I have drawn the graph using a <Path> element, and there are no problems there.
I am working on the labels for the axies, and I am experimenting with the use of a <Grid> element in the ItemTemplate - but I have no idea how the grid is supposed to function in this context, and MSDN says nothing about the panel in their sample code.
My XAML for the Y-axis labels currently looks like this:
<ListBox Background="Transparent" BorderThickness="0" ItemsSource="{Binding Path=GraphLabelYData}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=GraphLabelSpacing}" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{Binding ElementName=GraphLabelYData, Path=GraphLabelMarkerLength}" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Bottom" Text="{Binding Path=GraphLabelTag}" />
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Stroke="Black" Fill="Black" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Does this look correct? Nothing shows up at run-time, but I want to make sure the XAML is modeled correctly before I start debugging the data-bindings and the code-behind.

"Where is this panel going to exist in the ListBox?" - The listbox will make one copy of it for each list item, i.e. one for each element in the myTodoList collection. So within each list item, you'll have the three labels stacked one above the other.
"What is its purpose in the ItemTemplate?" - To make it possible to show more than one control for each element in the ItemsSource. ItemTemplate, like many things in WPF, can only take one child element, so if you want multiple children, you need to specify how you want them laid out, and you do that by adding a panel (StackPanel in this case).
"Can any System.Windows.Controls.Panel be used in its place, specifically a Grid?" - You bet.
"How would I go about using a <Grid> element as the template for each item in the ListBox?" - The same way you would use a Grid anywhere else. It's no different; it's just that ItemsControl (and its descendant, ListBox) will create multiple instances of your Grid. Note, though, that inside the ItemTemplate, your DataContext will be the current list item, and therefore your {Binding}s will be relative to that list item (unless you specify otherwise with e.g. ElementName).
"Does this look correct?" - This really should be posted as a separate question, as it's unrelated to the questions about the MSDN sample, and I'm not even sure what you're trying to do. But I'll try to answer: I suspect something is wrong, because you're using the name "GraphLabelYData" two different ways. In the ColumnDefinition, as far as I can tell, you're treating GraphLabelYData as the name of a XAML element (i.e. you're looking for another control in the window/page/UserControl with Name="GraphLabelYData" or x:Name="GraphLabelYData", and reading that control's GraphLabelMarkerLength property); but in the TextBlock, you're treating GraphLabelYData as the name of a property on the current collection item. I suspect one of those isn't right.

Related

Binding the content of a ListBoxItem to something not related to

I'm new to WPF, but am pretty familiar with binding list box controls to observable collections in the view model.
In my current project we have a ListBox that is used for navigating to different pages in a frame box. I want to add some display information to the first ListBoxItem to show which object (in this case, the Scenario) is being worked on (it is selected in a previous frame that visible in the subsequent frames). The ListBox itself is using a static list defined in the xaml, so it isn't bound to anything in the ViewModel. The CurrentScenario is a property on the ViewModel. I was able to add a Label to the same window that contains this ListBox and successfully bind CurrentScenario.Id to its content, and it updated correctly, so I know that the path in the Binding statement should resolve correctly.
<ListBox
Style="{StaticResource FunctionBackground}"
IsSynchronizedWithCurrentItem="True"
>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}">
<ListBoxItem.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=CurrentScenario.Id}"/>
</Grid>
</DataTemplate>
</ListBoxItem.ContentTemplate>
</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Parameter</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Run</ListBoxItem>
<ListBoxItem Style="{StaticResource FunctionListBoxItemStyle}" >Results</ListBoxItem>
</ListBox>
When I try to add this extra information to the listbox item, from what I can tell, the list box item has an empty text block below the text block with the word "Scenario." I can't figure out why the empty text box content is not showing the value of the bound property. When I put a normal string in the Text property of the second text block, it shows up correctly.
I imagine that either ListBoxItem content is only set up be bound to properties related to the ItemSource, and so it ignores attempts to bind to other things, or maybe there is something fundamental in WPF that I am missing. Or both...
Thanks if anyone has any ideas!
So if property CurrentScenario is in ViewModel you can use RelativeSource to binding to this property.
...
<TextBlock Grid.Row="0" Text="Scenario" />
<TextBlock Grid.Row="1" Text="{Binding Path=DataContext.CurrentScenario.Id, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
...

Setting width in a data template based on the size of a parent control

I am using a telerik TreeView in WPF, and I'm using a HierarchicalDataTemplate to show the nodes. Nodes represent matched items - which can be left only, right only, equal or inequal (a tree based comparison).
I am currently using a DataTemplateSelector to select from one of four templates, which all look similar to the following:
<HierarchicalDataTemplate x:Key="EqualTreeItemTemplate" ItemsSource="{Binding}">
<Grid Name="rowGrid" HorizontalAlignment="Stretch" Height="Auto" d:DataContext="{d:DesignInstance Carbon:ICarbonComparisonPair }">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Data="F1M574.042,314.611L533.8,344.398 522.251,328.798 515.235,333.988 526.786,349.593 526.782,349.596 531.978,356.603 579.235,321.622 574.042,314.611z" Stretch="Uniform" Fill="#FF000000" Width="16" Height="16" VerticalAlignment="Center" Margin="0,0,0,0" />
<TextBlock Grid.Column="1" Text="{Binding ObjectName}" Style="{StaticResource ObjectNameStyle}" Margin="4,0,0,0" />
<TextBlock Grid.Column="2" Text="{Binding ObjectName}" Style="{StaticResource ObjectNameStyle}" Margin="4,0,0,0" />
</Grid>
</HierarchicalDataTemplate>
The problem that I have is that the item content area is a different width based on the level of the tree that the item appears in. This means that the columns that I have don't line up - the text in the right hand column shifts to the right a bit for each level of the tree that you expand.
What I would like to do is specify the right hand grid column's width to be 50% of the size of the tree control as a whole, rather than 50% of the grid's natural area. I thought maybe I could do this with a binding with a RelativeSource, but I just can't seem to make it work. Is there a way to achieve this in XAML, or do I need to resort to code-behind?
If I'm understanding it correctly you want column index 2 to align across all items?
Check out the TreeListView control and see if that gives you what you need.
Silverlight demo here (just so you can see what it looks like - the WPF version is pretty much the same)
[Edit - More info]
The SharedGroupName property on ColumnDefinition is tempting but, thanks to the indent, it won't quite work - you'll end up with all of the content in column 1 or 2 being sized the same, but the pesky indent still throws it off. Check out ListView's View Property. I'm believe it's at least in the same spirit as what Telerik TreeListView is, if not darned similar in implementation.
Here's a decent writeup on how to use it. (Ironically I have that page bookmarked in a folder called "TreeGridList" so apparently at some point I had the idea to do that to accomplish something similar :) )

How to change HirerachicalDataTemplate associated with the WPF TreeView programatically at run time?

I have a TreeView control on my WPF window. I am giving only relevant XAML from my window.
<Window.Resources>
<HierarchicalDataTemplate x:Key="HierarchicalTemplate" ItemsSource="{Binding SubOrgUnitItems}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding ImagePath}" Stretch="Fill" Width="16"/>
<TextBlock Text="{Binding OrgUnitName}" Name="treeText" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView Margin="10,35,10,10" BorderThickness="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto"
IsTabStop="True" Name="orgTreeView" ItemsSource="{Binding}" ItemTemplate="{DynamicResource HierarchicalTemplate}" TabIndex="700" SelectedItemChanged="orgTreeView_SelectedItemChanged" />
When the Collection of Organisations is bound to DataContext of the TreeView, items are displayed with the OrgUnitName's value as a text at every node.
Now at run time I want to see some other property's value as a text at every node. e.g. OrgUnitCode instead of OrgUnitName. Both are properties declared in the view model class associated with the treeview.
How can i do it programatically at run time?
You should use HierarchicalDataTemplateSelector.
Define two different HierarchicalDataTemplate (as you did).
Inherite your custom selector class from DataTemplateSelector, override its SelectTemplate method and put there the logic of the selection. This method will return the correct template in each case.
Create a Resource(Custom Selector class) in the xaml file.
Set the TreeViews ItemTemplateSelector to the static selector resource.
See a simple example here: Link
I have achieved what I wanted to do, but unfortunately by some work around. Following thing worked for me but it may not be the correct answer to the problem.
I added one more HirerachicalDataTemplate and TreeView. The new template uses the OrgUnitCode property. The new tree view uses the new template.
<HierarchicalDataTemplate x:Key="HierarchicalTemplateUsingCode" ItemsSource="{Binding SubOrgUnitItems}">
<StackPanel Orientation="Horizontal">
<Image Height="16" Source="{Binding ImagePath}" Stretch="Fill" Width="16"/>
<TextBlock Text="{Binding OrgUnitCode}" Name="treeText" />
</StackPanel>
</HierarchicalDataTemplate>
<TreeView Margin="10,35,10,10" BorderThickness="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto"
IsTabStop="True" Name="orgTreeViewCode" ItemsSource="{Binding}" ItemTemplate="{DynamicResource HierarchicalTemplateUsingCode}" TabIndex="700" SelectedItemChanged="orgTreeViewCode_SelectedItemChanged" Visibility="Hidden"/>
At run time, when I want to see OrgUnitCode property value as a text at the node, I simply make new tree visible and hide the first one (mentioned in question). So making tree views visible/invisible help me to achieve what I wanted to do.

Spanning a Record Over Multiple Rows in WPF Toolkit's DataGrid

Is it possible to style WPF Toolkit's DataGrid so a data record can span multiple rows. Example screen shot from a commercial control.
Thanks,
Ben
It is not possible with the toolkit DataGrid or GridView for a ListView, no.
However you may have luck with your own implementation, as I recently discovered you can use GridViewHeaderRowPresenter (MSDN reference), set the Columns property to the columns you want: that will give you a header row.
Then you can use GridViewRowPresenter (MSDN reference), attach it to the same Columns collection and voila, your columns in the rows and header will be linked (resize the header, the columns change).
See here for a good example:
http://msdn.microsoft.com/en-us/library/ms752313.aspx
In order to get the stacked effect, you could create a ListView or ListBox, and for each item you'd output a vertically-stacked pair of GridViewRowPresenter controls, each bound to a separate columns collection. Then in your own custom header (just above the control) you'd do the same thing with a pair of GridViewHeaderRowPresenter controls.
You could then add any other bits you'd like as well, for example the text/label that they have in your example screenshot.
No reason why this shouldn't work. It's not a pre-built solution but is possible with clean coding, it's not a hack, and you have complete control over how it looks and works! Adding sorting and so on is fairly easy too, MSDN has an example for that too.
Hope that helps - any more questions on the details of this please add a comment here!
It looks as though the control in that screenshot is creating the illusion of row-span by dividing the cells in every column to the right of the picture into multiple rows. Perhaps you could achieve the row-span effect you are looking for in the same way.
<tk:DataGrid AutoGenerateColumns="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="ID" Binding="{Binding ID}" />
<tk:DataGridTemplateColumn Header="Photo">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Photo}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0">FirstName</TextBlock>
<TextBlock Grid.Row="1">LastName</TextBlock>
</Grid>
</tk:DataGridTemplateColumn.Header>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding FirstName}" />
<TextBlock Grid.Row="1" Text="{Binding LastName}" />
</Grid>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>

WPF - Hovering over one Element shows content in another Element

I would like to achieve an affect whereby I can hover over a Button and have a TextBlock update its content (via binding). To complicate matters, The Button is one of many buttons defined in an ItemsControl/DataTemplate. The TextBlock is outside the scope of the ItemsControl.
Some simplified markup of the problem is as follows:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Title}"
Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock x:Name="TitleTextBox" Grid.Row="1" />
</Grid>
Say in this example, I may want to bind the "Title" property of the data item to the TextBlock's "Text" property.
I assume I want to be tapping into the IsMouseOver of the button, but I can't seem to get it hooked up properly.
I don't believe you're going to be able to accomplish this without some code... however, you don't need to use codebehind. A "behavior" would work just fine for this (either an old school attached behavior, or a more modern Blend Behavior). Here's what the modified markup might look like using an attached behavior:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl x:Name="Buttons" Grid.Row="0" ext:Hover.TrackChildItems="true">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Title}"
Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock x:Name="TitleTextBox"
Grid.Row="1"
Text="{Binding ElementName=Buttons, Path=ext:Hover.Item.Content}" />
</Grid>
Here the attached behavor "Hover.TrackChildItems" is used to watch for the appropriate mouse events, and sets the readonly "Hover.Item" attached property to the control being hovered over. Writing the behavior should be simple enough, and is left to you.
Edit: To set up the event handlers for the items, the first thing to do is simple and obvious: iterate over the items in the Items property and add the handlers.
Mouse.AddMouseEnter(item, OnMouseEnter);
This works fine for static content, but dynamic (added and removed at runtime) content will be missed by this. So, next you must track changes to the Items property.
((INotifyCollectionChanged)Items).CollectionChanged += OnItemsChanged;
Add or remove the mouse event handlers as appropriate when items are added and removed in the CollectionChanged handler.
There's also another solution. Create a new HoverTrackingItemsControl for this, derived from ItemsControl. In this control override the GetContainerForItemOverride method to return a new HoverTrackingItem, which handles MouseEnter/MouseLeave to notify the parent HoverTrackingItemsControl. This solution is probably easier to implement, though it does require a specialized control, while the Behavior solution is more generic and can be used with any ItemsControl type (ItemsControl, ListBox, ComboBox, etc.).

Resources