Reputation: 1233
I have a couple of questions about hierarchical template formatting for a TreeView. This image will illustrate:
Here is the XAML code for the third level:
<HierarchicalDataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Height="42" Width="auto" >
<Grid Height="41" HorizontalAlignment="Left" Margin="0,0,0,0" Name="grid1" VerticalAlignment="Top" Width="auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Image Source= "{Binding XPath=UnitIcon}" Grid.Column="1" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="None" OpacityMask="White"></Image>
<Label Content="{Binding XPath=UnitName}" Height="54" HorizontalAlignment="Left" Name="label4" VerticalAlignment="Top" FontFamily="Smythe" FontSize="18" Margin="0,0,0,0" Grid.RowSpan="3" Grid.Column="2" Grid.ColumnSpan="3"/>
<Label Content="Strength:" FontFamily="Amaltea WF" FontSize="12" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="2" Grid.Row="2"/>
<TextBlock Text="{Binding XPath=UnitStrength, ConverterParameter=N0}" Margin="0,0,0,0" FontFamily="BauderieScriptSSK Bold" FontSize="18" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Column="3" Grid.Row="2"/>
</Grid>
<Line X1='0'
X2='200'
Y1='0'
Y2='0'
Stroke="Gray"
StrokeThickness='1' />
</Grid>
</HierarchicalDataTemplate>
Upvotes: 1
Views: 53
Reputation: 37066
Take the fixed height off the UnitName label. You've got grid cells, you don't want fixed heights. Part of that gap may be the line height from your font. Temporarily set Background="LightSkyBlue"
on the label to see how much space the label itself is actually taking up.
Looks like VerticalAlignment="Center"
on the image isn't having the desired effect because you've put conflicting fixed heights on everything. Your grid1
is fixed at 41 units high, but the unit name within it is 54 units high. The layout engine is doing its best to comply with the contradictory orders you're giving it.
Delete every fixed height in your XAML. Every one, no exceptions. Let things size themselves. If you absolutely must impose a fixed height on a control, consider putting its contents in a ViewBox
, so the contents can dynamically size themselves without overflowing the container. Or not; that can look weird. But first get your relative layout working, and then start working on cramming it down into whatever limited space you've got for it.
When you're having trouble with XAML layout, the naive impulse is to add stuff. And worst of all, to add random stuff -- "I don't know what this property means or what its value means, but maybe if I add it on this control, it'll fix what's wrong with the other one!" -- at best, the stuff you add will be harmless.
Don't do that. Remove stuff instead, then build back up. Add one thing at a time and see what it does. And add nothing without first reading the documentation on it. Adding six random properties from Intellisense seems to take less time than looking up one property on MSDN, but that turns out not to be the case in practice, because the first approach is always guaranteed to be a total waste of time. It's like driving by closing your eyes and trying to steer by the feel of the obstacles you crash into.
You're assigning the right format string to the wrong property. Try this:
<TextBlock Text="{Binding XPath=UnitStrength, StringFormat=N0}"
Except whoops LOL ha ha that doesn't work with Binding.XPath
, so I'm talking nonsense. And neither does this:
<Label Content="{Binding XPath=UnitStrength}" ContentStringFormat="N0" />
I suspect they're failing because you're giving them a string rather than an integer, but that's just a guess.
But this works.
public class IntToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int n = 0;
if (Int32.TryParse((string)value, out n))
{
value = n.ToString((String)parameter);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML. YOUR_NAMESPACE_HERE
is the C# namespace where you defined the IntToStringConverter
class. That doesn't necessarily have to be right there; it could be on the parent tag or any containing tag in this XAML file, including the root tag (Window
or UserControl
or ResourceDictionary
). Putting it here makes the example more self-contained.
<HierarchicalDataTemplate
xmlns:local="clr-namespace:YOUR_NAMESPACE_HERE"
>
<HierarchicalDataTemplate.Resources>
<local:IntToStringConverter
x:Key="IntToString"
/>
</HierarchicalDataTemplate.Resources>
<!-- blah blah -->
<TextBlock
Text="{Binding XPath=UnitStrength, Converter={StaticResource IntToString}, ConverterParameter=N0}"
/>
<Window.Resources>
<!-- stuff -->
<HierarchicalDataTemplate
x:Key="UnitTemplate"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
<!-- stuff -->
</Window.Resources>
And for the TreeView:
<TreeView
...
ItemTemplate="{StaticResource UnitTemplate}"
...
/>
But this works too, if a template is going to be used in only one place:
<TreeView
...
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Or finally, if you want to put all your data templates in a file of their own, you want to create a resource dictionary:
<Window.Resources>
<!-- If you're doing the merged thing, you have to explicitly have the
ResourceDictionary tag here.
-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DataTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- other resources maybe -->
</ResourceDictionary>
</Window.Resources>
DataTemplate.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ScenarioEditor"
>
<local:IntToStringConverter
x:Key="IntToString"
/>
<HierarchicalDataTemplate
x:Key="UnitTemplate"
ItemsSource="{Binding XPath=Unit}"
>
<Grid Width="auto">
<!-- stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="SomeOtherTemplate"
>
<Grid Width="auto">
<!-- different stuff -->
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
So the tree we're looking at has multiple levels, with a different template for each level. There are two ways to do this, at least: If we had a tree of .NET classes with different child types, we could define "implicit templates" in a resource dictionary. They'd have a DataType
attribute rather than x:Key
, with the result that (for example) the template with DataType="{x:Type local:HQ}"
would automatically be used to display any class instance of that type.
But you've got XML so that's not going to work. What you can do instead is give each HierarchicalDataTemplate
its own ItemTemplate
. For clarity, the following example omits ItemsSource
and much else -- it only illustrates how we set up those parent/child template relationships.
<HierarchicalDataTemplate
x:Key="UnitTemplate"
>
<Grid>
<!-- Unit stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="CommanderTemplate"
ItemTemplate="{StaticResource UnitTemplate}"
>
<Grid>
<!-- Commander stuff -->
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
x:Key="HQTemplate"
ItemTemplate="{StaticResource CommanderTemplate}"
>
<Grid>
<!-- HQ stuff -->
</Grid>
</HierarchicalDataTemplate>
HQTemplate
will be the treeview's ItemTemplate
<TreeView
...
ItemTemplate="{StaticResource HQTemplate}"
Upvotes: 1