I want to make a label that will extract the name or some other data of the bound item.
[Display(Description = "Gimme your goddamm first name will ya")]
public string FirstName { get; set; }
Code:
public class TitleLabel : ContentView
{
public Label Label { get; } = new Label();
public TitleLabel()
{
//TODO ensure Content is not accessed manually
Content = Label;
}
protected override void OnBindingContextChanged() =>
Label.Text = GetPropertyTitle();
string GetPropertyTitle()
{
var bcp = BindingContextProperty;
//pseudo:
var binding = GetBinding(bcp);
var obj = binding.Object;
var propertyName = binding.Path;
var propertyInfo = obj.GetType().GetTypeInfo().DeclaredMembers
.SingleOrDefault(m => m.Name == propertyName);
if (propertyInfo == null)
throw new InvalidOperationException();
return propertyInfo.GetCustomAttribute<DisplayAttribute>().Description;
}
}
XAML:
<my:TitleLabel Text="{Binding FirstName}" />
Rendered result:
<my:TitleLabel Text="Gimme your goddamm first name will ya" />
Gotcha (Gist):
public class DisplayExtension : IMarkupExtension<string>
{
public object Target { get; set; }
BindableProperty _Property;
public string ProvideValue(IServiceProvider serviceProvider)
{
if (Target == null
|| !(Target is Enum
|| Target is Type
|| (Target is Binding binding && !string.IsNullOrWhiteSpace(binding.Path))))
throw new InvalidOperationException($"'{nameof(Target)}' must be properly set.");
var p =(IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (!(p.TargetObject is BindableObject bo
&& p.TargetProperty is BindableProperty bp
&& bp.ReturnType.GetTypeInfo().IsAssignableFrom(typeof(string).GetTypeInfo())))
throw new InvalidOperationException(
$"'{nameof(DisplayExtension)}' cannot only be applied"
+ "to bindable string properties.");
_Property = bp;
bo.BindingContextChanged += DisplayExtension_BindingContextChanged;
return null;
}
void DisplayExtension_BindingContextChanged(object sender, EventArgs e)
{
var bo = (BindableObject)sender;
bo.BindingContextChanged -= DisplayExtension_BindingContextChanged;
string display = null;
if (Target is Binding binding)
display = ExtractMember(bo, (Binding)Target);
else if (Target is Type type)
display = ExtractDescription(type.GetTypeInfo());
else if (Target is Enum en)
{
var enumType = en.GetType();
if (!Enum.IsDefined(enumType, en))
throw new InvalidOperationException(
$"The value '{en}' is not defined in '{enumType}'.");
display = ExtractDescription(
enumType.GetTypeInfo().GetDeclaredField(en.ToString()));
}
bo.SetValue(_Property, display);
}
string ExtractMember(BindableObject target, Binding binding)
{
var container = target.BindingContext;
var properties = binding.Path.Split('.');
var i = 0;
do
{
var property = properties[i++];
var type = container.GetType();
var info = type.GetRuntimeProperty(property);
if (properties.Length > i)
container = info.GetValue(container);
else
{
return ExtractDescription(info);
}
} while (true);
}
string ExtractDescription(MemberInfo info)
{
var display = info.GetCustomAttribute<DisplayAttribute>(true);
if (display != null)
return display.Name ?? display.Description;
var description = info.GetCustomAttribute<DescriptionAttribute>(true);
if (description != null)
return description.Description;
return info.Name;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) =>
ProvideValue(serviceProvider);
}
Usage:
<Label Text="{my:Display Target={Binding FirstName}}"/>
<Label Text="{my:Display Target={Binding User.Person.Address.City.Country}}"/>
<Label Text="{my:Display Target={Type models:Person}}"/>
<Label Text="{my:Display Target={Static models:Gender.Male}}"/>