Reputation: 195
Here's a situation I am trying to solve:
I have a base UserControl from which I derive a number of other Controls that handle derivations of a base Object in a specific manner. (The purpose of this being to create a template for when additional derivations of the base control are needed later down the road.) What I would like to do is use the base control name as the tag in XAML, but when the control is actually rendered, show the derived control.
class BaseControl : UserControl { }
class DerivedControl1 : BaseControl { }
class DerivedControl2 : BaseControl { }
class BaseObject { }
class DerivedObject1 : BaseObject { // Requires DerivedControl1 to display }
class DerivedObject2 : BaseObject { // Requires DerivedControl2 to display }
class BaseContainerObject { }
class ContainerObject1 : BaseContainerObject
{
DerivedObject1 dObject0;
DerivedObject1 dObject1;
DerivedObject2 dObject2;
}
class ContainerObject2 : BaseContainerObject
{
DerivedObject2 dObject0;
DerivedObject2 dObject1;
DerivedObject1 dObject2;
}
window.xaml
<!-- Here is what I would like to do -->
<StackPanel>
<BaseControl Name="Object0" DependencyProperties="{Binding BaseContainerObject.dObject0}" />
<BaseControl Name="Object1" DependencyProperties="{Binding BaseContainerObject.dObject1}" />
<BaseControl Name="Object2" DependencyProperties="{Binding BaseContainerObject.dObject2}" />
</StackPanel>
I've played around with styles and data triggers to detect the specific type of ContainerObject, but I haven't found the right pattern to encapsulate a ContainerObject in a single template-able "package" yet.
I could dynamically add the controls from the code-behind, but I haven't had any luck with that so far. (The top-level of the control appears on VisualTree, but no children appear on the tree and none are rendered.)
Any thoughts?
EDIT:
I can't post a screenshot at the moment, but perhaps I can add a little more detail.
I have a data object (the DataContext for the window) that has up to nine attributes (the DerivedObjects) that the user will need to edit in my window. The meaning of those nine attributes, and, in turn, how they should be expressed in UI controls, changes based on the attributes of a second data object the user selects in a previous step. (That is the ContainerObject. The other data object is not referenced in the above code, although it contains a reference to the second data object.)
Those attributes can be expressed in four different ways: a text box (for continuous values), a combobox (for discrete values), a checkbox (for boolean values) and radio buttons (for a choice between two values).
I have created UserControls that package those controls in a horizontal Grid with 1) a label for the value's definition, 2) the value's units (if applicable) and, if applicable, 3) a checkbox to view the value in an alternate format (i.e. viewing a decimal number in hex). (Those are the DerivedControls that inherit from an XAML-less BaseControl that stores common properties and functions.) To maintain proper column alignment over the entire collection, I specify four column widths in a Style at the Window level and use a Converter to handle alignment for attributes that do not require the units and/or the alt-display checkbox.
When the user selects the second object in the previous step, the nine rows of the collection control should look to the second data object reference of the DataContext object to select the proper template and populate the other labels. Because I will need to use this collection in other programs, I am creating it in a separate assembly.
I know I am pigeon-holing myself in some fashion on this. I am trying to do this with as little code as possible, but I can't think of the right code pattern to use here. Every component is working fine, but I can't seem to get it all to come together in a simple way so I can work out the last few little bugs.
Thanks. I am just learning WPF, and I really like. I'm just at the point of trying to get my head wrapped around some of the finer details.
Upvotes: 0
Views: 661
Reputation: 13302
Here is a pretty good example from wpftutorial.net of what it sounds like you need. To summarize, you can use a DataTemplate to define how an object is displayed within a repeating control such as a ListBox, ComboBox or ListView. You can override the styles of those to make them appear as you want, or sometimes it's just easier to use ItemsControl (the control they inherit from) directly. They have a property named ItemsPanel that will allow you to specify a StackPanel as the ItemsPanelTemplate so you get the same desired layout of the objects as you showed above.
Setting how an object is dispalyed via a DataTemplate is great, but you want to dynamically change that template based on the type of the bound object if I understand correctly. This can be accomplished by creating a DataTemplateSelector.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate DerivedObject1Template { get; set; }
public DataTemplate DerivedObject2Template { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DataTemplate selectedTemplate = DefaultDataTemplate;
if (item is DerivedObject1)
{
selectedTemplate = DerivedObject1Template
}
else if (item is DerivedObject2)
{
selectedTemplate = DerivedObject2Template;
}
return selectedTemplate;
}
}
And then your XAML can use the template selector on the repeating control:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:..."
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplateResource">
...
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="DerivedObject1TemplateResource">
<local:DerivedControl1 .../>
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="DerivedObject2TemplateResource">
<local:DerivedControl2 .../>
</DataTemplate>
<!-- DataTemplate Selector -->
<local:PropertyDataTemplateSelector x:Key="myCustomTemplateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplateResource}"
DerivedObject1Template = "{StaticResource DerivedObject1TemplateResource}"
DerivedObject2Template = "{StaticResource DerivedObject2TemplateResource}"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource myCustomTemplateSelector}"/>
</Grid>
</Window>
Hopefully that will get you started!
Upvotes: 2