How to control the order of Dependecy Properties Initialization in C# - c#

I'm trying to create a TextBox control that has a "Table of values" and an "Index" as dependency properties, were I can changed the Index property and the TextBox will look for the corresponding Value from the Table.
Here's the code:
public class Record
{
public int Index { get; set; }
public string Value { get; set; }
}
public class IndexedTextBox : TextBox
{
public static readonly DependencyProperty TableProperty = DependencyProperty.Register(nameof(Table),
typeof(IEnumerable<Record>), typeof(IndexedTextBox));
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(nameof(Index),
typeof(int), typeof(IndexedTextBox), new PropertyMetadata(0, IndexChangedCallback));
private static void IndexChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IndexedTextBox ctl = d as IndexedTextBox;
int index = (int)e.NewValue;
if (ctl.Table.Any(t => t.Index == index))
ctl.Text = ctl.Table.Where(t => t.Index == index).FirstOrDefault().Value;
else
ctl.Text = "N/A";
}
public IEnumerable<Record> Table
{
get => (IEnumerable<Record>)GetValue(TableProperty);
set => SetValue(TableProperty, value);
}
public int Index {
get => (int)GetValue(IndexProperty);
set => SetValue(IndexProperty, value);
}
}
And the XAML code is something like this:
<local:IndexedTextBox Table="{Binding ViewModelTable}" Index="1"/>
Sometimes is works, other times it won't.
I noticed that the 2 properties (Table and Index) are being initialized in random order, and whenever the Index is loaded before the Table the control won't work as expected.
Is there a workaround to that? Can we control the order of the Initialization or there any other thing I should be doing.
I'd greatly appreciate a feedback
Thanks

whenever the Index is loaded before the Table the control won't work as expected ...
So change the implementation to make it work by for example invoking the callback for both properties and check the state of the control before doing anything, e.g.:
public class IndexedTextBox : TextBox
{
public static readonly DependencyProperty TableProperty = DependencyProperty.Register(nameof(Table),
typeof(IEnumerable<Record>), typeof(IndexedTextBox), new PropertyMetadata(null, TableChangedCallback));
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(nameof(Index),
typeof(int), typeof(IndexedTextBox), new PropertyMetadata(0, IndexChangedCallback));
private static void TableChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
((IndexedTextBox)d).SetText();
private static void IndexChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
((IndexedTextBox)d).SetText();
private void SetText()
{
if (Table != null)
{
int index = Index;
if (Table.Any(t => t.Index == index))
Text = Table.Where(t => t.Index == index).FirstOrDefault().Value;
else
Text = "N/A";
}
}
public IEnumerable<Record> Table
{
get => (IEnumerable<Record>)GetValue(TableProperty);
set => SetValue(TableProperty, value);
}
public int Index
{
get => (int)GetValue(IndexProperty);
set => SetValue(IndexProperty, value);
}
}

Related

Binding to ICollectionView doesn't display anything

It works when I bind directly to EventsSource but it doesn't when I change the binding to EventsView.
// EventsControl class
private bool Filter(object obj)
{
if (!(obj is Event #event)) return false;
if (string.IsNullOrEmpty(Location)) return true;
return true;
// return #event.Location == Location;
}
public static readonly DependencyProperty EventsSourceProperty = DependencyProperty.Register(
nameof(EventsSource), typeof(ObservableCollection<Event>), typeof(EventsControl), new PropertyMetadata(default(ObservableCollection<Event>), EventsSourceChanged));
public ObservableCollection<Event> EventsSource
{
get => (ObservableCollection<Event>)GetValue(EventsSourceProperty);
set => SetValue(EventsSourceProperty, value);
}
public ICollectionView EventsView { get; set; }
private static void EventsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is EventsControl eventsControl)) return;
var view = new CollectionViewSource { Source = e.NewValue }.View;
view.Filter += eventsControl.Filter;
eventsControl.EventsView = view;
//view.Refresh();
}
What could be wrong with this code?
I don't want to use a default view (
WPF CollectionViewSource Multiple Views? )
I made it a dependency property and it works. Not sure if that's the best way to solve it though.
public static readonly DependencyProperty EventsViewProperty = DependencyProperty.Register(
nameof(EventsView), typeof(ICollectionView), typeof(EventsControl), new PropertyMetadata(default(ICollectionView)));
public ICollectionView EventsView
{
get => (ICollectionView) GetValue(EventsViewProperty);
set => SetValue(EventsViewProperty, value);
}

Register Attached Property in UserControl

I'm trying to create a UserControl that exposes 2 other controls, UI and Host, so that I can use MediaPlayerWpf and set properties in the designer like UI.AllowPause and Host.Volume
I understand Attached Properties are what I need. Furthermore, I need to allow setting the Host, while UI only exposes its properties but can't be changed.
I can't get properties UI and Host to display in the designer. What am I doing wrong?
public partial class MediaPlayerWpf {
public MediaPlayerWpf() {
InitializeComponent();
SetUI(this, MediaUI);
}
public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterAttachedReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata(null));
public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
public PlayerControls UI { get => (PlayerControls)base.GetValue(UIProperty); private set => base.SetValue(UIPropertyKey, value); }
[AttachedPropertyBrowsableForType(typeof(MediaPlayerWpf))]
public static PlayerControls GetUI(UIElement element) {
return (PlayerControls)element.GetValue(UIProperty);
}
public static void SetUI(UIElement element, PlayerControls value) {
element.SetValue(UIPropertyKey, value);
}
public static DependencyProperty HostProperty = DependencyProperty.RegisterAttached("Host", typeof(PlayerBase), typeof(MediaPlayerWpf), new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)base.GetValue(HostProperty); set => base.SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
GetUI(P).PlayerHost = e.NewValue as PlayerBase;
}
}
[AttachedPropertyBrowsableForType(typeof(MediaPlayerWpf))]
public static PlayerBase GetHost(UIElement element) {
return (PlayerBase)element.GetValue(HostProperty);
}
public static void SetHost(UIElement element, PlayerBase value) {
element.SetValue(HostProperty, value);
}
}

DependencyProperty not being attached

SetFocusIndex is called when navigating away from a view. This should register/attach a DependencyProperty to the specified control.
GetFocusIndex is called upon returning to the view. This should extract the registered/attached DependencyProperty which holds the index of the last control (EightTileGrid item etc) that had focus.
I see the correct DependencyProperty being set , but when I retrieve it on back navigation it returns -1 value as if the property was never set.
Setting logic:
public static readonly DependencyProperty FocusIndexProperty =
DependencyProperty.RegisterAttached(
"FocusIndex",
typeof(int),
typeof(GamePadFocusManager),
new PropertyMetadata(-1));
public static int GetFocusIndex(DependencyObject obj)
{
return (int)obj.GetValue(FocusIndexProperty);
}
public static void SetFocusIndex(DependencyObject obj, int value)
{
obj.SetValue(FocusIndexProperty, value);
}
private void OnNavigatingMessage(NavigatingMessage navigatingMessage)
{
Messenger.Default.Unregister<NavigatingMessage>(this, OnNavigatingMessage);
SaveFocusIndex();
}
private void SaveFocusIndex()
{
var controls = VisualTreeQueryHelper.FindChildrenOfType<Control>(this).ToList();
for (int i = 0; i < controls.Count; i++)
{
if (controls[i].ContainsFocus())
{
GamePadFocusManager.SetFocusIndex(this, i);
break;
}
}
}
Retrieving logic:
private void BaseTileGrid_GotFocus(object sender, RoutedEventArgs e)
{
if ((FocusManager.GetFocusedElement() == this) || !this.ContainsFocus())
{
SetFocusOnChildControl();
}
}
public virtual void SetFocusOnChildControl()
{
var focusIndex = Math.Max(0, GamePadFocusManager.GetFocusIndex(this));
var contentControls = VisualTreeQueryHelper.FindChildrenOfType<ContentControl>(this).ToList();
DispatcherHelper.BeginInvokeAtEndOfUiQueue(() =>
{
if (contentControls.Count > focusIndex)
{
if (this.ContainsFocus())
{
GamePadFocusManager.FocusOn(contentControls[focusIndex]);
}
}
});
}
Grid
public class BaseTileGrid : Control
{
...
}
Xaml:
<GridLayout:BaseTileGrid x:Name="recentlyWatched"
Style="{StaticResource FourByTwoGrid}"
ItemsSource="{Binding ItemViewModels}"
ItemTemplate="{StaticResource GridLayoutDataTemplateSelector}"
OverflowPageTitle="{Binding OverflowPageTitle}"
MoreButtonCommand="{Binding MoreButtonCommand}"/>

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>

How can make all my user control dependency values load before they control accesses their values?

When I call this custom control, I have to put the attribute values in the correct order since the third dependency property (ItemTypeIdCode) accesses the values of the first two (KeyField, ValueField) to look up data in the database, and if they come after the first attribute, then their values are empty.
<controls:DropDown x:Name="TheItemTypes"
KeyField="idCode" ValueField="title"
ItemTypeIdCode="itemTypes"
Width="150" HorizontalAlignment="Left" Margin="0 0 0 5"/>
How can I make the order of my user control attributes arbitrary? i.e. so that they all load their values first before any of them register as changed?
Here is the code for my user control:
using System.Windows;
using System.Collections.Generic;
using TestApp.DataLayer;
namespace TestApp.Controls
{
public partial class DropDown : DependencyComboBox
{
#region DependencyProperty: ItemTypeIdCode
public string ItemTypeIdCode
{
get
{
if (GetValue(ItemTypeIdCodeProperty) != null)
return GetValue(ItemTypeIdCodeProperty).ToString();
else
return "";
}
set { SetValue(ItemTypeIdCodeProperty, value); }
}
public static readonly DependencyProperty ItemTypeIdCodeProperty =
DependencyProperty.Register("ItemTypeIdCode", typeof(string), typeof(DropDown),
new PropertyMetadata(string.Empty, OnItemTypeIdCodePropertyChanged));
private static void OnItemTypeIdCodePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
DropDown dropDown = dependencyObject as DropDown;
dropDown.OnPropertyChanged("ItemTypeIdCode");
dropDown.OnItemTypeIdCodePropertyChanged(e);
}
private void OnItemTypeIdCodePropertyChanged(DependencyPropertyChangedEventArgs e)
{
Items.Clear();
foreach (var kvp in Datasource.GetInstance().GetKeyValues(ItemTypeIdCode + "(" + KeyField + "," + ValueField + "); all; orderby displayOrder"))
{
Items.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value));
}
this.SelectedIndex = 0;
}
#endregion
#region DependencyProperty: KeyField
public string KeyField
{
get
{
if (GetValue(KeyFieldProperty) != null)
return GetValue(KeyFieldProperty).ToString();
else
return "";
}
set { SetValue(KeyFieldProperty, value); }
}
public static readonly DependencyProperty KeyFieldProperty =
DependencyProperty.Register("KeyField", typeof(string), typeof(DropDown),
new PropertyMetadata(string.Empty, OnKeyFieldPropertyChanged));
private static void OnKeyFieldPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
DropDown dropDown = dependencyObject as DropDown;
dropDown.OnPropertyChanged("KeyField");
dropDown.OnKeyFieldPropertyChanged(e);
}
private void OnKeyFieldPropertyChanged(DependencyPropertyChangedEventArgs e)
{
}
#endregion
#region DependencyProperty: ValueField
public string ValueField
{
get
{
if (GetValue(ValueFieldProperty) != null)
return GetValue(ValueFieldProperty).ToString();
else
return "";
}
set { SetValue(ValueFieldProperty, value); }
}
public static readonly DependencyProperty ValueFieldProperty =
DependencyProperty.Register("ValueField", typeof(string), typeof(DropDown),
new PropertyMetadata(string.Empty, OnValueFieldPropertyChanged));
private static void OnValueFieldPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
DropDown dropDown = dependencyObject as DropDown;
dropDown.OnPropertyChanged("ValueField");
dropDown.OnValueFieldPropertyChanged(e);
}
private void OnValueFieldPropertyChanged(DependencyPropertyChangedEventArgs e)
{
}
#endregion
public DropDown()
{
InitializeComponent();
DataContext = this;
}
}
}
Initialize Items only when all properties are set :
private void InitItems()
{
if (!string.IsNullOrEmpty(ItemTypeIdCode) &&
!string.IsNullOrEmpty(KeyField) &&
!string.IsNullOrEmpty(ValueField))
{
Items.Clear();
foreach (var kvp in Datasource.GetInstance().GetKeyValues(ItemTypeIdCode + "(" + KeyField + "," + ValueField + "); all; orderby displayOrder"))
{
Items.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value));
}
this.SelectedIndex = 0;
}
}
private void OnItemTypeIdCodePropertyChanged(DependencyPropertyChangedEventArgs e)
{
InitItems();
}
private static void OnKeyFieldPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
DropDown dropDown = dependencyObject as DropDown;
dropDown.InitItems();
dropDown.OnPropertyChanged("KeyField");
dropDown.OnKeyFieldPropertyChanged(e);
}
private static void OnValueFieldPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
DropDown dropDown = dependencyObject as DropDown;
dropDown.InitItems();
dropDown.OnPropertyChanged("ValueField");
dropDown.OnValueFieldPropertyChanged(e);
}
Any way you can do the setup processing in the Load event instead of the property accessors? Then all the needed properties will be set by load time.

Categories