ListView not updating when ObservableCollection is replaced - c#

I can not understand why the ListView is not refreshing when the ObservableCollection is replaced (with new one) and not changed (added or removed items).
I respected all requirements for property notifications, since I'm using a DependencyObject for my view model, and SetValue is called when the collection is replaced .
I have a WPF ListView bound to a Col property of my view model:
public class ViewModel1 : DependencyObject
{
public ViewModel1()
{
Col = new ObservableCollection<string>(new[] { "A", "B", "C", "D" });
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
Debug.WriteLine("Property changed "+ e.Property.Name);
}
public ObservableCollection<string> Col
{
get { return (ObservableCollection<string>)GetValue(ColProperty); }
set { SetValue(ColProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColProperty =
DependencyProperty.Register("ColProperty", typeof(ObservableCollection<string>), typeof(ViewModel1), new PropertyMetadata(null));
}
The XAML is like:
<Window x:Class="BindingPOC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<ListView Margin="0,10,0,0" ItemsSource="{Binding Col}" />
<Button Click="Button_Click" >click</Button>
</StackPanel>
</Grid>
</Window>
So with this code, everything work fine if I don't replace the initial ObservableCollection.
But when I click on the button. I replace the list with:
private void Button_Click(object sender, RoutedEventArgs e)
{
(DataContext as ViewModel1).Col = new System.Collections.ObjectModel.ObservableCollection<string>(new[] { "Z", "ZZ" });
}
The PropertyChanged method on the view model is called for Col, but the ListView is not updating its content.
Do I need to conserve the same ObservableCollection reference ? why ?

This is because your dependency property registration is incorrect. The name of the property passed to the Register method should be "Col", not "ColProperty":
public static readonly DependencyProperty ColProperty =
DependencyProperty.Register("Col", typeof(ObservableCollection<string>), typeof(ViewModel1), new PropertyMetadata(null));
The initial binding works because there is a property named Col, but it is not detected as a dependency property, so the binding is not automatically updated.

Related

Curious behavior with ComboBox in UWP - SelectedItem & ItemsSource

hope you're all fine.
I'm encountering 2issues with a ComboBox in a UWP application.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly. I only have the 2, 3 or 4 first items... depending on the time. No problem when the same collection is bound to a DataGrid so I think my collection is built correctly. As a workaround (code-behind), I first load my collection (in a Task) and set the ItemsSource property when the task is completed. This solution works but I'd like to do less things code-behind.
The binding on the property SelectedItem seems to work with ReferenceEquals only, the type of item in my collection implements Equals based on IDs and it has been tested separately and successfylly in a console app. As a workaround (code-behind), once my list is loaded, I change the property bound to SelectedItem like this:
Users.TaskFill.ContinueWith(t => BaseItemCollection.UserInterfaceAction.Invoke(() =>
{
if (Item?.Manager != null) Item.Manager = t.Result.FirstOrDefault(i => i.Equals(Manager));
ComboBoxManager.SetBinding(ComboBox.ItemsSourceProperty, this, "Users", BindingMode.TwoWay);
ComboBoxManager.SetBinding(Selector.SelectedItemProperty, "Manager", BindingMode.TwoWay);
}));
Users is my collection (filled asynchronously) used as source for the ComboBox
SetBinding is a custom extension method I've created myself to set bindings code-behind from a single-line (as follow):
public static class ExtensionMethods
{
#region DependencyObject
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, object source, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Source = source,
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
#endregion
}
How can I get this working from XAML withtout needing these workarounds? I has been able to get a similar configuration working with WPF for years but am really struggling with UWP...
Thank you in advance for your help.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly.
If your collection is not string, you need specify DisplayMemberPath, please check the following code. And please check the collection has value. For my testing collection that implements INotifyPropertyCollectionChanged works for ComboBox.
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding MyItems}"
PlaceholderText="Select Country ..."
/>
Curious behavior with ComboBox
The default ItemsPanelTemplate of ComboBox is CuriousPanel that could implement scroll loop within touch device. If you don't want to use it, you could replace it with StackPanel
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
The binding on the property SelectedItem seems to work with ReferenceEquals only,
The SelectedItem is not ComboBox display field, it is an complete User object. You could get the select user in the SelectedItem binding property set method. the following is complete code that you could refer.
public sealed partial class TestPage : Page, INotifyPropertyChanged
{
private User _selecteduser;
public TestPage()
{
this.InitializeComponent();
_myItems = new ObservableCollection<User>
{
new User{UserId=1,FirstName="Fay",LastName="Wang",City="Delhi",State="DEL",Country="INDIA"},
new User{UserId=2,FirstName="Mark",LastName="Liu",City="New York", State="NY", Country="USA"},
new User{UserId=3,FirstName="Rich",LastName="Cai",City="Philadelphia", State="PHL", Country="USA"},
new User{UserId=4,FirstName="Eveia",LastName="Dong",City="Noida", State="UP", Country="CANADA"}}
};
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)//string propertyName
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, args);
}
}
public ObservableCollection<User> Users
{
get
{ return _myItems; }
set
{
_myItems = value;
OnPropertyChanged("Users");
}
}
private ObservableCollection<User> _myItems;
public User SelectedUser
{
get
{
return _selecteduser;
}
set
{
_selecteduser = value;
OnPropertyChanged("SelectedUser");
}
}
}
Xaml
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding Users}"
PlaceholderText="Select User..."
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
/>

Generate custom item control from Dependency Property as Item Source in UserControl

I'm making a UserControl to generate a list of attached files from Dependency Property as ItemSource. But the ItemSource (DependencyProperty) count is 0.
I tried debugging and realized that the ObservableCollection in ViewModel was bound after the Constructor of my UserControl is initialized.
I'm coding in MVVM pattern, I made a function to prepare some sample data for ObservableCollection in ViewModel and inside the MainWindow I bound the DataContext of my UserControl with that ViewModel then set the ItemSource for ObservableCollection
My ViewModel code-behind:
//The properties
ObservableCollection<FileAttachmentModel> filesAttachment;
public ObservableCollection<FileAttachmentModel> FilesAttachment
{
get { return filesAttachment; }
set { filesAttachment = value; OnPropertyChanged("FilesAttachment"); }
}
//The function prepare sample data
private ObservableCollection<FileAttachmentModel> PrepareData()
{
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackA", FilePath = "D:\trackA.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackB", FilePath = "D:\trackB.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackC", FilePath = "D:\trackC.png" });
}
My UserControl xaml:
<UserControl x:Class="MailSender.Controls.FileAttachment.FileAttachment"
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:MailSender.Controls.FileAttachment"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Name="fileAttachmentUC"
>
<Grid>
<WrapPanel DataContext="{Binding ElementName=fileAttachmentUC,Path=DataContext,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" x:Name="wrapPanel">
</WrapPanel>
</Grid>
</UserControl>
My UserControl code-behind:
//the property ItemSource
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource", typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment),new UIPropertyMetadata());
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value);
}
}
//the function to generate each file attachment and add them to the Wrappanel in UserControl
//I call this function inside constructor of UserControl and pass ItemSource as parameter
void GenerateFileItem(ObservableCollection<FileAttachmentModel> lstFileAttachment)
{
if (lstFileAttachment != null && lstFileAttachment.Count>0)
{
foreach (var item in lstFileAttachment)
{
StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
TextBlock tbFileName = new TextBlock() { Text = item.FileName };
Button btFilePath = new Button() { Content = "X", Tag = item.FilePath };
btFilePath.Click += BtFilePath_Click;
sp.Children.Add(tbFileName);
sp.Children.Add(btFilePath);
sp.Style = Application.Current.FindResource("stackFileItem") as Style;
wrapPanel.Children.Add(sp);
}
}
}
In Usage:
<control:FileAttachment DataContext="{StaticResource vmMainWindow}" ItemSource="{Binding FilesAttachment,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
What I expect is to make a container for attached files like Outlook of Microsoft. Please help!
Thanks in advance!
You should call GenerateFileItem whenever the dependency property is set using a PropertyChangedCallback:
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource",
typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment), new PropertyMetadata(new PropertyChangedCallback(OnChanged));
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value); }
}
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FileAttachment fa = (FileAttachment)d;
fa.GenerateFileItem(fa.ItemSource);
}
The ItemSource property cannot be set before the UserControl has been initialized.
What I was facing is the ObservableCollection I generate in ViewModel is initialized after the UserControl is initialized. The #mm8 solution has fixed my problem by waiting for the ObservableCollection in ViewModel is initialized first and then pass it by the property I bound in MainWindow. Then the UserControl will be initialized and get the ObservableCollection that is passed from ViewModel then generate the custom controls inside my "wrapPanel".

WPF DataContext is set before properties that affect representation of DataContext are set

I have a UserControl that represents my custom DataContext to the user. This control also has a DependencyProperty (with a PropertyChangedCallback) that affects the way the DataContext is shown to the user.
My custom UserControl XAML:
<UserControl x:Class="WpfApplication1.MyControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<TextBox Text="{Binding FinalText,ElementName=Me}"/>
</UserControl>
My custom UserControl code behind:
using System.Diagnostics;
using System.Windows;
namespace WpfApplication1
{
public partial class MyControl
{
#region Static Fields and Constants
public static readonly DependencyProperty CapitalizeProperty = DependencyProperty.Register(nameof(Capitalize), typeof(bool),
typeof(MyControl), new PropertyMetadata(default(bool), CapitalizePropertyChanged));
public static readonly DependencyProperty FinalTextProperty =
DependencyProperty.Register(nameof(FinalText), typeof(string), typeof(MyControl), new PropertyMetadata(null));
#endregion
#region Properties and Indexers
public bool Capitalize
{
get => (bool)GetValue(CapitalizeProperty);
set => SetValue(CapitalizeProperty, value);
}
public string FinalText
{
get => (string)GetValue(FinalTextProperty);
set
{
Debug.WriteLine($"Setting {nameof(FinalText)} to value {value}");
SetValue(FinalTextProperty, value);
}
}
#endregion
#region Constructors
public MyControl()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
#endregion
#region Private members
private static void CapitalizePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MyControl me)
me.CreateFinalText(me.DataContext as string);
}
private void CreateFinalText(string text)
{
if (text != null)
{
FinalText = Capitalize ? text.ToUpperInvariant() : text.ToLowerInvariant();
}
else
{
FinalText = null;
}
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs args)
{
CreateFinalText(args.NewValue as string);
}
#endregion
}
}
When I use my UserControl in the following way:
<Grid>
<local:MyControl DataContext="Simple string" Capitalize="True"/>
</Grid>
My debug output shows the following:
Setting FinalText to value simple string
Setting FinalText to value SIMPLE STRING
I was wondering if it's possible to have the DependencyProperty Capitalize set before the DataContext is set? That way the FinalText property isn't set twice.
To complicate my issue a bit further, my actual UserControl needs to support rendering to an Image without being attached to a Window, meaning the Loaded event doesn't always trigger.
I could add a DependencyProperty that is being used instead of the DataContext, but then there's still no way to ensure that this new DependencyProperty is filled after all my other DependencyProperties are filled (Capitalize in the example)
Edit:
As pointed out in the comments, using the DataContext isn't recommended, and I should instead use another Property to set what needs to be rendered. That's fine and easy to do, however it still isn't guaranteed that this new Property is parsed after all the other Properties are parsed.
I guess the question could be reformulated to: How to detect if a UserControl has been fully parsed from XAML?
I was wondering if it's possible to have the DependencyProperty Capitalize set before the DataContext is set?
Try to change the order, i.e. set the Capitalize property before you set the DataContext property:
<local:MyControl Capitalize="True" DataContext="Simple string" />

Cannot bind to a collection from ItemsControl template

I'm trying to bind a user control property "MyUserControl.Names" to a collection property "Names" of the main window. It doesn't work if I do it in ItemsControl template, but it works if I move the control definition out of the ItemsControl template. Here is the xaml:
<Window x:Class="TestItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestItemsControl"
Height="200" Width="200"
Name="MainControl">
<StackPanel>
<ItemsControl ItemsSource="{Binding Groups, ElementName=MainControl}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- This doesn't work -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="{Binding .}"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- This works -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="Group3"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</StackPanel>
</Window>
MainWindow.xaml.cs contains two dependency properties:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SetValue(GroupsProperty, new ObservableCollection<string>());
SetValue(NamesProperty, new ObservableCollection<string>());
Groups.Add("Group1");
Groups.Add("Group2");
Names.Add("Name1");
Names.Add("Name2");
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Groups
{
get { return (ObservableCollection<string>)GetValue(GroupsProperty); }
set { SetValue(GroupsProperty, value); }
}
public static readonly DependencyProperty NamesProperty =
DependencyProperty.Register("Names", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
}
Here is the result:
The first two rectangles are what ItemsControl generates. The third one is what I have manually added right after the ItemsControl. As you can see, even though the code is exactly the same in both cases, the first two rectangles don't have names, but the third one has. Is there any reason why wouldn't it work with ItemsControl?
Edit:
Here is the code of the MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
SetValue(NamesProperty, new ObservableCollection<string>());
}
public static readonly DependencyProperty NamesProperty = DependencyProperty.Register(
"Names", typeof(ObservableCollection<string>), typeof(MyUserControl),
new PropertyMetadata(null, NamesPropertyChanged));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
private static void NamesPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
oldCollection.CollectionChanged -= control.NamesCollectionChanged;
if (newCollection != null)
newCollection.CollectionChanged += control.NamesCollectionChanged;
control.UpdateNames();
}
private void NamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateNames();
}
private void UpdateNames()
{
NamesPanel.Children.Clear();
if (Names == null)
return;
foreach(var name in Names)
{
var textBlock = new TextBlock();
textBlock.Text = name + ", ";
NamesPanel.Children.Add(textBlock);
}
}
}
MyUserControl.xaml:
<UserControl x:Class="TestItemsControl.MyUserControl"
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:TestItemsControl"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="ParentControl">
<StackPanel Name="NamesPanel" Orientation="Horizontal"/>
</UserControl>
Replace SetValue in the UserControl's constructor by SetCurrentValue. It may even make sense not to assign an initial value at all for the Names property.
public MyUserControl()
{
InitializeComponent();
SetCurrentValue(NamesProperty, new ObservableCollection<string>());
}
SetValue (as opposed to SetCurrentValue) sets a so-called local value to the Names property. When you assign a Binding as in the second case, this is also considered a local value with the same precedence as the one set in the constructor.
However, in the first case, the Binding is set in a DataTemplate, where it doesn't count as a local value. Since it has lower precedence, it does not replace the initial value.
More details here: Dependency Property Value Precedence

C# VS : Factoring code into UserControl, using ObservableCollection, and consuming it with Binding

I am factoring some code into UserControls which parameters are bound when consumed. I am meeting difficulties with the use of ObservableCollection as a DependencyProperty.
The example showing the difficulty is a project consisting in a MainWindow with two DependencyProperty:
one representing a String (named "Data") and
another one representing an ObservableCollection (named "Origin");
and a UserControl (named UserControl1) exposing two similar DependencyProperty (named resp. "Liste" and "Noun").
The MainWindow contains a TextBlock which Text is bound to "Data" and a ComboBox which ItemsSource is bound to "Origin". Both are working fine.
Both controls are factored into UserControl1, with the DependencyProperty "Liste" and "Noun" acting as intermediate, and UserControl1 is consumed in MainWindow.
Each DataContext (of MainWindow and of UserControl1) is set to "this".
The trouble is while the factored TextBlock (within UserControl1) is working and showing the content of "Data", the factored ComboBox is not working and its DropDown is empty.
The code of MainWindow.xaml is:
<Window x:Class="ChainedBindingUserControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350" Width="525"
xmlns:Local="clr-namespace:ChainedBindingUserControl"
>
<StackPanel>
<TextBlock Text="{Binding Data}"
Width="150"
/>
<ComboBox ItemsSource="{Binding Origin}"
Width="150"
/>
<Label Content="--------------------------------------------------"
Width="200"
/>
<Local:UserControl1 Liste="{Binding Origin}"
Noun="{Binding Data}"
Height="50" Width="150"
/>
</StackPanel>
</Window>
Its code behind is :
namespace ChainedBindingUserControl
{
public partial class MainWindow : Window
{
public ObservableCollection<String> Origin
{
get { return (ObservableCollection<String>)GetValue(OriginProperty); }
set { SetValue(OriginProperty, value); }
}
public static readonly DependencyProperty OriginProperty =
DependencyProperty.Register("Origin", typeof(ObservableCollection<String>), typeof(MainWindow),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public String Data
{
get { return (String)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("Blablabla", FrameworkPropertyMetadataOptions.AffectsRender));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ObservableCollection<String> zog = new ObservableCollection<String>();
zog.Add("A");
zog.Add("B");
zog.Add("C");
Origin = zog;
}
}
}
The file UserControl1.xaml is :
<UserControl x:Class="ChainedBindingUserControl.UserControl1"
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"
mc:Ignorable="d"
Name="root"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBlock Text="{Binding Noun}"
/>
<ComboBox ItemsSource="{Binding Liste}"
/>
</StackPanel>
</UserControl>
Its code behind is :
namespace ChainedBindingUserControl
{
public partial class UserControl1 : UserControl
{
public ObservableCollection<String> Liste
{
get { return (ObservableCollection<String>)GetValue(ListeProperty); }
set { SetValue(ListeProperty, value); }
}
public static readonly DependencyProperty ListeProperty =
DependencyProperty.Register("Liste", typeof(ObservableCollection<String>), typeof(UserControl1),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public String Noun
{
get { return (String)GetValue(NounProperty); }
set { SetValue(NounProperty, value); }
}
public static readonly DependencyProperty NounProperty =
DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
}
}
}
`
EDIT
According to the pieces of information and snippets provided on http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/ , I changed the code behind of UserControl1 into
public partial class UserControl1 : UserControl
{
public IList Liste
{
get { return (List<String>)GetValue(ListeProperty); }
set { SetValue(ListeProperty, value); }
}
public static readonly DependencyProperty ListeProperty =
DependencyProperty.Register("Liste", typeof(IList), typeof(UserControl1),
new FrameworkPropertyMetadata(new List<String>(), FrameworkPropertyMetadataOptions.AffectsRender));
public String Noun
{
get { return (String)GetValue(NounProperty); }
set { SetValue(NounProperty, value); }
}
public static readonly DependencyProperty NounProperty =
DependencyProperty.Register("Noun", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender));
public UserControl1()
{
InitializeComponent();
this.DataContext = this;
SetValue(ListeProperty, new List<String>());
}
}
but it is still not working.
The trouble doesn't come from the DataContext since the TextBlock works as expected.
The trouble here is specific: why a DependecyProperty acting as an intermediate for Binding is working when the property is of type String while it doesn't work when it is of type ObservableCollection (or List, etc).
Thanks in advance for any explanation.
Your problem is in the UserControl's xaml, here:
<TextBlock Text="{Binding Noun}"
/>
<ComboBox ItemsSource="{Binding Liste}"
/>
These binding expressions are attempting to locate Noun and Liste properties on the DataContext of your UserControl, not on the UserControl itself. You need to specify a different target. Since you've already named your UserControl element, you can replace the bindings with this:
<TextBlock Text="{Binding ElementName=root, Path=Noun}"
/>
<ComboBox ItemsSource="{Binding ElementName=root, Path=Liste}"
/>
Imagine that you are creating control that has property that accepts collection:
public class CustomControl : Control
{
public IEnumerable<string> Items { get; set; }
}
If you want property Items to act as binding target you must change it to be dependency property:
public class CustomControl : Control
{
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable<string>), typeof (CustomControl), new PropertyMetadata(new List<string>()));
public IEnumerable<string> Items
{
get { return (IEnumerable<string>) GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
As you can see, we changed this property to dependency property and supplied new instance of List class as default parameter. As it turned out, this default value will be used on class level (i.e. it will be created only once and each instance of CustomControl will have reference to the same collection). Therefore, we need one modification:
public class CustomControl : Control
{
public CustomControl()
{
Items = new List<string>();
}
}
Now you can use this control and supply value for Items property via binding:
<Grid>
<DependencyPropertiesCollection:CustomControl Items="{Binding ItemsSource}"/>
</Grid>
Currently this control has one limitation – Items property can’t be filled directly in XAML like this code does:
<Grid>
<DependencyPropertiesCollection:CustomControl>
<DependencyPropertiesCollection:CustomControl.Items>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
<System:String>Item 4</System:String>
<System:String>Item 5</System:String>
</DependencyPropertiesCollection:CustomControl.Items>
</DependencyPropertiesCollection:CustomControl>
</Grid>
To fix this, you need to change property type from IEnumerable to IList:
public class CustomControl : Control
{
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof (IList), typeof (CustomControl), new PropertyMetadata(new List<string>()));
public IList Items
{
get { return (IList)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public CustomControl()
{
Items = new List<string>();
}
}
Credits:-
http://sshumakov.com/2012/11/13/how-to-create-dependency-properties-for-collections/

Categories

Resources