Selthien
Selthien

Reputation: 1258

Bind ItemsControl ItemSource to UserControl Dependency Property

this is my very first shot at trying to create a User Control with Dependency Properties. So excuse my lack of knowledge over the subject. I created a general design on one of my pages that I would like to bring over to a reusable user control.

Original Control On Page

So this is the control I am trying to port into a reusable UserControl

<ToggleButton x:Name="filterButton" Background="{StaticResource BackgroundLightBrush}" BorderThickness="0">
    <fa:ImageAwesome x:Name="border"
                     Height="15"
                     Foreground="{StaticResource MediumBlueBrush}"
                     Icon="Filter"/>
</ToggleButton>

<Popup x:Name="popup" 
       AllowsTransparency="True"
       StaysOpen="False"
       PlacementTarget="{Binding ElementName=filterButton}"
       IsOpen="{Binding ElementName=filterButton,Path=IsChecked,Mode=TwoWay}">
    <Border BorderThickness="2" BorderBrush="{StaticResource MediumBlueBrush}" Background="{StaticResource BackgroundLightBrush}" CornerRadius="5">
        <ItemsControl ItemsSource="{Binding HeaderList}"
                      Background="{StaticResource BackgroundLightBrush}"
                      Margin="1">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <CheckBox IsChecked="{Binding IsChecked}"/>
                        <TextBlock Text="{Binding HeaderName}"/>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Border>
</Popup>

Here is a picture of what this control looks like

enter image description here

This is my Dependency Property Code

Here you can see I created an ObservableCollection of type RulesColumnHeader. This is the item source I am trying to set my UserControl to.

public partial class FilterDropDown : UserControl
{
    public ObservableCollection<RulesColumnHeader> ItemSource
    {
        get => (ObservableCollection<RulesColumnHeader>)GetValue(ItemSourceProperty);
        set => SetValue(ItemSourceProperty, value);
    }
    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemSourceProperty =
        DependencyProperty.Register("ItemSource", typeof(ObservableCollection<RulesColumnHeader>), typeof(FilterDropDown), new FrameworkPropertyMetadata(null));

    public FilterDropDown()
    {
        InitializeComponent();
    }
}

UserControl

Here is my "attempt" at creating a user control and binding the item source to the dependency property I created.

<UserControl x:Class="YAI.BomConfigurator.Desktop.Control.FilterDropDown"
         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:local="clr-namespace:YAI.BomConfigurator.Desktop.Control"
         xmlns:fa="http://schemas.fontawesome.io/icons/"
         mc:Ignorable="d" 
         d:DesignHeight="50" d:DesignWidth="50">
<Grid>
    <ToggleButton x:Name="filterButton" Background="Transparent" BorderThickness="0">
        <fa:ImageAwesome x:Name="border"
                         Height="15"
                         Foreground="{StaticResource MediumBlueBrush}"
                         Icon="Filter"/>
    </ToggleButton>

    <Popup x:Name="popup" 
           AllowsTransparency="True"
           StaysOpen="False"
           PlacementTarget="{Binding ElementName=filterButton}"
           IsOpen="{Binding ElementName=filterButton,Path=IsChecked,Mode=TwoWay}">

        <Border BorderThickness="2" BorderBrush="{StaticResource MediumBlueBrush}" Background="{StaticResource BackgroundLightBrush}" CornerRadius="5">
            <ItemsControl ItemsSource="{Binding ItemSource, Source={local:FilterDropDown}}"
                          Background="{StaticResource BackgroundLightBrush}"
                          Margin="1">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal" Margin="5">
                            <CheckBox IsChecked="{Binding IsChecked}"/>
                            <TextBlock Text="{Binding HeaderName}"/>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Border>
    </Popup>
</Grid>

On my ItemSource binding on ItemsControl for the, the source= portion throws an error saying "Invalid Markup Extension, expected type is 'object' actual is FilterDropDown".

So this is basically where I am at now. I am not sure how to move forward or what to do from here. I am trying to figure out how to bind the UserControl item source to the dependency property. I don't know if its my syntax or I am doing this whole thing incorrectly. If anyone could help guide me along that would be great.

Thank you,

Upvotes: 2

Views: 1951

Answers (2)

Clemens
Clemens

Reputation: 128084

Setting the Source property of the ItemsSource Binding is wrong.

Replace

ItemsSource="{Binding ItemSource, Source={local:FilterDropDown}}"

with

ItemsSource="{Binding ItemSource,
                      RelativeSource={RelativeSource AncestorType=UserControl}}"

Besides that, it is unnecessary or even wrong to declare the ItemsSource as an ObservableCollection. Use a more general type like IEnumerable:

public IEnumerable ItemSource
{
    get => (IEnumerable)GetValue(ItemSourceProperty);
    set => SetValue(ItemSourceProperty, value);
}

public static readonly DependencyProperty ItemSourceProperty =
    DependencyProperty.Register(
        nameof(ItemSource), typeof(IEnumerable), typeof(FilterDropDown));

Upvotes: 4

SledgeHammer
SledgeHammer

Reputation: 7726

When you are building a custom control, you want to define your ItemsSource as:

public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable),
    typeof(xxx), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));

    public IEnumerable ItemsSource
    {
        get
        {
            return (IEnumerable)GetValue(ItemsSourceProperty);
        }

        set
        {
            SetValue(ItemsSourceProperty, value);
        }
    }

The OnItemsSourceChanged isn't necessary, I just had it in the code that I c&p'ed.

You'll also want your control to derive from Control. NOT a UserControl. And thus, the .cs and .xaml are not nested the same way. They are completely separate. The .cs is standalone and the xaml is in your Themes folder.

public class xxx : Control

and have a static constructor:

static xxx()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(xxx), new FrameworkPropertyMetadata(typeof(xxx)));
}

Then in your XAML, you'd define a Style that targets xxx and in that style set the ControlTemplate.

In there, in general, you would use TemplateBinding binding to bind to properties.

Upvotes: 0

Related Questions