user4717662
user4717662

Reputation:

UWP/XAML custom property which can "host" controls

I'm actually creating an UserControl for a custom ListView. What I'm trying to achieve is the same thing as this post: Hide or Show stackpanel of ListViewItem with VisualStateManager. I already implemented the code and it works, but now I want to make it more "generic" so I've created a user control.

My control is divided into two grid. The upper grid and the collapsed hidden grid.

And I want to use my user control like this:

<controls:ExpandableListView ItemsSource="{Binding ConnectedObjects}">
    <controls:ExpandableListView.Header>
        <TextBlock Text="Hello World!" />
    </controls:ExpandableListView.Header>
    <controls:ExpandableListView.ExpandedContent>
         <Button Content="test 2" />
    </controls:ExpandableListView.ExpandedContent>
</controls:ExpandableListView>

The Header property is the content which will be placed in the upper grid, and the ExpandedContent is the content placed in the collapsed grid.

In my user control I have:

<ListView x:Name="LIST" SelectionChanged="LIST_SelectionChanged" ItemsSource="{Binding ItemsSource}">

    <!-- Item container style -->
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
    </ListView.ItemContainerStyle>

    <!-- Item template -->
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>

                <Grid x:Name="GRID_1" Grid.Row="0">
                    <ContentPresenter Content="{Binding Path=Header}" />
                </Grid>

                <Grid x:Name="GRID_2" Grid.Row="1" Visibility="Collapsed">
                    <ContentPresenter Content="{Binding Path=ExpandedContent}" />
                </Grid>

                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal"></VisualState>
                        <VisualState x:Name="Selected">
                            <VisualState.Setters>
                                <Setter Target="GRID_2.Visibility" Value="Visible"></Setter>
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

There is the code behind of the user control

public sealed partial class ExpandableListView : UserControl
{
    private static readonly DependencyProperty ItemsSourceProperty = 
        DependencyProperty.Register(
            "ItemsSource", 
            typeof(IEnumerable), 
            typeof(ExpandableListView),
            new PropertyMetadata(null));

    public static readonly DependencyProperty HeaderContentProperty =
        DependencyProperty.Register(
            "Header",
            typeof(Object),
            typeof(ExpandableListView),
            new PropertyMetadata(null));

    public static readonly DependencyProperty ExpandedContentProperty =
        DependencyProperty.Register(
            "ExpandedContent",
            typeof(Object),
            typeof(ExpandableListView),
            new PropertyMetadata(null));

    public IEnumerable ItemsSource
    {
        get { return this.GetValue(ItemsSourceProperty) as IEnumerable; }
        set { this.SetValue(ItemsSourceProperty, value); }
    }

    public Object Header
    {
        get { return (Object)this.GetValue(HeaderContentProperty); }
        set { this.SetValue(HeaderContentProperty, value); }
    }

    public Object ExpandedContent
    {
        get { return (Object)this.GetValue(ExpandedContentProperty); }
        set { this.SetValue(ExpandedContentProperty, value); }
    }

    public ExpandableListView()
    {
        this.InitializeComponent();
        this.LIST.DataContext = this;
    }

    private void LIST_SelectionChanged(Object sender, SelectionChangedEventArgs e)
    {
    }

    private void SetInEditMode()
    {
        VisualStateManager.GoToState(this, "Selected", true);
    }

    private void SetInViewMode()
    {
        VisualStateManager.GoToState(this, "Normal", true);
    }
}

Upvotes: 0

Views: 1149

Answers (2)

DotNetRussell
DotNetRussell

Reputation: 9867

   <Grid x:Name="GRID_2" Grid.Row="1" Visibility="Collapsed">
      <ContentPresenter Content="{Binding MyUsersContent }"/>
   </Grid>

In your control CS file

public class YourControl{  

     public Object MyUsersContent
     {
         get { return (Object)GetValue(MyUsersContentProperty); }
         set { SetValue(MyUsersContentProperty, value); }
     }
    
     public static readonly DependencyProperty MyUsersContentProperty =
                   DependencyProperty.Register("MyUsersContent", 
                       typeof(Object), typeof(YoutControl), new PropertyMetadata());   
 }

This is probably the easiest way to do it. This is the reason we need to see your code though. Because it makes a difference how you implement this feature. If you have trouble with the binding then you can grab your control in the OnApplyTemplate function and pump it in manually.

Before you continue though I would highly recommend doing this tutorial. It will give you an idea of how this, and many other issues you'll face should be solved.

How to create a Custom Control in WPF

Upvotes: 0

Yuriy Pelekh
Yuriy Pelekh

Reputation: 308

I believe you need to add content attribute to specify which property will be used as content. Example:

[ContentProperty("ExpandedContent")]
public sealed partial class ExpandableListView : UserControl
{
...
    public static readonly DependencyProperty ExpandedContentProperty =
        DependencyProperty.Register(
            "ExpandedContent",
            typeof(Object),
            typeof(ExpandableListView),
            new PropertyMetadata(null));
...
    public Object ExpandedContent
    {
        get { return (Object)this.GetValue(ExpandedContentProperty); }
        set { this.SetValue(ExpandedContentProperty, value); }
    }

...
}

Also I prefer to use nemeof() in dependency property declaration. It makes code safer to refactoring and renaming:

public static readonly DependencyProperty ExpandedContentProperty =
    DependencyProperty.Register(
        nameof(ExpandedContent),
        typeof(Object),
        typeof(ExpandableListView),
        new PropertyMetadata(null));

Upvotes: 1

Related Questions