Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for binding to sub-objects in ComboBoxColumn #2919

Merged
merged 19 commits into from May 28, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c63dd05
Merge branch 'master' of https://github.com/windows-toolkit/WindowsCo…
filipwa84 Mar 8, 2019
9fb0160
Merge branch 'master' of https://github.com/windows-toolkit/WindowsCo…
filipwa84 Apr 30, 2019
3468fcb
Added support for sub-object binding in ComboBoxColumn
filipwa84 Apr 30, 2019
9c7e6eb
Refactored SetDataItemProperty() method
filipwa84 May 2, 2019
953c1ff
Merge branch 'master' into features/sub-objects
michael-hawker Jul 17, 2019
0c29eaa
Merge branch 'master' into features/sub-objects
Kyaa-dost Sep 10, 2019
fcca41d
Merge branch 'master' into features/sub-objects
Kyaa-dost Sep 16, 2019
33d30be
Utilized existing methods in TypeHelper.cs and added SetNestedPropert…
filipwa84 Nov 19, 2019
4e85af1
Merge branch 'upstream-master' into features/sub-objects
filipwa84 Nov 19, 2019
a0efca6
Fixed issue with usage of incorrect PropertyPath
filipwa84 Nov 19, 2019
91589ae
Implemented changes according to review comments
filipwa84 Nov 20, 2019
cfb6685
Merge branch 'master' into features/sub-objects
michael-hawker Feb 8, 2020
fece248
Merge branch 'master' into features/sub-objects
michael-hawker Mar 17, 2020
2b2cc88
Merge branch 'upstream-master'
filipwa84 May 5, 2020
f2d4c8b
Merge branch 'master' into features/sub-objects
filipwa84 May 5, 2020
8987029
Merge branch 'master' into features/sub-objects
Kyaa-dost May 6, 2020
7e9792c
Merge branch 'master' into features/sub-objects
michael-hawker May 12, 2020
1ab0f99
Merge branch 'master' into features/sub-objects
filipwa84 May 18, 2020
6fee7b4
Merge branch 'master' into features/sub-objects
michael-hawker May 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Controls.DataGridInternals;
using Microsoft.Toolkit.Uwp.UI.Utilities;
using Microsoft.Toolkit.Uwp.Utilities;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
Expand Down Expand Up @@ -207,6 +208,8 @@ protected override FrameworkElement GenerateEditingElement(DataGridCell cell, ob

EnsureItemsSourceBinding();

EnsureColumnTypeAgreement(dataItem);

var comboBox = new ComboBox
{
Margin = default(Thickness),
Expand All @@ -216,10 +219,10 @@ protected override FrameworkElement GenerateEditingElement(DataGridCell cell, ob

if (dataItem != null)
{
var value = dataItem.GetType().GetProperty(Binding.Path.Path).GetValue(dataItem);
var value = TypeHelper.GetNestedPropertyValue(dataItem, Binding.Path.Path);

var selection = !string.IsNullOrEmpty(DisplayMemberPath)
? ItemsSource?.Cast<object>().FirstOrDefault(x => x.GetType().GetProperty(Binding.Path.Path).GetValue(x).Equals(value))
var selection = !string.IsNullOrEmpty(DisplayMemberPath)
? ItemsSource?.Cast<object>().FirstOrDefault(x => TypeHelper.GetNestedPropertyValue(x, Binding.GetBindingPropertyName()).Equals(value))
: ItemsSource?.Cast<object>().FirstOrDefault(x => x.Equals(value));

comboBox.SelectedItem = selection;
Expand Down Expand Up @@ -270,20 +273,10 @@ protected override FrameworkElement GenerateEditingElement(DataGridCell cell, ob
if (item != null)
{
var newValue = !string.IsNullOrEmpty(DisplayMemberPath)
? item.GetType().GetProperty(Binding.Path.Path).GetValue(item)
? item.GetType().GetProperty(Binding.GetBindingPropertyName())?.GetValue(item)
: item;

if (dataItem != null)
{
dataItem.GetType().GetProperty(Binding.Path.Path).SetValue(dataItem, newValue);
}
else
{
var dataType = OwningGrid.ItemsSource.GetItemType();
var newDataItem = Activator.CreateInstance(dataType);
dataType.GetProperty(Binding.Path.Path).SetValue(newDataItem, newValue);
dataItem = newDataItem;
}
TypeHelper.SetNestedPropertyValue(ref dataItem, newValue, Binding.Path.Path);
}
};

Expand All @@ -299,6 +292,7 @@ protected override FrameworkElement GenerateEditingElement(DataGridCell cell, ob
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
EnsureColumnBinding(dataItem);
EnsureColumnTypeAgreement(dataItem);
EnsureDisplayMemberPathExists();
EnsureItemsSourceBinding();

Expand Down Expand Up @@ -360,16 +354,16 @@ protected override void CancelCellEdit(FrameworkElement editingElement, object u
{
if (uneditedValue != null)
{
var property = uneditedValue.GetType().GetProperty(Binding.Path.Path);
var property = uneditedValue.GetType().GetNestedProperty(Binding.GetBindingPropertyName());

if (property == null)
{
comboBox.SelectedItem = uneditedValue;
}
else
{
var value = property.GetValue(uneditedValue);
var selection = ItemsSource?.Cast<object>().FirstOrDefault(x => x.GetType().GetProperty(Binding.Path.Path).GetValue(x).Equals(value));
var value = TypeHelper.GetNestedPropertyValue(uneditedValue, Binding.GetBindingPropertyName());
var selection = ItemsSource?.Cast<object>().FirstOrDefault(x => TypeHelper.GetNestedPropertyValue(x, Binding.GetBindingPropertyName()).Equals(value));

comboBox.SelectedItem = selection;
}
Expand Down Expand Up @@ -609,9 +603,9 @@ private string GetDisplayValue(object dataItem)
{
if (Binding?.Path != null && dataItem != null)
{
var value = dataItem.GetType().GetProperty(Binding.Path.Path).GetValue(dataItem);
var value = TypeHelper.GetNestedPropertyValue(dataItem, Binding.Path.Path);

var item = ItemsSource?.Cast<object>().FirstOrDefault(x => x.GetType().GetProperty(Binding.Path.Path).GetValue(x).Equals(value));
var item = ItemsSource?.Cast<object>().FirstOrDefault(x => TypeHelper.GetNestedPropertyValue(x, Binding.GetBindingPropertyName()).Equals(value));

var displayValue = item?.GetType().GetProperty(DisplayMemberPath).GetValue(item) ?? string.Empty;

Expand All @@ -633,9 +627,25 @@ private void EnsureColumnBinding(object dataItem)
throw DataGridError.DataGridComboBoxColumn.UnsetBinding(GetType());
}

if (!dataItem?.GetType().GetProperties().Any(x => x.Name.Equals(Binding.Path.Path)) ?? false)
var property = dataItem?.GetType().GetNestedProperty(Binding?.Path?.Path);

if (property == null && dataItem != null)
{
throw DataGridError.DataGridComboBoxColumn.UnknownBindingPath(Binding, dataItem.GetType());
throw DataGridError.DataGridComboBoxColumn.UnknownBindingPath(Binding, dataItem?.GetType());
}
}

private void EnsureColumnTypeAgreement(object dataItem)
{
if (string.IsNullOrEmpty(DisplayMemberPath))
{
var itemsSourceType = ItemsSource?.GetType().GetEnumerableItemType();
var dataItemType = dataItem?.GetType().GetNestedPropertyType(Binding?.Path?.Path);

if (dataItemType != null && itemsSourceType != null && itemsSourceType != dataItemType)
{
throw DataGridError.DataGridComboBoxColumn.BindingTypeMismatch(dataItemType, itemsSourceType);
}
}
}

Expand All @@ -658,7 +668,7 @@ private void EnsureItemsSourceBinding()
{
var item = ItemsSource.Cast<object>().FirstOrDefault();

if (item != null && !item.GetType().GetProperties().Any(y => y.Name.Equals(Binding.Path.Path)))
if (item != null && !item.GetType().GetProperties().Any(y => y.Name.Equals(Binding.GetBindingPropertyName())))
{
throw DataGridError.DataGridComboBoxColumn.UnknownItemsSourcePath(Binding);
}
Expand Down
Expand Up @@ -206,6 +206,11 @@ public static ArgumentException UnknownItemsSourcePath(Binding binding)
{
return new ArgumentException(Format("The ItemsSource elements do not contain a property {0}. Ensure that the binding path has been set correctly.", binding.Path.Path));
}

public static ArgumentException BindingTypeMismatch(Type bindingType, Type itemSourceType)
{
return new ArgumentException(Format("The DataGridComboBoxColumn ItemSource elements of type \'{0}\' do not match the Binding type \'{1}\'. Ensure that the paths have been set correctly and specify a DisplayMemberPath for non built-in types.", itemSourceType.FullName, bindingType.FullName));
}
}

public static class DataGridTemplateColumn
Expand Down
50 changes: 50 additions & 0 deletions Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/TypeHelper.cs
Expand Up @@ -8,6 +8,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml.Data;

namespace Microsoft.Toolkit.Uwp.Utilities
{
Expand Down Expand Up @@ -113,6 +114,11 @@ private static string GetDefaultMemberName(this Type type)
return defaultMemberAttribute == null ? null : defaultMemberAttribute.MemberName;
}

internal static string GetBindingPropertyName(this Binding binding)
{
return binding?.Path?.Path?.Split('.')?.LastOrDefault();
}

/// <summary>
/// Finds the PropertyInfo for the specified property path within this Type, and returns
/// the value of GetShortName on its DisplayAttribute, if one exists. GetShortName will return
Expand Down Expand Up @@ -356,6 +362,50 @@ internal static string RemoveDefaultMemberName(string property)
return property;
}

/// <summary>
/// Sets the value of a given property path on a particular item.
/// </summary>
/// <param name="item">Parent data item.</param>
/// <param name="newValue">New child value</param>
/// <param name="propertyPath">Property path</param>
internal static void SetNestedPropertyValue(ref object item, object newValue, string propertyPath)
{
if (string.IsNullOrEmpty(propertyPath))
{
item = newValue;
}
else
{
var propertyPathParts = SplitPropertyPath(propertyPath);

if (propertyPathParts.Count == 1)
{
item?.GetType().GetProperty(propertyPath)?.SetValue(item, newValue);
}
else
{
object temporaryItem = item;
object nextToLastItem = null;

PropertyInfo propertyInfo = null;

for (var i = 0; i < propertyPathParts.Count; i++)
{
propertyInfo = temporaryItem?.GetType().GetProperty(propertyPathParts[i]);

if (i == propertyPathParts.Count - 2)
{
nextToLastItem = propertyInfo?.GetValue(temporaryItem);
}

temporaryItem = propertyInfo?.GetValue(temporaryItem);
}

propertyInfo?.SetValue(nextToLastItem, newValue);
}
}
}

/// <summary>
/// Returns a list of substrings where each one represents a single property within a nested
/// property path which may include indexers. For example, the string "abc.d[efg][h].ijk"
Expand Down