I am having no luck trying to bind a collection of data to my custom control its property. I already have implemented the mechanism for a string property of this control (with a small help from here) and expected the collection type to be as easy. However I cannot make it work again.
Here is my custom control view
<UserControl x:Class="BadaniaOperacyjne.Controls.Matrix"
mc:Ignorable="d" Name="CustomMatrix"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<!-- ... -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ElementName=CustomMatrix, Path=Title}"/>
<Grid Grid.Row="2" Grid.Column="1" Name="contentGrid">
<ListBox ItemsSource="{Binding ElementName=CustomMatrix, Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</UserControl>
and its code-behind
#region ItemsList Property
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(ObservableCollection<object>), typeof(Matrix), new PropertyMetadata(new ObservableCollection<object>(), new PropertyChangedCallback(ItemsListChanged)));
public ObservableCollection<object> ItemsList
{
get { return GetValue(ItemsListProperty) as ObservableCollection<object>; }
set { SetValue(ItemsListProperty, value); }
}
private void ItemsListChanged(object value)
{
System.Diagnostics.Debug.WriteLine("matrix: items list changed " + value);
if (ItemsList != null)
{
ItemsList.CollectionChanged += ItemsList_CollectionChanged;
System.Diagnostics.Debug.WriteLine("got " + string.Join(",", ItemsList.ToList()));
}
else
{
System.Diagnostics.Debug.WriteLine("got null");
}
}
void ItemsList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("matrix: current items list collection changed");
}
private static void ItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// a breakpoint
((Matrix)d).ItemsListChanged(e.NewValue);
}
#endregion
// showing the Title property implementation just to state that
// it is done the same way as for ItemsList
#region Title Property
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(Matrix), new PropertyMetadata("", new PropertyChangedCallback(TitleChanged)));
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
private void TitleChanged(string title)
{
System.Diagnostics.Debug.WriteLine("matrix: title changed to: " + title);
}
private static void TitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Matrix)d).TitleChanged((string)e.NewValue);
}
#endregion
And here's how I am trying to bind to that control
<custom:Matrix x:Name="customMatrix" DockPanel.Dock="Top" Title="{Binding Title}" ItemsList="{Binding Items}"/>
and the code-behind for the main page is
//internal ObservableCollection<List<int>> ItemsList { get; set; }
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Items = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6};
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("problem manager: items list changed " + e.NewItems.Count);
}
public ObservableCollection<int> Items { get; set; }
protected string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("Title");
}
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public ViewModel VM { get; private set; }
// this is the window constructor
private ProblemManager()
{
VM = new ViewModel();
DataContext = VM;
InitializeComponent();
VM.Title = "title";
}
private int i = 0;
private void btnAddRow_Click(object sender, RoutedEventArgs e)
{
// when doing either of these two lines below,
// the control breakpoint is never hit
VM.Items.Add(++i);
VM.Items = new ObservableCollection<int> { 2, 3 };
// however, when directly assigning to the control's property,
// the event is raised and the breakpoint is hit and the UI is updated
customMatrix.ItemsList = new ObservableCollection<object> { 1, 2, 3 };
customMatrix.ItemsList.Add(66);
// and this of course makes the control's title update
VM.Title = (++i).ToString();
}
Both DependencyProperty
s for the control's Title
and ItemsList
are, I believe, created the same way. Nonetheless, the binding is probably not working as the ItemsListChanged
event is not raised by that binding. So, the problem is that I cannot bind my window's ViewModel.Items
collection via XAML to the control's ItemsList
collection. Is creating a DependencyProperty
for a collection within a control any different from DependencyProperty
for a simple string
property?
Problem is in your DependencyProperty
registration. Co-variance is not applicable for generic lists
ie you cannot do this -
ObservableCollection<object> objects = new ObservableCollection<int>();
You have declared type of DP as ObservableCollection<object>
but binding it with list of type ObservableCollection<int>
.
You should change either type of DP to ObservableCollection<int>
OR change binding collection type to ObservableCollection<object>
.
public ViewModel()
{
Items = new ObservableCollection<object> { 1, 2, 3, 4, 5, 6};
}
public ObservableCollection<object> Items { get; set; }
After such a long time, I kind of needed this to work in both ways:
I am working on a bread crumb like control to be more precise.
Indeed, the ItemsSource DP is defined with an IList type, to allow the "genericity" aforementioned:
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(BreadCrumbUserControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
And here is how I use my BreadCrumb:
<views:BreadCrumbUserControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
where Items is an ObservableCollection of a custom type:
public ObservableCollection<BaseItem> Items { get; set; }
Indeed it works fine, my ItemsSource local property points to the right collection but I need to be informed in my UserControl when an item is added to the ObservableCollection.
So, I've made use of the PropertyChangedCallback delegate on my ItemsSource DP.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BreadCrumbUserControl thisUserControl = (BreadCrumbUserControl)d;
(e.NewValue as INotifyCollectionChanged).CollectionChanged += thisUserControl.BreadCrumbUserControl_CollectionChanged;
}
This is the trick that worked for me, because my input reference is in fact the ObservableCollection defined in the upper layers.
Both goals are now achieved: work with collection of any custom types and in the same type, stay informed.
If a protocol is set, reflection can be used to change values on our custom type.
Here is just an example of iterating on my collection which has some unknown type in my UserControl's world:
foreach (var baseItem in ItemsSource)
{
baseItem.GetType().GetProperty("IsActive").SetValue(baseItem, false);
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.