Евгений К
Евгений К

Reputation: 15

Wpf binding collection property in UserControl (xaml)

Add collections of buttons in my userControl (Options). In xaml disigner the are displayed.

Output When i run my application:

MainWindow.xaml

    <Window x:Class="WpfAppUserControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppUserControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="300">
    <Grid>
        <local:Buttons x:Name="Buttons"  
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" 
                       HorizontalContentAlignment="Center">
            <local:Buttons.Options>
                <Button Content="Agudabi 1" Height="20" Margin="2" />
                <Button Content="Agudabi 2" Height="20" Margin="2" />
                <Button Content="Agudabi 3" Height="20" Margin="2" />
            </local:Buttons.Options>
        </local:Buttons>
    </Grid>
</Window>

Buttons.xaml

<UserControl x:Class="WpfAppUserControl.Buttons"
             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:WpfAppUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <WrapPanel x:Name="InternalContainer"  Orientation="Horizontal" HorizontalAlignment="Center"/>
    </Grid>
</UserControl>

Buttons.xaml.cs

#region Usings

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

#endregion

namespace WpfAppUserControl
{
    public partial class Buttons : UserControl
    {
        public Buttons()
        {
            //Options = new ObservableCollection<Button>();
            InitializeComponent();
        }

        public ObservableCollection<Button> Options
        {
            get { return (ObservableCollection<Button>) GetValue(OptionsProperty); }
            set { SetValue(OptionsProperty, value); }
        }

        public static readonly DependencyProperty OptionsProperty =
            DependencyProperty.Register(nameof(Options), typeof(ObservableCollection<Button>), typeof(Buttons),
                                        new PropertyMetadata(/*new ObservableCollection<Button>()*/, PropertyChangedCallback));

        private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = d as Buttons;

            foreach (var button in obj.Options)
            {
                obj.InternalContainer.Children.Add(button);
            }
        }
    }
}

Upvotes: 0

Views: 1017

Answers (2)

ASh
ASh

Reputation: 35680

first initialize OptionsProperty with an empty ObservableCollection:

public partial class Buttons : UserControl
{
    public Buttons()
    {
        Options = new ObservableCollection<Button>();

        InitializeComponent();
    }

    public ObservableCollection<Button> Options
    {
        get { return (ObservableCollection<Button>) GetValue(OptionsProperty); }
        set { SetValue(OptionsProperty, value); }
    }

    public static readonly DependencyProperty OptionsProperty =
        DependencyProperty.Register("Options", typeof(ObservableCollection<Button>), typeof(Buttons));    
}

Clemens commented that "Never set the default value of a collection type DP to anything else than null. Otherwise all instances of the UserControl class will operate on the same default collection instance." The same is true about any reference type. So property initialization is done in constructor.

you can do without PropertyChangedCallback, because it is possible to display collection more effectively using ItemsControl:

<UserControl x:Name="myUC" x:Class="WpfAppUserControl.Buttons"
             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:WpfAppUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
    <ItemsControl ItemsSource="{Binding Path=Options, ElementName=myUC}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel x:Name="InternalContainer"  Orientation="Horizontal" HorizontalAlignment="Center"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
  </Grid>
</UserControl>

note that PropertyChangedCallback is not triggered when you add items to collections because collection property itself hasn't changed, it is the same reference

Upvotes: 0

Clemens
Clemens

Reputation: 128061

Here is an example of what you should actually do.

Instead of a UserControl with a collection property, use an ItemsControl like this:

<ItemsControl ItemsSource="{Binding Options}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Name}" Command="{Binding Command}"
                    Height="20" Margin="2"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Then create a view model with a collection of data items with properties for the Button Content and Command:

public class ViewModel
{
    public ObservableCollection<Option> Options { get; }
        = new ObservableCollection<Option>();
}

public class Option
{
    public string Name { get; set; }
    public ICommand Command { get; set; }
}

Initialize it like shown below, where the ICommand implementation is ommited for brevity. Search the web for RelayCommand for a the implementation details.

public MainWindow()
{
    InitializeComponent();

    var vm = new ViewModel();
    vm.Options.Add(new Option { Name = "Agudabi 1" });
    vm.Options.Add(new Option { Name = "Agudabi 2" });
    vm.Options.Add(new Option { Name = "Agudabi 3" });

    DataContext = vm;
}

Upvotes: 1

Related Questions