Dependency Property reset after setting DataContext - c#

I have a UserControl (AgreementDetails) in WPF with the following DependencyProperty and function:
// UserControl AgreementDetails
public int AgreementID
{
get { return Convert.ToInt32(GetValue(AgreementIDProperty)); }
set { SetValue(AgreementIDProperty, value); }
}
public static readonly DependencyProperty AgreementIDProperty = DependencyProperty.Register("AgreementID", typeof(int), typeof(UC1001_AgreementDetails_View), new PropertyMetadata(null));
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int id = AgreementID;
if (id > 0)
{
GetData();
SetBindingContext();
this.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Visibility = System.Windows.Visibility.Collapsed;
}
}
private void GetData()
{
ConsultantServiceClient client = new ConsultantServiceClient();
_contract = new UC1001_ActiveAgreementContract();
_contract = client.GetAgreementDetailsByAgreementID(AgreementID);
}
private void SetBindingContext()
{
this.DataContext = _contract;
}
I use this UserControl to show as a tooltip in another UserControl (Dashboard) where I set the AgreementID property:
// Dashboard
<Setter Property="DataGridCell.ToolTip">
<Setter.Value>
<my:UC1001_AgreementDetails_View Background="#FFF" Opacity="0.88" AgreementID="{Binding Months[9].AgreementID}"/>
</Setter.Value>
</Setter>
In AgreementDetails, I use the AgreementID to get some data from the database to show in the UserControl. The first time I do this, everything goes smooth. But when I set the incoming WCF DataContract as the datacontext in AgreementDetails, the AgreementID property resets to 0, so the second call will not work because obviously I do not have an agreement with AgreementID = 0. I checked and the AgreementID resets in the SetBindingContext(); method after the DataContext is set.
How can I make it so the AgreementID property will not reset after I set a new dataContext in AgreementDetails??
More information can be provided if wanted.
EDIT: I now have the following code:
// Dependency properties
public int AgreementID
{
get { return (int)GetValue(AgreementIDProperty); }
set { SetValue(AgreementIDProperty, value); }
}
public UC1001_ActiveAgreementContract AgreementDetailsContract
{
get { return (UC1001_ActiveAgreementContract)GetValue(AgreementDetailsContractProperty); }
set { SetValue(AgreementDetailsContractProperty, value); }
}
public static readonly DependencyProperty AgreementIDProperty = DependencyProperty.Register("AgreementID", typeof(int), typeof(UC1001_AgreementDetails_View), new PropertyMetadata(null));
public static readonly DependencyProperty AgreementDetailsContractProperty = DependencyProperty.Register("AgreementDetailsContract", typeof(UC1001_ActiveAgreementContract), typeof(UC1001_AgreementDetails_View), new PropertyMetadata(null));
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int id = AgreementID;
if (id > 0)
{
GetData();
SetBindingContext();
this.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Visibility = System.Windows.Visibility.Collapsed;
}
}
private void GetData()
{
ConsultantServiceClient client = new ConsultantServiceClient();
AgreementDetailsContract = client.GetAgreementDetailsByAgreementID(AgreementID);
}
private void SetBindingContext()
{
this.DataContext = AgreementDetailsContract;
}
I still have the the problem that the AgreementID resets to 0 after the DataContext is set.
Also when I use the following statement to bind, I get an empty label:
<Label Content="{Binding RelativeSource={RelativeSource Self}, Path=AgreementDetailsContract.EndClientName}" />
SOLVED:
I removed the SetDataBinding() method so the Binding doesn't reset my DependencyProperty, and for the Binding of my labels I used the following Binding (instead of RelativeSource Self):
<Label Content="{Binding ElementName=AgreementDetails, Path=AgreementDetailsContract.EndClientName}" Grid.Column="1" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="11,0,0,0" Name="_labelEindklant" VerticalAlignment="Top" />
ElementName=AgreementDetails is the name of my UserControl. Strange enough with {RelativeSource Self} it didn't work...

When you set the datacontext in your Usercontrol, you are actually resetting the data context in the parent control too (Dashboard). It's the same context. Because of this your Agreement ID is no longer in the context and so gets reset.
Edit: Actually I didn't word that very well. You're not affecting the data context in Dashboard, but you ARE affecting the data context used by the AgreementId binding declared in that control. The binding is declared in the Dashboard control, but the binding is actually looking in the data context of the child control, which you are resetting.
See my similar question here:
Setting DataContext within UserControl is affecting bindings in parent
EDIT: Here is what I mean:
// UserControl AgreementDetails
public int AgreementID
{
get { return Convert.ToInt32(GetValue(AgreementIDProperty)); }
set { SetValue(AgreementIDProperty, value); }
}
//The new property to bind to instead of DataContext
public UC1001_ActiveAgreementContract Agreement
{
get { return (UC1001_ActiveAgreementContract)GetValue(AgreementProperty); }
private set { SetValue(AgreementProperty, value); }
}
public static readonly DependencyProperty AgreementIDProperty = DependencyProperty.Register("AgreementID", typeof(int), typeof(UC1001_AgreementDetails_View), new PropertyMetadata(null));
//should really be readonly dependency property
public static readonly DependencyProperty AgreementProperty = DependencyProperty.Register("Agreement", typeof(UC1001_ActiveAgreementContract), typeof(UC1001_AgreementDetails_View), new PropertyMetadata(null));**
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int id = AgreementID;
if (id > 0)
{
GetData();
SetBindingContext();
this.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Visibility = System.Windows.Visibility.Collapsed;
}
}
private void GetData()
{
ConsultantServiceClient client = new ConsultantServiceClient();
_contract = new UC1001_ActiveAgreementContract();
_contract = client.GetAgreementDetailsByAgreementID(AgreementID);
}
private void SetBindingContext()
{
this.Agreement = _contract;
}
Then in your AgreementDetails.xaml, you probably have something like:
<!-- Bound to property in DataContext -->
<TextBlock Text={Binding SomeContractProperty} />
which binding needs to change to:
<!-- Bound to new property on UC1001_AgreementDetails_View (UserControl) -->
<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UC1001_AgreementDetails_View}}, Path=Agreement.SomeContractProperty} />

Related

Binding a property of UserControl with ViewModel programatically

I have a window called SettingsWindow and I have some user controls that can be content of the window. I have a ContentControl and I have a method in view-model that returns new instance of user control to ContentControl's content. I need to set binding properties of user control to view-model programatically.
<Window x:Class="KnitterNotebook.Views.Windows.SettingsWindow"
<Window.Resources>
<viewModels:SettingsViewModel x:Key="SettingsViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource SettingsViewModel}">
<ContentControl Content="{Binding WindowContent, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
public partial class UserSettingsUserControl : UserControl
{
public UserSettingsUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NewNicknameProperty =
DependencyProperty.Register(nameof(NewNickname), typeof(string), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string NewNickname
{
get { return GetValue(NewNicknameProperty).ToString()!; }
set { SetValue(NewNicknameProperty, value); }
}
public static readonly DependencyProperty ChangeNicknameCommandAsyncProperty =
DependencyProperty.Register(nameof(ChangeNicknameCommandAsync), typeof(ICommand), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public ICommand ChangeNicknameCommandAsync
{
get { return (GetValue(ChangeNicknameCommandAsyncProperty) as ICommand)!; }
set { SetValue(ChangeNicknameCommandAsyncProperty, value); }
}
}
public class SettingsViewModel : BaseViewModel
{
public SettingsViewModel()
{
WindowContent = new UserSettingsUserControl();
ChooseSettingsWindowContentCommand = new RelayCommand<Type>(ChooseSettingsWindowContent!);
ChangeNicknameCommandAsync = new AsyncRelayCommand(ChangeNicknameAsync);
}
private string newNickname;
public string NewNickname
{
get { return newNickname; }
set { newNickname = value; OnPropertyChanged(); }
}
public ICommand ChooseSettingsWindowContentCommand { get; private set; }
public ICommand ChangeNicknameCommandAsync { get; set; }
private void ChooseSettingsWindowContent(Type userControl)
{
if (userControl == typeof(UserSettingsUserControl))
{
WindowContent = new UserSettingsUserControl()
{
NewNickname = NewNickname,
ChangeNicknameCommandAsync = ChangeNicknameCommandAsync
};
}
}
Please take a look at private void ChooseSettingsWindowContent(Type userControl). When I use Nickname = Nickname etc., the element is not binded to view-model. I need to set binding programatically. I can't create a new instance of user control in window, because I want to return user control from the method. I read about Binding class and BindingOperations but I still can't solve how to implement it. How can I set bindings programatically in ChooseSettingsWindowContent?

SelectedValue binding of a nested DataGrid doesn't work while ItemsSource does

I have a DataGrid inside of a UserControl which in turn lies inside of another UserControl. This is due to other needs of the project and I can't change this nested architecture. I'm binding a list of Person class to this DataGrid. This is a dumbed-down version without using a VM, but in my real project I am using a VM.
My UserControl with the DataGrid:
<Grid>
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"
MouseDoubleClick="MyDg_MouseDoubleClick"
SelectedValue="{Binding SelectedValue, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Code Behind:
public partial class UCDataGrid : UserControl
{
public event RoutedEventHandler RoutedDataGridDoubleClick;
public UCDataGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
private void MyDg_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
RoutedDataGridDoubleClick?.Invoke(this, new RoutedEventArgs());
}
}
2nd UserControl that contains the above control:
<Grid>
<ContentControl Content="{Binding MyDataGrid, ElementName=ucDisplay}"/>
</Grid>
ucDisplay is simply the Name property value of this UserControl.
Code Behind:
Nothing fancy here.
public partial class UCDisplay : UserControl
{
public UCDisplay()
{
InitializeComponent();
}
public static readonly DependencyProperty MyDataGridProperty = DependencyProperty.Register("MyDataGrid", typeof(object), typeof(UCDisplay), new PropertyMetadata(null));
public object MyDataGrid
{
get { return GetValue(MyDataGridProperty); }
set { SetValue(MyDataGridProperty, value); }
}
}
Main Window
In my Main Window, I bind my People list as well as SelectedPerson instance, like so:
<Grid>
<local:UCDisplay>
<local:UCDisplay.MyDataGrid>
<local:UCDataGrid ItemsSource="{Binding People}"
SelectedValue="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged}"
RoutedDataGridDoubleClick="UCDataGrid_RoutedDataGridDoubleClick"/>
</local:UCDisplay.MyDataGrid>
</local:UCDisplay>
</Grid>
Code Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private List<Person> people;
public List<Person> People
{
get => people;
set => SetField(ref people, value);
}
private Person selectedPerson;
public Person SelectedPerson
{
get => selectedPerson;
set => SetField(ref selectedPerson, value);
}
public MainWindow()
{
InitializeComponent();
People = GetPeople();
DataContext = this;
}
private void UCDataGrid_RoutedDataGridDoubleClick(object sender, RoutedEventArgs e)
{
}
private List<Person> GetPeople()
{
return new List<Person>
{
new Person() { Name = "A" },
new Person() { Name = "B" },
new Person() { Name = "C" }
};
}
public class Person
{
public string Name { get; set; }
}
}
Again, in reality I'm using a VM, this is only to keep things simple.
Now when I run this I can display my list content just fine. But when I double-click an item in my DataGrid, in the corresponding in my Main Window code behind, the SelectedPerson remains null, although its binding is identical to the People list. I confirm this by using a break point in the main code behind:
But if I debug and see the value in the code behind of my innermost UserControl, you see that the SelectedValue there has the correct selected items value.
So what am I doing wrong here? Why can't I seem to bind the SelectedValue although I do it exactly the same as my ItemsSource binding, but the latter works?
SelectedValue is supposed to be used in conjunction with SelectedValuePath. You should use SelectedItem instead.
Besides that, you are missing a TwoWay Binding. Either explicitly declare the SelectedItem Binding TwoWay
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem,
RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}"/>
or register the property to bind TwoWay by default:
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem), typeof(object), typeof(UCDataGrid),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Also note that setting UpdateSourceTrigger=PropertyChanged is pointless in all your Bindings.

Wpf Textbox UpdateSourceTrigger doesn't update the source

I have a simple Window with a TextBox
XAML
<Window x:Class="Configurator.ConfiguratorWindow"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</Window>
in the code behind
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata());
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set {
SetValue(DescriptionProperty, value);
_actual_monitor.Description = value;
}
}
}
the graphic is updating right, but when i change the text in the textbox and lose focus it doesn't update the source property.
What is wrong?
DependencyProperties are used for UserControls rather than ViewModel type bindings.
You should
Create a ConfigurationWindowViewModel (Read about MVVM) and implement INotifyPropertyChanged
Create a Property Description that utilizes the INotifyPropertyChanged
Create a new instance of that view model to be set to the DataContext of your ConfigurationWindow.
The getter and setter of the CLR wrapper of a dependency property must not contain any other code than GetValue and SetValue. The reason is explained in the XAML Loading and Dependency Properties article on MSDN.
So remove the _actual_monitor.Description = value; assignment from the setter and add a PropertyChangedCallback to react on property value changes:
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(ConfiguratorWindow),
new PropertyMetadata(DescriptionPropertyChanged));
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
private static void DescriptionPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ConfiguratorWindow obj = d as ConfiguratorWindow;
obj._actual_monitor.Text = (string)e.newValue;
}
}
Try this
<Window x:Class="Configurator.ConfiguratorWindow"
xmlns:myWindow="clr-namespace:YourNamespace"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type myWindow}}, Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata(null, CallBack);
private static void callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var foo = d as ConfiguratorWindow ;
all you need to do, you can do here
}
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set { SetValue(DescriptionProperty, value);}
}
}
But it would be much easier to just have a View Model and bind to property there.

WPF Custom Control's Dependency Property unintentionally affecting other controls

I am making a window which is supposed to record the mouse position. I have created a user control which is supposed to display the current coordinates for that position tag and allow the user to change them.
It works great except for the fact that when one is updated they all change.
I think this has something to do with the fact that dependency properties are statically registered. They need to be dependency properties because I need to be able to Bind them to my model from the xaml.
How can I have the User Controls independent from one another?
<UserControl x:Class="SapFormFiller.SerializableMouseEditorControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60*"/>
...
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding LabelText}"></Label>
<Label Grid.Column="1" Content="{Binding SerializableMouseKeyboardEventArgs.X}"/>
<Label Grid.Column="2" Content="{Binding SerializableMouseKeyboardEventArgs.Y}"/>
<Button Grid.Column="3" Margin="0,0,0.4,0" Click="ButtonBase_OnClick">Edit</Button>
</Grid>
cs:
public partial class SerializableMouseEditorControl : UserControl
{
public static DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof (string), typeof (SerializableMouseEditorControl), new PropertyMetadata(default(string)));
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static DependencyProperty SerializableMouseKeyboardEventArgsProperty = DependencyProperty.Register(
"SerializableMouseKeyboardEventArgs", typeof (SerializableMouseKeyboardEventArgs), typeof (SerializableMouseEditorControl), new PropertyMetadata(new SerializableMouseKeyboardEventArgs()));
public SerializableMouseKeyboardEventArgs SerializableMouseKeyboardEventArgs
{
get { return (SerializableMouseKeyboardEventArgs) GetValue(SerializableMouseKeyboardEventArgsProperty); }
set { SetValue(SerializableMouseKeyboardEventArgsProperty, value); }
}
public SerializableMouseEditorControl()
{
InitializeComponent();
SerializableMouseKeyboardEventArgs = new SerializableMouseKeyboardEventArgs();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
SerializableMouseKeyboardEventArgs.Update();
}
}
SerializableMouseKeyboardEventArgs:
public class SerializableMouseKeyboardEventArgs : INotifyPropertyChanged
{
public int X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
}
}
public int Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
}
}
...
IKeyboardMouseEvents gkme;
private int _x = 0;
private int _y=0;
public override string ToString(){...}
public SerializableMouseKeyboardEventArgs()
{
gkme = Hook.GlobalEvents();
}
public void Update()
{
IsEditing = true;
gkme.MouseClick += Gkme_MouseClick;
}
private void Gkme_MouseClick(object sender, MouseEventArgs e)
{
if(e.Button==MouseButtons.Left)
{
this.X = e.X;
this.Y = e.Y;
}
else if (e.Button == MouseButtons.Right)
{
gkme.MouseClick -= Gkme_MouseClick;
IsEditing = false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This line
new PropertyMetadata(new SerializableMouseKeyboardEventArgs()));
looks like it could be causing you problems. I was in a situation where declaring default values using new keyword was causing one instance of the UserControl to share that property value with another instance.
Try initializing any such properties in your constructor and it might work.

Binding a collection to a custom control property

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 DependencyPropertys 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 i.e. 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:
Be able to define a collection of any kind of items
Be informed when an item was added/removed and so on.
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);
}

Categories