Hierarchical templating multiple object types in silverlight
Is it possible and if so what is the best way to implement the following hierarchical structure in a silverlight (4) TreeView control? (where Item and Group are classes which can exist in the tree).
Group
|
|-Item
|
|-Group
| |
| |-Item
| |
| |-Item
|
|-Item
The structure could of course be arbitrarily more complex than this, if needed.
HierarchicalDataTemplates appear to be the way to approach this, but I'm specifically having trouble understanding how I might apply the template to interpre开发者_运维知识库t the different classes correctly.
A similar question was asked for WPF, the answer for which made use of the TargetType property on the HierarchicalDataTemplate, but I am uncertain whether that property is available in the silverlight version since I don't appear to have access to it in my environment.
For Silverlight, you might need to create a converter to help do this. For instance, a "target type data template selector" or similar.
Though you could get more advanced, here is an example from the Silverlight unit test framework that allows you to provide instances of data templates for hard-coded types. A generalized version could probably be made in about 20 minutes.
To use this, you'd provide instances of data templates - in your case though, you'd probably need to make them hierarchical.
<local:DataTemplateSelector
x:Key="DetailsViewDataTemplate"
DefaultDataTemplate="{StaticResource DefaultDataTemplate}"
TestMethodTemplate="{StaticResource TestMethodDataTemplate}"
TestClassTemplate="{StaticResource TestClassDataTemplate}"/>
And here is the implementation:
/// <summary>
/// A specialized data template selector.
/// </summary>
public sealed class DataTemplateSelector : IValueConverter
{
/// <summary>
/// Gets or sets the default data template.
/// </summary>
public DataTemplate DefaultDataTemplate { get; set; }
/// <summary>
/// Gets or sets the test method template.
/// </summary>
public DataTemplate TestMethodTemplate { get; set; }
/// <summary>
/// Gets or sets the test class template.
/// </summary>
public DataTemplate TestClassTemplate { get; set; }
/// <summary>
/// Initializes a new instance of the DataTemplateSelector type.
/// </summary>
public DataTemplateSelector()
{
}
/// <summary>
/// Convert a value to a data template.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target parameter.</param>
/// <param name="parameter">ConverterParameter value.</param>
/// <param name="culture">The culture parameter.</param>
/// <returns>Returns the object.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
Type type = value.GetType();
if (typeof(TestMethodData).TypeHandle == type.TypeHandle)
{
return TestMethodTemplate;
}
else if (typeof(TestClassData).TypeHandle == type.TypeHandle)
{
return TestClassTemplate;
}
}
return DefaultDataTemplate;
}
/// <summary>
/// No 2-way databinding support.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target parameter.</param>
/// <param name="parameter">ConverterParameter value.</param>
/// <param name="culture">The culture parameter.</param>
/// <returns>Returns the object.</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
My implementation
<UserControl.Resources>
<DataTemplate x:Key="DefaultDataTemplate">
<TextBlock Text="Default"/>
</DataTemplate>
<DataTemplate x:Key="ConditionDataTemplate">
<Grid>
<TextBlock Text="{Binding ConditionType}" Margin="5" Width="100"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ButtonDataTemplate">
<Grid>
<Button Content="{Binding Name}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="FieldDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" ItemsSource="{Binding Fields}" SelectedItem="{Binding Name,Mode=TwoWay}" Margin="5,0,5,0"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding ConditionTypes}" SelectedItem="{Binding ConditionType,Mode=TwoWay}" Margin="5,0,5,0"/>
<TextBox Grid.Column="2" Text="{Binding Value, Mode=TwoWay}" Margin="5" Width="100"/>
</Grid>
</DataTemplate>
<local:DataTemplateSelector
x:Key="DetailsViewDataTemplate"
DefaultDataTemplate="{StaticResource DefaultDataTemplate}"
ConditionDataTemplate="{StaticResource ConditionDataTemplate}"
FieldDataTemplate="{StaticResource FieldDataTemplate}"
ButtonDataTemplate="{StaticResource ButtonDataTemplate}"/>
</UserControl.Resources>
<Grid>
<sdk:TreeView ItemsSource="{Binding Tree}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding ChildNodes}">
<ContentControl ContentTemplate="{Binding Converter={StaticResource DetailsViewDataTemplate}}" Content="{Binding}"/>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
</Grid>
MainClass
public class MainPageModel : BaseModel
{
private ObservableCollection<object> _Tree;
public ObservableCollection<object> Tree
{
get
{
return _Tree;
}
set
{
_Tree = value;
Notify("Tree");
}
}
public MainPageModel()
{
Tree = new ObservableCollection<object>();
Tree.Add(new Condition()
{
ConditionType = "OR",
ChildNodes = new ObservableCollection<object>()
{
new Field()
{
Name = "Поле 2",
ConditionType = "=",
Value = "3"
},
new Field()
{
Name = "Поле 3",
ConditionType = ">",
Value = "3"
},
new Field()
{
Name = "Поле 4",
ConditionType = "<",
Value = "3"
},
new Condition()
{
ConditionType = "AND" ,
ChildNodes = new ObservableCollection<object>()
{
new Field()
{
Name = "Поле 2",
ConditionType = "=",
Value = "3"
},
new Field()
{
Name = "Поле 3",
ConditionType = ">",
Value = "3"
},
new Field()
{
Name = "Поле 4",
ConditionType = "<",
Value = "3"
},
new Button()
{
Name = "Добавить"
}
}
}
}
});
Notify("Tree");
}
}
public static class PickList
{
public static ObservableCollection<string> Fields
{
get
{
return new ObservableCollection<string>() { "Поле 1", "Поле 2", "Поле 3", "Поле 4" };
}
}
public static ObservableCollection<string> ConditionType
{
get
{
return new ObservableCollection<string>() { ">", "<", "=" };
}
}
}
public class Condition : BaseModel
{
private ObservableCollection<object> _ChildNodes;
public ObservableCollection<object> ChildNodes
{
get { return _ChildNodes; }
set { _ChildNodes = value; Notify("ChildNodes"); }
}
public string ConditionType { get; set; }
}
public class Field : BaseModel
{
public ObservableCollection<string> Fields
{
get
{
return PickList.Fields;
}
}
public ObservableCollection<string> ConditionTypes
{
get
{
return PickList.ConditionType;
}
}
public string Name { get; set; }
public string ConditionType { get; set; }
public string Value { get; set; }
}
public class Button : BaseModel
{
public string Name { get; set; }
}
Converter
public sealed class DataTemplateSelector : IValueConverter
{
public DataTemplate ConditionDataTemplate { get; set; }
public DataTemplate FieldDataTemplate { get; set; }
public DataTemplate ButtonDataTemplate { get; set; }
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplateSelector()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
Type type = value.GetType();
if (typeof(Condition).TypeHandle == type.TypeHandle)
{
return ConditionDataTemplate;
}
else if (typeof(Field).TypeHandle == type.TypeHandle)
{
return FieldDataTemplate;
}
else if (typeof(Button).TypeHandle == type.TypeHandle)
{
return ButtonDataTemplate;
}
}
return DefaultDataTemplate;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
I plan to add the Field display types DateTime, Bool, ...
精彩评论