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}"/>
Related
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);
}
}
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>
I'm just playing with the grid class, to make it "cleaver". So I want to be able to use it in xaml and just specify how many rows I want, or columns etc, to do this I've got the code below. It all works fine, I used the loaded event to add rows and columns, but now I am thinking how should I unsubscribe from this event? I can't find straight forward information of how to do it?
public class MyFormGrid : Grid
{
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(ASLFormGrid), new PropertyMetadata(0));
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(ASLFormGrid), new PropertyMetadata(0));
public MyFormGrid()
{
Loaded += MyGrid_Loaded;
}
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
private void MyGrid_Loaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < Columns; i++)
{
ColumnDefinitions.Add(new ColumnDefinition());
}
for (var i = 0; i < Rows; i++)
{
RowDefinitions.Add(new RowDefinition(););
}
}
}
Kind Regards
This could easily be put inside your loaded event handler:
public class MyFormGrid : Grid
{
...
public MyFormGrid()
{
Loaded += MyGrid_Loaded;
}
...
private void MyGrid_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= MyGrid_Loaded;
...
}
}
In my application i'm trying to load the main module trough code. Everything works up to the point of rendering and i have NO clue why it isn't rendering. The content has the right values and everything.. My guess is that something went wibbly wobbly and I seem to be missing it.
Control that holds the view
[ContentPropertyAttribute("ContentView")]
public class ContentControlExtened : ContentControl
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Type",
typeof(Type),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentTypeChanged));
public static readonly DependencyProperty ContentViewProperty =
DependencyProperty.Register(
"View",
typeof(FrameworkElement),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentViewChanged));
private static void ContentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public ContentControlExtened()
{
this.Loaded += ContentControlLoaded;
}
private void ContentControlLoaded(object sender, RoutedEventArgs e)
{
this.LoadContent();
}
private void LoadContent()
{
UserControl u = null;
if (Type != null)
{
u = (UserControl)ServiceLocator.Current.GetInstance(this.Type);
}
u.Background = new SolidColorBrush(Color.FromRgb(0, 0, 255));
this.View = u;
}
public Type Type
{
get { return (Type)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Method in shell to load the main view of the given moduleInfo
private void OpenMainView(ModuleInfo module)
{
Type moduleType = Type.GetType(module.ModuleType);
var moduleMainViewType = moduleType.Assembly.GetType(moduleType.Namespace + ".Views.MainView");
this.ContentHolder.MainContent.Type = moduleMainViewType;
}
The contructor of the mainview is straight forward. Just standard control InitializeComponent() and Datacontext is being set trough the servicelocator.
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
Here you are getting and setting the ContentProperty. It should be ContentViewProperty.
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.