0
votes

If I build a custom control with some controls inside it (witch also have some bindings), how can I remove the binding parts from the custom control XAML (like Text="{Binding Path=Name}" and ItemsSource="{Binding}") to make the control reusable? My guess is to create some dependency properties but I don't know how to do this and what makes it harder for me is that some bindings are inside the DataTemplate of the custom control and I can't get the instances by GetTemplateChild().

Here is a my code:

Custom Control:

public class CustomListBox : Control
    {
        static CustomListBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
        }
    }

Generics.xaml:

<Style TargetType="{x:Type local:CustomListBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomListBox}">
                <ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <TextBox x:Name="BindingTextBox" Text="{Binding Path=Name}"></TextBox>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

MainWindow.xaml:

<StackPanel>
     <local:CustomListBox x:Name="BindingCustomListBox"></local:CustomListBox>
</StackPanel>

MainWindow.xaml.cs And Person(Sample Data) Class:

public partial class MainWindow : Window
{
    public ObservableCollection<Person> PersonList { get; set; }
    public MainWindow()
    {
        InitializeComponent();
        PersonList = new ObservableCollection<Person>
        {
            new Person{ Name = "Person1" },
            new Person{ Name = "Person2" }
        };
        BindingCustomListBox.DataContext = PersonList;
    }
}
public class Person
{
    public string Name { get; set; }
}

by removing binding parts I mean moving from custom control to Window.xaml or where ever the user wants to use the control. I hope it's clear enough.

2
Take a look at Control Authoring Basics. Your control should expose (bindable) dependency properties, and the elements in its Template should bind to these properties by TemplateBindings or Bindings with RelativeSource TemplatedParent. However, a custom ListBox should simply be derived from ListBox.Clemens
It seems TemplatedParent is find but I couldn't make it work yet. Should I create those dependency properties of type String or Binding? can you help me on this?amir mola

2 Answers

1
votes

And an ItemsSource property to your control:

public class CustomListBox : Control
{
    static CustomListBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
    }

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

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox));
}

Bind to it in your template:

<Style TargetType="{x:Type local:CustomListBox}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:CustomListBox}">
                <ListBox x:Name="MainListBox" ItemsSource="{TemplateBinding ItemsSource}" IsSynchronizedWithCurrentItem="true">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <TextBox x:Name="BindingTextBox" Text="{Binding Name}"></TextBox>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

...and in your view:

<local:CustomListBox x:Name="BindingCustomListBox" ItemsSource="{Binding PersonList}" />
0
votes

I found a solution though not sure if a better one exists(I didn't find any after 3 days). first I added a dependency property (NameBindingStr) to enable users to define the PropertyPath of the binding:

public class CustomListBox : Control
    {
        static CustomListBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
        }
        public static readonly DependencyProperty NameBindingStrProperty =
           DependencyProperty.Register(
               "NameBindingStr", typeof(string), typeof(CustomListBox),
               new FrameworkPropertyMetadata(""));
        public string NameBindingStr
        {
            get { return (string)GetValue(NameBindingStrProperty); }
            set { SetValue(NameBindingStrProperty, value); }
        }
    }

And the XAML for the custom control:

<Style TargetType="{x:Type local:CustomListBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomListBox}">
                    <ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <StackPanel>
                                    <local:BindTextBox TextBindingPath="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomListBox}}, Path=NameBindingStr, Mode=TwoWay}"></local:BindTextBox>
                                </StackPanel>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

for the custom control's items to bind I inherited BindTextBox from TextBox:

public class BindTextBox : TextBox
    {
        static BindTextBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(BindTextBox), new FrameworkPropertyMetadata(typeof(BindTextBox)));
        }
        public static readonly DependencyProperty TextBindingPathProperty =
           DependencyProperty.Register(
               "TextBindingPath", typeof(string), typeof(BindTextBox),
               new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextBindingPathChanged)));
        public string TextBindingPath
        {
            get { return (string)GetValue(TextBindingPathProperty); }
            set { SetValue(TextBindingPathProperty, value); }
        }
        private static void OnTextBindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            BindTextBox elem = obj as BindTextBox;
            var newTextBinding = new Binding((string)args.NewValue);
            newTextBinding.Mode = BindingMode.TwoWay;
            BindingOperations.SetBinding(elem, TextProperty, newTextBinding);
        }
    }

XAML:

<Style TargetType="{x:Type local:BindTextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:BindTextBox}">
                    <TextBox x:Name="TemplateTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>  

The MainWindow.xaml.cs is not changed and I won't type it again(can be found in the question). I have to recall that my goal was to let the user to easily set the binding path. Now the the custom control can be used by one single code:

<local:CustomListBox x:Name="BindingCustomListBox" NameBindingStr="Name"></local:CustomListBox>

Works perfect.