Skip to content

Commit

Permalink
Merge pull request #2919 from filipwa84/features/sub-objects
Browse files Browse the repository at this point in the history
Added support for binding to sub-objects in ComboBoxColumn
  • Loading branch information
michael-hawker committed May 28, 2020
2 parents fec9710 + 6fee7b4 commit 26d00f2
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 23 deletions.
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,11 +219,11 @@ 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))
: ItemsSource?.Cast<object>().FirstOrDefault(x => x.Equals(value));
? 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 @@ -354,6 +360,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

0 comments on commit 26d00f2

Please sign in to comment.