Reputation: 405
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
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
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