1
votes

I have implemented a custom control that internally uses a ListView within the control template. This ListView is accessable by FindParts. I need to set the ListViews View property to an instance that I have to instanciate programmatically within my custom control. I want to be able to create this instance from a DataTemplate that can be bound to a property of my custom control.

The problem is that I do not know how to create an instance from a DataTemplate.

Note that I do not want to bind a GridView directly to the View property (e.g. from a ResourceDictionary where the DataTemplates x:Shared attribute is set to false) as this leads to problems in the XAML designer (View can't be shared by more than one ListView).

Edit: Durig my discussion with grek40 it became clear that it is not possible to provide the GridView within a DataTemplate. Thus the answer marked as solution does not handle the question how to create an instance from a DataTemplate as implied by the title of my question.

2
I get the feeling that you are slightly off track at some point. ListView.View is of type ViewBase so if you want to pass this part from the outside into your CustomControl, you need a property of this type. Maybe some sample code would help to understand where you want the DataTemplate in this whole thing. - grek40
Yes, you are right. At the moment I have a DependencyProperty of type ViewBase. In a ResourceDictionary I have Resources for that property and Styles with a Setter to assign those Resources. As I mentioned above I had to set x:Shared=false in order to get those Resources to be usable at RunTime but they lead to problems at design time. I now want to change my control to accept a DataTemplate that contains the definition of the GridView. The control then needs to create an instance of the DataTemplate. My question is how to create an instance from a DataTemplate. - E812
Is there a reason you're not just binding the listview's itemsource and Itemtemplate? - MikeT
The control is an AutoCompleteLookup. It can be styled to support different data types. When the suggestions are displayed the items can be shown with columns. For some data types it will show only one column, for others there can be multiple. In order to be able to define the various columns I must be able to exchange the Listviews view. - E812

2 Answers

2
votes

Since you didn't share much code, I made something out of thin air. The Idea: MyCustomControl is hosting items and for that, a ListView is used in the ControlTemplate. A default GridView is included directly in the ControlTemplate but it's possible to change it via MyCustomControl.MyView

The Custom Control:

public class MyCustomControl : ItemsControl
{
    static MyCustomControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
    }


    public ViewBase MyView
    {
        get { return (ViewBase)GetValue(MyViewProperty); }
        set { SetValue(MyViewProperty, value); }
    }

    public static readonly DependencyProperty MyViewProperty =
        DependencyProperty.Register("MyView", typeof(ViewBase), typeof(MyCustomControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyViewChanged)));

    private static void OnMyViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyCustomControl)d).HasCustomView = e.NewValue != null;
    }



    // readonly property to support the Trigger in the ControlTemplate in order to exchange the ListView.View
    public bool HasCustomView
    {
        get { return (bool)GetValue(HasCustomViewProperty); }
        private set { SetValue(HasCustomViewPropertyKey, value); }
    }

    private static readonly DependencyPropertyKey HasCustomViewPropertyKey =
        DependencyProperty.RegisterReadOnly("HasCustomView", typeof(bool), typeof(MyCustomControl), new PropertyMetadata(false));
    public static readonly DependencyProperty HasCustomViewProperty = HasCustomViewPropertyKey.DependencyProperty;
}

The Control Template (note the default GridView and the Trigger for Replacement):

<Style TargetType="{x:Type local:MyCustomControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ListView x:Name="PART_ListView"
                              ItemsSource="{Binding Items,RelativeSource={RelativeSource TemplatedParent}}">
                        <ListView.View>
                            <GridView>
                                <GridView.Columns>
                                    <GridViewColumn Header="DEF"/>
                                </GridView.Columns>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasCustomView" Value="True">
                        <Setter TargetName="PART_ListView" Property="View" Value="{Binding MyView,RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Usage:

<local:MyCustomControl>
    <!--uses default view-->
    <TextBlock Text="Item 1"/>
    <TextBlock Text="Item 2"/>
</local:MyCustomControl>

<local:MyCustomControl>
    <!--uses custom view-->
    <local:MyCustomControl.MyView>
        <GridView>
            <GridView.Columns>
                <GridViewColumn Header="ABC"/>
            </GridView.Columns>
        </GridView>
    </local:MyCustomControl.MyView>
    <TextBlock Text="Item 1"/>
    <TextBlock Text="Item 2"/>
</local:MyCustomControl>

Feel free to ask if anything remains unclear. As commented, I don't know how to apply your DataTemplate idea to all this stuff.

Edit:

The following is a an example with resource section to define some data and DataTemplates as well as their possible usage.

<StackPanel>
    <StackPanel.Resources>
        <x:Array x:Key="Items1" Type="{x:Type sys:Int32}">
            <sys:Int32>1</sys:Int32>
            <sys:Int32>2</sys:Int32>
            <sys:Int32>3</sys:Int32>
            <sys:Int32>4</sys:Int32>
        </x:Array>

        <x:Array x:Key="Items2" Type="{x:Type sys:Int32}">
            <sys:Int32>5</sys:Int32>
            <sys:Int32>4</sys:Int32>
            <sys:Int32>6</sys:Int32>
            <sys:Int32>7</sys:Int32>
        </x:Array>

        <CollectionViewSource x:Key="ItemsSource1" Source="{StaticResource Items1}"/>
        <CollectionViewSource x:Key="ItemsSource2" Source="{StaticResource Items2}"/>

        <DataTemplate x:Key="IntegerListTemplate1">
            <local:MyCustomControl ItemsSource="{Binding}">
                <!--uses default view-->
            </local:MyCustomControl>
        </DataTemplate>

        <DataTemplate x:Key="IntegerListTemplate2">
            <local:MyCustomControl ItemsSource="{Binding}">
                <!--uses custom view-->
                <local:MyCustomControl.MyView>
                    <GridView>
                        <GridView.Columns>
                            <GridViewColumn Header="ABC"/>
                        </GridView.Columns>
                    </GridView>
                </local:MyCustomControl.MyView>
            </local:MyCustomControl>
        </DataTemplate>
    </StackPanel.Resources>
    <!--the default view-->
    <ContentControl ContentTemplate="{StaticResource IntegerListTemplate1}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
    <Separator Margin="3"/>
    <!--same items other view-->
    <ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource1}}"/>
    <Separator Margin="3"/>
    <!--different items same view as second one, no complaints about reusing-->
    <ContentControl ContentTemplate="{StaticResource IntegerListTemplate2}" Content="{Binding Source={StaticResource ItemsSource2}}"/>
</StackPanel>

Edit 2, targeting the commented MCVE:

I would solve this exactly as I already explained in my first edit. Turn your control into a DataTemplate, not the view into a style.

Replace in MainWindow.xaml:

<!-- old -->
<myctrls:MyControl Grid.Row="0" Grid.Column="0" Style="{StaticResource AddressStyle}" ItemsSource="{Binding Addresses}"/>
<!-- new -->
<ContentControl Grid.Row="0" Grid.Column="0" ContentTemplate="{StaticResource AddressTemplate}" Content="{Binding Addresses}"/>

Same with the other occurences.

In MyControlStyles.xaml (or a renamed file that indicates DataTemplate usage), merge the GridView resource and the Style with the myctrls:MyControl into a DataTemplate.

Old:

<GridView x:Key="AddressGridView" x:Shared="False">
    <GridViewColumn Header="City" Width="Auto" >
        <GridViewColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
            </DataTemplate>
        </GridViewColumn.CellTemplate>
    </GridViewColumn>
    <GridViewColumn Header="Country" Width="Auto" >
        <GridViewColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
            </DataTemplate>
        </GridViewColumn.CellTemplate>
    </GridViewColumn>
</GridView>

<Style x:Key="AddressStyle" TargetType="{x:Type myctrls:MyControl}">
    <Setter Property="SuggestionsView" Value="{DynamicResource AddressGridView}"/>
</Style>

New:

<DataTemplate x:Key="AddressTemplate">
    <!-- the base control -->
    <myctrls:MyControl ItemsSource="{Binding}">
        <!-- this was previously assigned by AddressStyle -->
        <myctrls:MyControl.SuggestionsView>
            <!-- this was previously the AddressGridView -->
            <!-- Same as before, only removed the x:Key and x:Shared -->
            <GridView>
                <GridViewColumn Header="City" Width="Auto" >
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="Country" Width="Auto" >
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Country}" HorizontalAlignment="Left"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </myctrls:MyControl.SuggestionsView>
    </myctrls:MyControl>
</DataTemplate>

Same procedure for the ProductsStyle.

If you don't like the ContentControl wire-up approach, consider the creation of additional viewmodels that host then collections, like AddressListViewModel. This would allow you to handle the DataTemplate by DataType instead of resource key and explicit usage in ContentControl.

0
votes

You could implement your own DataTemplateSelector, which then dynamically creates your one-time-use DataTemplate, roughly like that:

public DataTemplate GenerateTemplate() {
  var template = new DataTemplate();
  var p = new FrameworkElementFactory(typeof(Grid));
  p.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
  p.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
  ...
  template.VisualTree = p;
  return template;
}