None
None

Reputation: 5670

Pass ObservableCollection<> type as dependency property

I am trying to create a multi-select Combobox Custom control, This custom control should expose a dependency property called DropDownDataSource through which the user of the control can decide what day should bound to ComboBox. My code looks like this:

MainPage.Xaml

<Grid>
    <local:CustomComboBox x:Name="customcb" DropDownDataSource="{x:Bind DropDownDataSource, Mode=OneWay}"  Loaded="CustomControl_Loaded"> </local:CustomComboBox>
</Grid>

MainPage.Xaml.cs

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    private ObservableCollection<Item> _dropDownDataSource;
    public ObservableCollection<Item> DropDownDataSource
    {
        get => _dropDownDataSource;
        set
        {
            _dropDownDataSource = value;
            OnPropertyChanged();
        }
    }

    public MainPage()
    {
        this.InitializeComponent();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName]string name = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
    private void CustomControl_Loaded(object sender, RoutedEventArgs e)
    {

       
        var Items = new ObservableCollection<Item>(Enumerable.Range(1, 10)
              .Select(x => new Item
              {
                  Text = string.Format("Item {0}", x),
                  IsChecked = x == 40 ? true : false
              }));
        DropDownDataSource = Items;
    
    }
}

Models

    public class Item : BindableBase
{
    public string Text { get; set; }

    bool _IsChecked = default;
    public bool IsChecked { get { return _IsChecked; } set { SetProperty(ref _IsChecked, value); } }
}

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void SetProperty<T>(ref T storage, T value,
        [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
    {
        if (!object.Equals(storage, value))
        {
            storage = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

CustomUserControl XAML

  <Grid x:Name="GrdMainContainer">
   
    <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBox Width="200" FontSize="24" Text="{Binding Header, Mode=TwoWay}" 
             IsReadOnly="True" TextWrapping="Wrap" MaxHeight="200" />
        <ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200" Width="200" Background="White">
            <ItemsControl ItemsSource="{Binding Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <CheckBox Content="{Binding Text}" 
                      FontSize="24" 
                      Foreground="Black"
                      IsChecked="{Binding IsChecked, Mode=TwoWay}" 
                      IsThreeState="False" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </StackPanel>
</Grid>

CustomUserControl Cs file

    public sealed partial class CustomComboBox : UserControl
{
    public CustomComboBox()
    {
        this.InitializeComponent();

    }
    public ObservableCollection<Item> DropDownDataSource
    {
        get { return (ObservableCollection<Item>)GetValue(DropDownDataSourceProperty); }
        set { SetValue(DropDownDataSourceProperty, value); }
    }

    public static readonly DependencyProperty DropDownDataSourceProperty =
        DependencyProperty.Register("DropDownDataSource", typeof(ObservableCollection<Item>), typeof(CustomComboBox), new PropertyMetadata("", HasDropDownItemUpdated));
    private static void HasDropDownItemUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is CustomComboBox ucrcntrl)
        {
            var grd = UIElementExtensions.FindControl<Grid>(ucrcntrl, "GrdMainContainer");
            grd.DataContext = ucrcntrl.DropDownDataSource as ObservableCollection<Item>;
        }
    }

}

All looks good to me, but for some reason, Dropdown is coming empty. Instead of the dependency property, If I assign a view model directly to the Control it works fine. But in my condition, it is mandatory that I have properties like DataSource,SelectedIndex, etc on the user control for the end-user to use. Can anyone point out what is going wrong here? Here, I have attached a copy of my complete code.

Upvotes: 0

Views: 240

Answers (1)

Anran Zhang
Anran Zhang

Reputation: 7727

I downloaded your sample code, the problem should be in the binding.

<ItemsControl ItemsSource="{Binding Items}">

This way of writing is not recommended. In the ObservableCollection, Items is a protected property and is not suitable as a binding property.

You can try to bind dependency property directly in ItemsControl:

<ItemsControl ItemsSource="{x:Bind DropDownDataSource,Mode=OneWay}">
    <ItemsControl.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <CheckBox IsChecked="{x:Bind IsChecked, Mode=TwoWay}" 
                      IsThreeState="False" >
                <TextBlock Text="{x:Bind Text}" Foreground="Black" FontSize="24"/>
            </CheckBox>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

In addition, you may have noticed that I modified the style of CheckBox and rewritten the content to TextBlock, because in the default style of CheckBox, Foreground is not bound to the internal ContentPresenter.

Thanks.

Upvotes: 1

Related Questions