Dependency property get value returning null - c#

I have a dependency property in a user control
public List<Exclusion> SelectedExclusions
{
get { return (List<Exclusion>)GetValue(SelectedExclusionsProperty); }
set { SetValue(SelectedExclusionsProperty, value); }
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions), typeof(List<Exclusion>), typeof(TimeSeriesChart), new PropertyMetadata(new List<Exclusion>()));
The list is populated on delete key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.Exclusions[this.Index]);
}
}
}
In the view model I new up a list & public property. A delete command is invoked from the setter:
private IList<Exclusion> selectedExclusionsToDelete = new List<Exclusion>();
public IList<Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
//Delete the selected exclusion
ExecuteDeleteSelectedExclusionsCommand();
this.RaisePropertyChanged();
}
}
Finally, my xaml binding in the view:
SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
My problem is that at run time the SelectedExclusions dp getter returns null instead of a List<Exclusion> Is there something I'm missing here given that the PropertyMetadata value is of type List<Exclusion>?

Related

c# WPF MVVM Set View Property From ViewModel

I want to create a simple Control to enter Text.
For this Control i want to create a Property.
This Property should be bind to the ViewModel.
I Created a Model for an Folder:
public class FolderModel : ModelBase
{
private string fullPath;
public string FullPath
{
get { return fullPath; }
set
{
if (fullPath == value)
return;
fullPath = value;
RaisePropertyChanged(nameof(FullPath));
}
}
}
In my View xaml i bound the Data to my ViewModel:
<UserControl.DataContext>
<Browser:FolderBrowserViewModel/>
</UserControl.DataContext>
<Grid>
<TextBox x:Name="TextBox_Folder" IsReadOnly="True" Text="{Binding Folder.FullPath}"/>
</Grid>
To Create the Property created this code in the View:
public string FullPath
{
get { return (string)GetValue(FolderFullPathProperty); }
set { SetValue(FolderFullPathProperty, value); }
}
public static readonly DependencyProperty FolderFullPathProperty = DependencyProperty.Register("FullPath", typeof(string), typeof(FolderBrowser), new PropertyMetadata("", new PropertyChangedCallback(OnSetTextChanged)));
private static void OnSetTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FolderBrowser UserControl1Control = d as FolderBrowser;
UserControl1Control.OnSetTextChanged(e);
}
private void OnSetTextChanged(DependencyPropertyChangedEventArgs e)
{
TextBox_Folder.Text = e.NewValue.ToString();
}
My ViewModel looks like this:
private ICommand _browseFolderCommand;
public ICommand BrowseFolderCommand
{
get
{
return _browseFolderCommand is null ? (_browseFolderCommand = new RelayCommand(() => BrowseFolder(), true)) : _browseFolderCommand;
}
}
private FolderModel folder = new FolderModel();
public FolderModel Folder
{
get { return folder; }
set
{
if (folder == value)
return;
folder = value;
}
}
private void BrowseFolder()
{
System.Windows.Forms.FolderBrowserDialog fbd = new System.Windows.Forms.FolderBrowserDialog();
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
SetField(ref folder, Folder, "FullPath");
}
}
and my ViewModelBase looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> 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;
}
protected void Dispatch(Action f) => Application.Current.Dispatcher.Invoke(f);
protected TResult Dispatch<TResult>(Func<TResult> f) => Application.Current.Dispatcher.Invoke(f);
}
if i use the Control i want to set the Property "FullPath"
when i set this Property the Value should be set to the ViewModels Folder.FullPath.
If this is set, the Binding of the TextBox and Folder.FullPath should show the correct path.
if its possible i want all Code in the ViewModel =)
Edit:
I tried to illustrate it a bit more pictorially:
On the left side you can see the control as it is placed on a window. There you can set MyProp to any value and the TextBox will receive it.
On the right side I have tried to show it in more detail.
The view has the label, a TextBox and a button.
The view also has the property "MyProp".
The textbox of the view is bound to the ViewModel, namely to the field "MyCoolFieldValue".
This means that if I do anything in the ViewModel with the field MyCoolFieldValue, I know that it will always have the value that is in the textbox.
If I now press the button, a command is called. This command changes the WErt of MyCoolFieldValue. When this happens, the value should be written directly back into the property and the textbox of the view.
However, I can't get this to work and have tried it with the code above.

why my SelectedItems dependency property always returns null to bound property

I created a UserControl1 that wraps a DataGrid (this is simplified for test purposes, the real scenario involves a third-party control but the issue is the same). The UserControl1 is used in the MainWindow of the test app like so:
<test:UserControl1 ItemsSource="{Binding People,Mode=OneWay,ElementName=Self}"
SelectedItems="{Binding SelectedPeople, Mode=TwoWay, ElementName=Self}"/>
Everything works as expected except that when a row is selected in the DataGrid, the SelectedPeople property is always set to null.
The row selection flow is roughly: UserControl1.DataGrid -> UserControl1.DataGrid_OnSelectionChanged -> UserControl1.SelectedItems -> MainWindow.SelectedPeople
Debugging shows the IList with the selected item from the DataGrid is being passed to the SetValue call of the SelectedItems dependency property. But when the SelectedPeople setter is subsequently called (as part of the binding process) the value passed to it is always null.
Here's the relevant UserControl1 XAML:
<Grid>
<DataGrid x:Name="dataGrid" SelectionChanged="DataGrid_OnSelectionChanged" />
</Grid>
In the code-behind of UserControl1 are the following definitions for the SelectedItems dependency properties and the DataGrid SelectionChanged handler:
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(UserControl1), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
private bool _isUpdatingSelectedItems;
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as UserControl1;
if ((ctrl != null) && !ctrl._isUpdatingSelectedItems)
{
ctrl._isUpdatingSelectedItems = true;
try
{
ctrl.dataGrid.SelectedItems.Clear();
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
var validSelectedItems = selectedItems.Cast<object>().Where(item => ctrl.ItemsSource.Contains(item) && !ctrl.dataGrid.SelectedItems.Contains(item)).ToList();
validSelectedItems.ForEach(item => ctrl.dataGrid.SelectedItems.Add(item));
}
}
finally
{
ctrl._isUpdatingSelectedItems = false;
}
}
}
private void DataGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isUpdatingSelectedItems && sender is DataGrid)
{
_isUpdatingSelectedItems = true;
try
{
var x = dataGrid.SelectedItems;
SelectedItems = new List<object>(x.Cast<object>());
}
finally
{
_isUpdatingSelectedItems = false;
}
}
}
Here is definition of SomePeople from MainWindow code-behind:
private ObservableCollection<Person> _selectedPeople;
public ObservableCollection<Person> SelectedPeople
{
get { return _selectedPeople; }
set { SetProperty(ref _selectedPeople, value); }
}
public class Person
{
public Person(string first, string last)
{
First = first;
Last = last;
}
public string First { get; set; }
public string Last { get; set; }
}
I faced the same problem, i dont know reason, but i resolved it like this:
1) DP
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(object), typeof(UserControl1),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
public object SelectedItems
{
get { return (object) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
2) Grid event
private void DataGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelectedItemsCasted = SelectedItems as IList<object>;
if (SelectedItemsCasted == null)
return;
foreach (object addedItem in e.AddedItems)
{
SelectedItemsCasted.Add(addedItem);
}
foreach (object removedItem in e.RemovedItems)
{
SelectedItemsCasted.Remove(removedItem);
}
}
3) In UC which contain UserControl1
Property:
public IList<object> SelectedPeople { get; set; }
Constructor:
public MainViewModel()
{
SelectedPeople = new List<object>();
}
I know this is a super old post- but after digging through this, and a few other posts which address this issue, I couldn't find a complete working solution. So with the concept from this post I am doing that.
I've also created a GitHub repo with the complete demo project which contains more comments and explanation of the logic than this post. MultiSelectDemo
I was able to create an AttachedProperty (with some AttachedBehavour logic as well to set up the SelectionChanged handler).
MultipleSelectedItemsBehaviour
public class MultipleSelectedItemsBehaviour
{
public static readonly DependencyProperty MultipleSelectedItemsProperty =
DependencyProperty.RegisterAttached("MultipleSelectedItems", typeof(IList), typeof(MultipleSelectedItemsBehaviour),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, MultipleSelectedItemsChangedCallback));
public static IList GetMultipleSelectedItems(DependencyObject d) => (IList)d.GetValue(MultipleSelectedItemsProperty);
public static void SetMultipleSelectedItems(DependencyObject d, IList value) => d.SetValue(MultipleSelectedItemsProperty, value);
public static void MultipleSelectedItemsChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is DataGrid dataGrid)
{
if (e.NewValue == null)
{
dataGrid.SelectionChanged -= DataGrid_SelectionChanged;
}
else
{
dataGrid.SelectionChanged += DataGrid_SelectionChanged;
}
}
}
private static void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid dataGrid)
{
var selectedItems = GetMultipleSelectedItems(dataGrid);
if (selectedItems == null) return;
foreach (var item in e.AddedItems)
{
try
{
selectedItems.Add(item);
}
catch (ArgumentException)
{
}
}
foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}
}
}
}
To use it, one critical thing within the view model, is that the view model collection must be initialized so that the attached property/behaviour sets up the SelectionChanged handler. In this example I've done that in the VM constructor.
public MainWindowViewModel()
{
MySelectedItems = new ObservableCollection<MyItem>();
}
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get => _myItems;
set => Set(ref _myItems, value);
}
private ObservableCollection<MyItem> _mySelectedItems;
public ObservableCollection<MyItem> MySelectedItems
{
get => _mySelectedItems;
set
{
// Remove existing handler if there is already an assignment made (aka the property is not null).
if (MySelectedItems != null)
{
MySelectedItems.CollectionChanged -= MySelectedItems_CollectionChanged;
}
Set(ref _mySelectedItems, value);
// Assign the collection changed handler if you need to know when items were added/removed from the collection.
if (MySelectedItems != null)
{
MySelectedItems.CollectionChanged += MySelectedItems_CollectionChanged;
}
}
}
private int _selectionCount;
public int SelectionCount
{
get => _selectionCount;
set => Set(ref _selectionCount, value);
}
private void MySelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do whatever you want once the items are added or removed.
SelectionCount = MySelectedItems != null ? MySelectedItems.Count : 0;
}
And finally to use it in the XAML
<DataGrid Grid.Row="0"
ItemsSource="{Binding MyItems}"
local:MultipleSelectedItemsBehaviour.MultipleSelectedItems="{Binding MySelectedItems}" >
</DataGrid>

Binding to custom Dependency Property fails

As the SelectedItems property of the ListBox control is a normal property and not a dependency property to bind to, I have derived of the ListBox and created a new dependency property SelectedItemsEx.
But my XAML compiler keeps giving me the error
A 'Binding' cannot be set on the 'SelectedItemsEx' property of the
type 'MyListBox'. A 'Binding' can only be set on a DependencyProperty
of a DependencyObject.
Why my property is not recognized as a dependency property? Any help is appreciated, thank you!
XAML:
<MyListBox ItemsSource="{Binding MyData}" SelectedItemsEx="{Binding SelectedEx}"
SelectionMode="Extended"> ... </MyListBox>
ListBox' implementation:
public class MyListBox : ListBox
{
public readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register("SelectedItemsEx",
typeof(ObservableCollection<MyItemsDataType>),
typeof(MyListBox),
new PropertyMetadata(default(ObservableCollection<MyItemsDataType>)));
public ObservableCollection<MyItemsDataType> SelectedItemsEx
{
get
{
var v = GetValue(SelectedItemsExProperty);
return (ObservableCollection<MyItemsDataType>)v;
}
set { SetValue(SelectedItemsExProperty, value); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItemsEx != null)
{
SelectedItemsEx.Clear();
foreach (var item in base.SelectedItems)
{
SelectedItemsEx.Add((MyItemsDataType)item);
}
}
}
The DependencyProperty field must be static:
public static readonly DependencyProperty SelectedItemsExProperty = ...
Note also that in order to make your derived ListBox a little more reusable, you should not constrain the type of the SelectedItemsEx property. Use IEnumerable (or IList like SelectedItems) instead. Moreover, there is no need to specify a default value by property metadata, as it is null already and default(<any reference type>) is also null.
You will however have to get notified whenever the SelectedItemsEx property has changed. Therefore you have to register a change callback via property metadata:
public static readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register(
"SelectedItemsEx", typeof(IEnumerable), typeof(MyListBox),
new PropertyMetadata(SelectedItemsExPropertyChanged));
public IEnumerable SelectedItemsEx
{
get { return (IEnumerable)GetValue(SelectedItemsExProperty); }
set { SetValue(SelectedItemsExProperty, value); }
}
private static void SelectedItemsExPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = (MyListBox)obj;
var oldColl = e.OldValue as INotifyCollectionChanged;
var newColl = e.NewValue as INotifyCollectionChanged;
if (oldColl != null)
{
oldColl.CollectionChanged -= listBox.SelectedItemsExCollectionChanged;
}
if (newColl != null)
{
newColl.CollectionChanged += listBox.SelectedItemsExCollectionChanged;
}
}
private void SelectedItemsExCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
}

Help with registerattached Dependency Property

I created my own DataGrid control which inherits from DataGrid. I declared a Dependency Property which I want to use at column level, so on the PreviewKeyDown event I check the value and decide if this current cell needs to be handled or not.
public class MyDataGrid : DataGrid
{
public static DependencyProperty HandleKeyPressEventProperty =
DependencyProperty.RegisterAttached(
"HandleKeyPressEvent",
typeof(bool),
typeof(MyDataGrid),
new FrameworkPropertyMetadata(true));
public bool HandleKeyPressEvent
{
get { return (bool)GetValue(HandleKeyPressEventProperty); }
set { SetValue(HandleKeyPressEventProperty, value); }
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (HandleKeyPressEvent)
{
HandleKeyPress(e);
}
else
{
base.OnPreviewKeyDown(e);
}
}
}
My XAML looks like this:
<MyDataGrid x:Name="myDataGrid">
<DataGridTextColumn MyDataGrid.HandleKeyPressEvent = "True" />
<DataGridTemplateColumn MyDataGrid.HandleKeyPressEvent = "False"/>
</MyDataGrid>
But I am having a real problem to have this dependency property available at the column level. What I try to do is just like Grid.Column. Can someone help me with that?
An attached property has a static Get method and a static Set method (which are declared by the property name prefixed by Get/Set) instead of a CLR Property wrapper. To check the current column in OnPreviewKeyDown, you can use CurrentCell.Column
public class MyDataGrid : DataGrid
{
public static readonly DependencyProperty HandleKeyPressEventProperty =
DependencyProperty.RegisterAttached("HandleKeyPressEvent",
typeof(bool),
typeof(MyDataGrid),
new UIPropertyMetadata(true));
public static bool GetHandleKeyPressEvent(DependencyObject obj)
{
return (bool)obj.GetValue(HandleKeyPressEventProperty);
}
public static void SetHandleKeyPressEvent(DependencyObject obj, bool value)
{
obj.SetValue(HandleKeyPressEventProperty, value);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (GetHandleKeyPressEvent(CurrentCell.Column) == true)
{
HandleKeyPress(e);
}
else
{
base.OnPreviewKeyDown(e);
}
}
}

Synchronously populating DependencyProperties of a custom control in Silverlight

I have a Silverlight custom control with two properties; Text and Id. I have created DependencyProperties for these as per the code below.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(LookupControl), new PropertyMetadata(NotifyPropertyChanged));
public static readonly DependencyProperty IdProperty = DependencyProperty.Register("Id", typeof(Guid?), typeof(LookupControl), new PropertyMetadata(NotifyPropertyChanged));
public event PropertyChangedEventHandler PropertyChanged;
public static void NotifyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as LookupControl;
if (control != null && control.PropertyChanged != null)
{
control.PropertyChanged(control, new PropertyChangedEventArgs("Text"));
}
}
public Guid? Id
{
get { return (Guid?)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
In a control method, the Id is populated first, and then the Text. My problem is that when i bind to Text and Id on this control, I want their data to be populated synchronously, so that when a PropertyChanged event fires on either property, both of them have updated data.
At this point in time I catch when the Id has changed, performed some processing, and if required, i set the Text to a new value. But once this OnChange of Id has finished, then the control method continues and populates the Text after i have already changed it back to something else.
Cold you save the values and only set when you have both?
private Guid? id;
private string text;
public Guid?Id
{
get { return id; }
set {
id = value;
TrySetValue();
}
}
public string Text
{
get { return text; }
set { text = value;
TrySetValue()}
}
private void TrySetValue()
{
if (id != null && text != null)
{
SetValue(IdProperty, id);
SetValue(TextProperty, text);
}
}

Categories