amir mola
amir mola

Reputation: 405

How to make a reusable WPF Custom control with data binding properties?

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.

Upvotes: 0

Views: 1435

Answers (2)

amir mola
amir mola

Reputation: 405

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.

Upvotes: 0

mm8
mm8

Reputation: 169150

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}" />

Upvotes: 2

Related Questions