Juan Pablo Gomez
Juan Pablo Gomez

Reputation: 5534

XAML x:Class Generic

I have this class

public class FeatureTabBase<T> : UserControl, IFeatureTab<T>
    where T : BaseModel
{
    public string TabGuid { get; set; }

    public T FeaturedElement
    {
        get { return (T)GetValue(FeaturedElementProperty); }
        set { SetValue(FeaturedElementProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FeaturedElement.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FeaturedElementProperty =
        DependencyProperty.Register("FeaturedElement", typeof(T), typeof(FeatureTabBase<T>), new PropertyMetadata(null));
}

That implements this interface

public interface IFeatureTab<T>
    where T : class
{
    T FeaturedElement { get; set; }
    string TabGuid { get; set; }
}

And this instance from it

public partial class MyClass : FeatureTabBase<MyType>
{
    public MyClass()
    {
        InitializeComponent();
    }
}

But don't know how instantiate it on XAML

My xaml code for partial class MyClass : FeatureTabBase<MyType>

All I'm trying to do is a generic console that can show some pages for my different kind of items.

I was reading about x:TypeArguments at

https://learn.microsoft.com/en-us/dotnet/desktop-wpf/xaml-services/xtypearguments-directive

But nothing works.

Any Ideas?

Upvotes: 0

Views: 809

Answers (3)

Dean Chalk
Dean Chalk

Reputation: 20471

XAML doesnt support generics, so the ctrl:FeatureTabBase will never work. Also, you cannot inherit the XAML part of a UserControl if you derive a new class from an existing UserControl. You can't use strongly-typed DataTemplates as they only hook up to the concrete class specified in the type. You need to take a different approach. Maybe simplify ?

public class FeatureTab : UserControl
{
    public static readonly DependencyProperty FeaturedElementProperty =
        DependencyProperty.Register(
            "FeaturedElement",
            typeof(ModelBase),
            typeof(FeatureTab)
            , new PropertyMetadata(null));

    public string TabGuid { get; set; }

    public ModelBase FeaturedElement
    {
        get => (ModelBase) GetValue(FeaturedElementProperty);
        set => SetValue(FeaturedElementProperty, value);
    }
}

NOTE This answer is valid for .Net frameworks prior to 4.7, if you point your project to .Net framework 4.7.2 Generics must work on xaml.

Upvotes: 0

Clemens
Clemens

Reputation: 128136

Add x:TypeArguments to the UserControl declaration in XAML:

<ctr:FeatureTabBase
     x:Class="YourNamespace.MyClass"
     x:TypeArguments="local:MyType"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:ctr="clr-namespace:YourNamespace"
     xmlns:local="clr-namespace:YourNamespace"
     mc:Ignorable="d" 
     d:DesignHeight="450" d:DesignWidth="800">
    ...
</ctr:FeatureTabBase>

Upvotes: 1

Andy
Andy

Reputation: 12276

Here's a control I use which will at least illustrate the concept.

Obviously, this is not going to be cut and paste for whatever it is you have in mind.

An editrow allows me to easily line up a series of labelled controls inside a stackpanel, and add various standardised functionality to the controls I make content.

public class EditRow : ContentControl
{
    public string LabelFor
    {
        get { return (string)GetValue(LabelForProperty); }
        set { SetValue(LabelForProperty, value); }
    }
    public static readonly DependencyProperty LabelForProperty = DependencyProperty.RegisterAttached(
                      "LabelFor",
                      typeof(string),
                      typeof(EditRow));
    public string LabelWidth
    {
        get { return (string)GetValue(LabelWidthProperty); }
        set { SetValue(LabelWidthProperty, value); }
    }
    public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.RegisterAttached(
                      "LabelWidth",
                      typeof(string),
                      typeof(EditRow)
                      );
    public string PropertyWidth
    {
        get { return (string)GetValue(PropertyWidthProperty); }
        set { SetValue(PropertyWidthProperty, value); }
    }
    public static readonly DependencyProperty PropertyWidthProperty = DependencyProperty.RegisterAttached(
                      "PropertyWidth",
                      typeof(string),
                      typeof(EditRow)
                     );
    public EditRow()
    {
        this.IsTabStop = false;
    }
}

I template this in a resource dictionary. ( There are other options including custom control generic xaml)

<Style TargetType="{x:Type spt:EditRow}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type spt:EditRow}">
                <Grid Height="Auto">
                    <Grid.ColumnDefinitions>

                        <ColumnDefinition Width="{Binding RelativeSource={
                                      RelativeSource FindAncestor,
                                      AncestorType=spt:EditRow}, 
                                      Path=LabelWidth, TargetNullValue=2*}"/>

                        <ColumnDefinition Width="{Binding RelativeSource={
                                      RelativeSource FindAncestor,
                                      AncestorType=spt:EditRow}, 
                                      Path=PropertyWidth, TargetNullValue=3*}"/>

                    </Grid.ColumnDefinitions>

                    <TextBlock Text="{Binding RelativeSource={
                                      RelativeSource FindAncestor,
                                      AncestorType=spt:EditRow}, 
                                      Path=LabelFor}"
                                      HorizontalAlignment="Right"
                                      TextAlignment="Right"
                                      Margin="2,4,0,2"/>
                    <Border Padding="8,2,8,2" Grid.Column="1" BorderThickness="0">
                        <ContentPresenter>
                            <ContentPresenter.Resources>
                                <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource ErrorToolTip}"/>
                                <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ErrorToolTip}"/>
                                <Style TargetType="{x:Type DatePicker}" BasedOn="{StaticResource ErrorToolTip}"/>
                            </ContentPresenter.Resources>
                        </ContentPresenter>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Usage:

                <ItemsControl>
                <spt:EditRow LabelFor="Name:" >
                        <TextBox Text="{Binding  EditVM.TheEntity.CustomerName, 
                            UpdateSourceTrigger=PropertyChanged,  
                            NotifyOnSourceUpdated=True,
                            NotifyOnValidationError=True,
                            Mode=TwoWay}"  />
                    </spt:EditRow>

                    <spt:EditRow LabelFor="Address:" >
                        <TextBox Text="{Binding  EditVM.TheEntity.Address1, 
                                                UpdateSourceTrigger=PropertyChanged, 
                                                NotifyOnSourceUpdated=True,
                                                NotifyOnValidationError=True,
                                                Mode=TwoWay}"  />
                    </spt:EditRow>

Notice that I have a TextBox as content of each of those editrows, but it could be a datepicker or whatever.

You could bind that content. Then use datatype on viewmodel type for variable datatemplates.

Upvotes: 1

Related Questions