Q. How can you add meta-information, such as a description, to a class and return it without instantiating the class
This is the second out of three articles in this series.
Part 1 (Previous): Refactoring the Project folder and XAML namespace directivesPart 2 (This article): Inserting MetaInformation into a class and generically returning it.
Part 3 (Next): How do we get a list of pages within a namespace ( a subspace of the project).
In the article on Xamarin.Forms Xaml Samples, ”Part 5. From Data Bindings to MVVM”, towards the end it presents an app ”Implementing a Navigation Menu” that displays a menu of sample Content pages. The menu lists a Title and Description for each page that is supplied as each page type is added to a collection. The menu binds to these two pieces of information. My issue is that that meta-information does not come from the ContentPage class but is external to it. This grates against my comprehension of what Object Oriented Programming is about .. Data should be contained within the object. Also each page is explicitly added to the collection of page types, without instantiating the pages. I’d like to iterate through all pages found in a folder and therefore get this information from the class code. Three related approaches are discussed here
https://docs.microsoft.com/en-au/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
https://github.com/xamarin/xamarin-forms-samples
https://github.com/xamarin/xamarin-forms-samples/tree/master/XamlSamples
My Current Master on GitHub (Latest)XAMLSamples folder therein
[1] XAML namespace mapping: Refactor the project folder: Move files in conceptual folders
[2] MetaInfo: Encapsulate the pages’ metainformation (Title and Description) in the pages’ codebehind. (For this article)
[3] Refection: Use Reflection to get a collection of pages in XamlSamples.Views.
This article addresses:
[2] MetaInfo Add the Title and Description meta-information to ContentPages’ codebehind pages and return it in a generic manner without page instantiation in the PageDataViewModel class.
Xamarin.Forms ContentPage has a Title property that is typically set in the Xaml page in the class definition at the start of the page. On one hand it would be convenient in this quest to get that property value but I guess that value is not installed until the page is instantiated. So what is needed is some way to place some metainformation in the codebehind that can be extracted from the class type. Three solution are presented here.
In PageDataViewModel, for each page, beside the class type, e a Title and Description is required:
namespace XamlSamples { public class PageDataViewModel { public Type Type { private set; get; } public string Title { private set; get; } public string Description { private set; get; }
One way is to add some Dummy non-static properties to the page class, each with a Description attribute. This can then be extracted from the type via the System.ComponentModel API:
namespace XamlSamples.Views { public partial class HelloXamlPage : ContentPage { [System.ComponentModel.Description("Hello, XAML")] public string PTitle { get; set; } [System.ComponentModel.Description("Display a Label with many properties set")] public string PInfo { get; set; } public HelloXamlPage() { InitializeComponent(); } } }
Adding the Dummy” properties, each with a Description attribute.
This is then extracted from the type in PageVeiwModel in a method:
private string GetPropDescription(Type type, string prop) { PropertyInfo propInfo = type.GetProperty(prop); System.ComponentModel.DescriptionAttribute attrib = (System.ComponentModel.DescriptionAttribute)propInfo.GetCustomAttributes( typeof(System.ComponentModel.DescriptionAttribute), false).FirstOrDefault(); return attrib.Description; }
PageDataViewModel.GetPropDescription( ) Method
Which is called in the PageDataViewModel List constructor to set the required properties for each page:
//[1] Get from dummy properties' Descriptions Title = GetPropDescription(type, "PTitle"); Description = GetPropDescription(type, "PInfo");
Another approach is to add the information as static properties to the class. But how do you get the static properties when they aren’t properties of the page type (ContentPage)?
namespace XamlSamples.Views { public partial class HelloXamlPage : ContentPage { public static string StatPTitle { get; set; } = "Hello, XAML"; public static string StatPInfo { get; set; } = "Display a Label with many properties set";
Title and Description as static class properties
private string GetStaticProperty(Type type, string prop) { PropertyInfo propertyInfo = type .GetProperty(prop, BindingFlags.Public | BindingFlags.Static); if (propertyInfo == null) return string.Empty; // Use the PropertyInfo to retrieve the value from the type by not passing in an instance return (string)propertyInfo.GetValue(null); }
GetStaticProperty( ) method
//[2] Use static properties Title = GetStaticProperty(type, "StatPTitle"); Description = GetStaticProperty(type, "StatPInfo");
A third approach is add the Title and Description as a tilde separated list of two strings. Description is a designated Custom Attribute of a ContentPage so how do you get access to without instantiating the page?
namespace XamlSamples.Views { [System.ComponentModel.Description("Hello Xaml~Display a Label with many properties set")] public partial class HelloXamlPage : ContentPage {
class Description Custom Attribute: Title and Description as tilde separated list.
private string [ ] GetClassDescription(Type type) { string infos = null; foreach (var cAttrib in type.CustomAttributes) { foreach (var constructorArg in cAttrib.ConstructorArguments) { var val = constructorArg.Value; if (val is string) { string strn = (string)val; //One ConstructorArg is the relative path to the class file if (!strn.Contains("\\")) { //The class description is tilde separated class Title and Description if (strn.Contains("~")) { infos = strn.Split(new char [ ] {'~' }); break; } } } } } return infos; }
GetClassDescription( ) Method
//[3] Get it from the Class Description Custom attribute string infos = GetClassDescription(type); if (infos != null) { Title = infos[0]; Description = infos[1]; }
The PageDataViewModel constructor parameters are then removed:
public PageDataViewModel(Type type) //, string title, string description) {
And the calls to it are then simplified thus:
static PageDataViewModel() { All = new List<PageDataViewModel> { // Part 1. Getting Started with XAML new PageDataViewModel(typeof(HelloXamlPage)), new PageDataViewModel(typeof(XamlPlusCodePage)), etc
Each CodePage in this version of the repository has all three options. There is a mode at the top of PageDataViewmodel that chooses which mode (1, 2 or 3) to use”"
namespace XamlSamples.Views { [System.ComponentModel.Description("Hello Xaml~Display a Label with many properties set")] public partial class HelloXamlPage : ContentPage { public static string StatPTitle { get; set; } = "Hello, XAML"; public static string StatPInfo { get; set; } = "Display a Label with many properties set"; [System.ComponentModel.Description("Hello, XAML")] public string PTitle { get; set; } [System.ComponentModel.Description("Display a Label with many properties set")] public string PInfo { get; set; } public HelloXamlPage() { InitializeComponent(); } } }
namespace XamlSamples { public class PageDataViewModel { static int mode = 3; public Type Type { private set; get; } public string Title { private set; get; } public string Description { private set; get; } public PageDataViewModel(Type type) //, string title, string description) { Type = type; //Three ways to get it implicitly from the class if (mode == 1) { //[1] Get from dummy properties' Descriptions } else if (mode == 2) { //[2] Use static properties } else { //[3] Get it from the Class Description Custom attribute } } private string GetPropDescription(Type type, string prop) {} private string GetClassDescription(Type type) {} private string GetStaticProperty(Type type, string prop) {} static PageDataViewModel() {} public static IList<PageDataViewModel> All { private set; get; } } }
Three solutions to the question are provided. Each are correct solutions but are substantially different. But which is the best? “Beauty is in the eye of the beholder!” So its an esoteric decision to make. The last solution is possibly the most didactic as it assigns the properties to the class itself but is a little less neat as the two are combined. The first approach is possibly more straight forward, two class properties, but is possibly a kludge as it uses dummy properties. Finally the static properties approach may to be a straight forward approach within the class code but the extraction of this information is a bit esoteric. I had to do a bit of “monkey on a typewriter” coding to get to this, with a bit of Intellisense and debug watches thrown in. What’s your choice?
Which of the three solutions is your favourite? Please vote on the alternatives at : https://tinyurl.com/y9xyug3g