Pages

Thursday, March 29, 2012

WPF Grid Layout Simplified Using Rows and Columns Attached Properties

A major problem with WPF and XAML is that XAML code tends to be very cluttered and after a little your XAML code gets too much complicated. In a recent project I suggested to define new controls by deriving from existing controls, so you can assign a shorter name to them or avoiding setting the same property the same value for thousand of time(e.g. Horizontal orientation stack panel).

Here is another tip to make you XAML code less cluttered. One area that you may find it hard to write and read and maintain is Grid layout control. Defining RowDefinitions and ColumnDefinitions waste too much space and adds too unnecessary lines of code. Hence it become too complicated to handle it.
The solution I am sugessting here is to use some attached properties to define RowDefinitions and ColumnDefinitions in a smarter way.
You can fetch the code for attached properties here: https://gist.github.com/2234500
This code adds these attached properties to Grid

  • Grid.Rows
  • Grid.Columns

and adds these attached properties to UIElemnet

  • Cell

Grid.Rows and Grid.Columns are same thing but one is for specifying rows and one is for columns. These two properties are simply text value which you can set it according to the following Rules:
  • Separate items with semi colon(;) , Example "auto;auto;*"
  • Use auto for adding an Auto sized row or column , Example "auto;auto"
  • Specify * for adding an star sized row or column, Example "1*,2*, *"
  • Specify only a number for adding a fixed size row or column, Example "200,200,100"
  • Append # followed by a number to add multiple rows and columns of same sizing. "1*#4,auto"
  • The number of starts in an item will multiplies star factor. Example "**,***,*" instead of "2*,3*,*"

XAML Sample:

<Grid s:Grid.Rows="auto;1*;auto;auto" s:Grid.Columns="*#5;auto"> ... </Grid>

Adds 4 rows to grid which are all Auto sized except the second row which occupies all remained space. Also adds 5 star columns followed by an Auto sized column.
Implementaion
As I said this is simply 3 attached properties which allows you to specify the rows and columns using a textual representation. Here is the function which converts the textual value into a GridLength value:

public static GridLength ParseGridLength(string text)
{
    text = text.Trim();
    if (text.ToLower() == "auto")
        return GridLength.Auto;
    if (text.Contains("*"))
    {
        var startCount = text.ToCharArray().Count(c => c == '*');
        var pureNumber = text.Replace("*""");
        var ratio = string.IsNullOrWhiteSpace(pureNumber) ? 1 : double.Parse(pureNumber);
        return new GridLength(startCount * ratio, GridUnitType.Star);
    }
    var pixelsCount = double.Parse(text);
    return new GridLength(pixelsCount, GridUnitType.Pixel);
}

And here is the function which makes # operator working:

public static IEnumerable<GridLength> Parse(string text)
{
    if (text.Contains("#"))
    {
        var parts = text.Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
        var count = int.Parse(parts[1].Trim());
        return Enumerable.Repeat(ParseGridLength(parts[0]), count);
    }
    else
    {
        return new[] { ParseGridLength(text) };
    }
}

And finally the attached property change callback:

var grid = d as System.Windows.Controls.Grid;
var oldValue = e.OldValue as string;
var newValue = e.NewValue as string;
if (oldValue == null || newValue == null)
    return;
 
if (oldValue != newValue)
{
    grid.ColumnDefinitions.Clear();
    newValue
        .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
        .SelectMany(Parse)
        .Select(l => new ColumnDefinition { Width = l })
        .ToList().ForEach(grid.ColumnDefinitions.Add);
}

The above code is for Columns attached property but this is the same for Rows attached property as well.

The whole source code for these attached properties:

namespace Sam
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
public static class Grid
{
#region helper methods
public static IEnumerable<GridLength> Parse(string text)
{
if (text.Contains("#"))
{
var parts = text.Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
var count = int.Parse(parts[1].Trim());
return Enumerable.Repeat(ParseGridLength(parts[0]), count);
}
else
{
return new[] { ParseGridLength(text) };
}
}
public static GridLength ParseGridLength(string text)
{
text = text.Trim();
if (text.ToLower() == "auto")
return GridLength.Auto;
if (text.Contains("*"))
{
var startCount = text.ToCharArray().Count(c => c == '*');
var pureNumber = text.Replace("*", "");
var ratio = string.IsNullOrWhiteSpace(pureNumber) ? 1 : double.Parse(pureNumber);
return new GridLength(startCount * ratio, GridUnitType.Star);
}
var pixelsCount = double.Parse(text);
return new GridLength(pixelsCount, GridUnitType.Pixel);
}
#endregion
#region GridColumnsLayout
public static string GetColumns(DependencyObject obj)
{
return (string)obj.GetValue(ColumnsProperty);
}
public static void SetColumns(DependencyObject obj, string value)
{
obj.SetValue(ColumnsProperty, value);
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.RegisterAttached("Columns", typeof(string), typeof(System.Windows.Controls.Grid),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Columns_PropertyChangedCallback));
public static void Columns_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as System.Windows.Controls.Grid;
var oldValue = e.OldValue as string;
var newValue = e.NewValue as string;
if (oldValue == null || newValue == null)
return;
if (oldValue != newValue)
{
grid.ColumnDefinitions.Clear();
newValue
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.SelectMany(Parse)
.Select(l => new ColumnDefinition { Width = l })
.ToList().ForEach(grid.ColumnDefinitions.Add);
}
}
#endregion
#region Rows
public static string GetRows(DependencyObject obj)
{
return (string)obj.GetValue(RowsProperty);
}
public static void SetRows(DependencyObject obj, string value)
{
obj.SetValue(RowsProperty, value);
}
// Using a DependencyProperty as the backing store for Rows. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RowsProperty =
DependencyProperty.RegisterAttached("Rows", typeof(string), typeof(System.Windows.Controls.Grid),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Rows_PropertyChangedCallback));
public static void Rows_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as System.Windows.Controls.Grid;
var oldValue = e.OldValue as string;
var newValue = e.NewValue as string;
if (oldValue == null || newValue == null)
return;
if (oldValue != newValue)
{
grid.ColumnDefinitions.Clear();
newValue
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.SelectMany(Parse)
.Select(l => new RowDefinition { Height = l })
.ToList().ForEach(grid.RowDefinitions.Add);
}
}
#endregion
#region Cell
public static string GetCell(DependencyObject obj)
{
return (string)obj.GetValue(CellProperty);
}
public static void SetCell(DependencyObject obj, string value)
{
obj.SetValue(CellProperty, value);
}
// Using a DependencyProperty as the backing store for Cell. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CellProperty =
DependencyProperty.RegisterAttached("Cell", typeof(string), typeof(UIElement),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Cell_PropertyChangedCallback));
public static void Cell_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
var oldValue = e.OldValue as string;
var newValue = e.NewValue as string;
if (oldValue == null || newValue == null)
return;
if (oldValue != newValue)
{
var rowAndColumn = newValue.Split(new char[] { ' ', ';' });
var row = int.Parse(rowAndColumn[0]);
var column = int.Parse(rowAndColumn[1]);
System.Windows.Controls.Grid.SetRow(element, row);
System.Windows.Controls.Grid.SetColumn(element, column);
}
}
#endregion
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Tuesday, March 27, 2012

GitHub Gist

Love sharing? Are you a social coder? A lot of times you may wanted to share piece of code but you give up just because it was going to be a cumbersome process to create a project on CodePlex or GitHub.
I just see a simple feature of GitHub, named Gist. It's a simple method for sharing your code that you find useful for others. For example, having to implement online payment functionality for my Asp.Net MVC website I ended up with a simple PaymentController which I just shared it with GitHub.
Here is my first gist:

// WARNING : THIS CODE IS AS IS. NOTHING IS GUARANTEED.
// PLEASE NOTE THAT THIS IS ONLY A TEMPLATE, YOU NEED TO WORK ON IT TO MAKE IT SUITABLE FOR YOUR NEEDS
// AT LEAST YOU NEED TO REPLACE THE VALUE FOR HASH SECRET VARIABLES BY VALUES YOU RECEIVED FROM YOUR BANK
// For more information go to : http://samondotnet.blogspot.com/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
public class PaymentController : Controller
{
public ActionResult OrderConfirm()
{
return View();
}
[HttpGet]
public ActionResult InitiatePayment()
{
try
{
#region parameters
var VPC_URL = "https://migs.mastercard.com.au/vpcpay";
var paymentRequest = new PaymentRequest
{
Amount = "100",
ReturnUrl = "http://YOURDOMAINNAMECOMESHERE.com.au/payment/paymentconfirm",
OrderInfo = "Visa Assessment",
};
// SECURE_SECRET can be found in Merchant Administration/Setup page
string hashSecrest = "FE7555C9D2C35C2552934E8DB1D73D43";
#endregion
#region redirect to payment gateway
var transactionData = paymentRequest.GetParameters().OrderBy(t => t.Key, new VPCStringComparer()).ToList();
// Add custom data, transactionData.Add(new KeyValuePair<string, string>("Title", title));
var redirectUrl = VPC_URL + "?" + string.Join("&", transactionData.Select(item => HttpUtility.UrlEncode(item.Key) + "=" + HttpUtility.UrlEncode(item.Value)));
if (!string.IsNullOrEmpty(hashSecrest))
redirectUrl += "&vpc_SecureHash=" + PaymentHelperMethods.CreateMD5Signature(hashSecrest + string.Join("", transactionData.Select(item => item.Value)));
return Redirect(redirectUrl);
#endregion
}
catch (Exception ex)
{
var message = "(51) Exception encountered. " + ex.Message;
return View("PaymentError", ex);
}
}
public ActionResult PaymentConfirm()
{
try
{
// SECURE_SECRET can be found in Merchant Administration/Setup page
string hashSecrest = "FE7555C9D2C35C2552934E8DB1D73D43";
var secureHash = Request.QueryString["vpc_SecureHash"];
if (!string.IsNullOrEmpty(secureHash))
{
if (!string.IsNullOrEmpty(hashSecrest))
{
var rawHashData = hashSecrest + string.Join("", Request.QueryString.AllKeys.Where(k => k != "vpc_SecureHash").Select(k => Request.QueryString[k]));
var signature = PaymentHelperMethods.CreateMD5Signature(rawHashData);
if (signature != secureHash)
return View("Error", new ApplicationException("Invalid request."));
}
}
var vpcResponse = new PaymentResponse(Request);
return View(vpcResponse);
}
catch (Exception ex)
{
var message = "(51) Exception encountered. " + ex.Message;
return View("Error", ex);
}
}
}
public class VPCStringComparer : IComparer<string>
{
public int Compare(string x, string y)
{
var myComparer = CompareInfo.GetCompareInfo("en-US");
return myComparer.Compare(x, y, System.Globalization.CompareOptions.Ordinal);
}
}
public class PaymentHelperMethods
{
public static string getResponseDescription(string vResponseCode)
{
string result = "Unknown";
if (vResponseCode.Length > 0)
{
switch (vResponseCode)
{
case "0": result = "Transaction Successful"; break;
case "1": result = "Transaction Declined"; break;
case "2": result = "Bank Declined Transaction"; break;
case "3": result = "No Reply from Bank"; break;
case "4": result = "Expired Card"; break;
case "5": result = "Insufficient Funds"; break;
case "6": result = "Error Communicating with Bank"; break;
case "7": result = "Payment Server detected an error"; break;
case "8": result = "Transaction Type Not Supported"; break;
case "9": result = "Bank declined transaction (Do not contact Bank)"; break;
case "A": result = "Transaction Aborted"; break;
case "B": result = "Transaction Declined - Contact the Bank"; break;
case "C": result = "Transaction Cancelled"; break;
case "D": result = "Deferred transaction has been received and is awaiting processing"; break;
case "F": result = "3-D Secure Authentication failed"; break;
case "I": result = "Card Security Code verification failed"; break;
case "L": result = "Shopping Transaction Locked (Please try the transaction again later)"; break;
case "N": result = "Cardholder is not enrolled in Authentication scheme"; break;
case "P": result = "Transaction has been received by the Payment Adaptor and is being processed"; break;
case "R": result = "Transaction was not processed - Reached limit of retry attempts allowed"; break;
case "S": result = "Duplicate SessionID"; break;
case "T": result = "Address Verification Failed"; break;
case "U": result = "Card Security Code Failed"; break;
case "V": result = "Address Verification and Card Security Code Failed"; break;
default: result = "Unable to be determined"; break;
}
}
return result;
}
public static string displayAVSResponse(string vAVSResultCode)
{
string result = "Unknown";
if (vAVSResultCode.Length > 0)
{
if (vAVSResultCode.Equals("Unsupported"))
{
result = "AVS not supported or there was no AVS data provided";
}
else
{
switch (vAVSResultCode)
{
case "X": result = "Exact match - address and 9 digit ZIP/postal code"; break;
case "Y": result = "Exact match - address and 5 digit ZIP/postal code"; break;
case "S": result = "Service not supported or address not verified (international transaction)"; break;
case "G": result = "Issuer does not participate in AVS (international transaction)"; break;
case "A": result = "Address match only"; break;
case "W": result = "9 digit ZIP/postal code matched, Address not Matched"; break;
case "Z": result = "5 digit ZIP/postal code matched, Address not Matched"; break;
case "R": result = "Issuer system is unavailable"; break;
case "U": result = "Address unavailable or not verified"; break;
case "E": result = "Address and ZIP/postal code not provided"; break;
case "N": result = "Address and ZIP/postal code not matched"; break;
case "0": result = "AVS not requested"; break;
default: result = "Unable to be determined"; break;
}
}
}
return result;
}
public static string displayCSCResponse(string vCSCResultCode)
{
string result = "Unknown";
if (vCSCResultCode.Length > 0)
{
if (vCSCResultCode.Equals("Unsupported"))
{
result = "CSC not supported or there was no CSC data provided";
}
else
{
switch (vCSCResultCode)
{
case "M": result = "Exact code match"; break;
case "S": result = "Merchant has indicated that CSC is not present on the card (MOTO situation)"; break;
case "P": result = "Code not processed"; break;
case "U": result = "Card issuer is not registered and/or certified"; break;
case "N": result = "Code invalid or not matched"; break;
default: result = "Unable to be determined"; break;
}
}
}
return result;
}
public static System.Collections.Hashtable splitResponse(string rawData)
{
System.Collections.Hashtable responseData = new System.Collections.Hashtable();
try
{
if (rawData.IndexOf("=") > 0)
{
// Extract the key/value pairs for each parameter
foreach (string pair in rawData.Split('&'))
{
int equalsIndex = pair.IndexOf("=");
if (equalsIndex > 1 && pair.Length > equalsIndex)
{
string paramKey = System.Web.HttpUtility.UrlDecode(pair.Substring(0, equalsIndex));
string paramValue = System.Web.HttpUtility.UrlDecode(pair.Substring(equalsIndex + 1));
responseData.Add(paramKey, paramValue);
}
}
}
else
{
responseData.Add("vpc_Message", "The data contained in the response was not a valid receipt.<br/>\nThe data was: <pre>" + rawData + "</pre><br/>\n");
}
return responseData;
}
catch (Exception ex)
{
// There was an exception so create an error
responseData.Add("vpc_Message", "\nThe was an exception parsing the response data.<br/>\nThe data was: <pre>" + rawData + "</pre><br/>\n<br/>\nException: " + ex.ToString() + "<br/>\n");
return responseData;
}
}
public static string CreateMD5Signature(string RawData)
{
var hasher = System.Security.Cryptography.MD5CryptoServiceProvider.Create();
var HashValue = hasher.ComputeHash(Encoding.ASCII.GetBytes(RawData));
return string.Join("", HashValue.Select(b => b.ToString("x2"))).ToUpper();
#region commented code
//string strHex = "";
//foreach(byte b in HashValue)
//{
// strHex += b.ToString("x2");
//}
//return strHex.ToUpper();
#endregion
}
}
public class PaymentRequest
{
public PaymentRequest()
{
Version = "1";
Command = "pay";
AccessCode = "";
MerchTxnRef = "";
Merchant = "";
OrderInfo = "";
Amount = "100";
ReturnUrl = "http://YOURDOMAINNAMECOMESHERE.com.au/payment/paymentconfirm";
Locale = "en";
}
public PaymentRequest(int dollars, int cents, string orderInfo)
: this()
{
this.Amount = (dollars * 100 + cents).ToString();
this.OrderInfo = orderInfo;
}
public string Version { get; set; }
public string Command { get; set; }
public string AccessCode { get; set; }
public string MerchTxnRef { get; set; }
public string Merchant { get; set; }
public string OrderInfo { get; set; }
public string Amount { get; set; }
public string ReturnUrl { get; set; }
public string Locale { get; set; }
public Dictionary<string, string> GetParameters()
{
var parameters = new Dictionary<string, string> {
{ "vpc_Version" ,Version},
{ "vpc_Command",Command},
{ "vpc_AccessCode" ,AccessCode},
{ "vpc_MerchTxnRef" ,MerchTxnRef},
{ "vpc_Merchant" ,Merchant},
{ "vpc_OrderInfo",OrderInfo},
{ "vpc_Amount" ,Amount},
{ "vpc_ReturnURL", ReturnUrl},
{ "vpc_Locale",Locale}
};
return parameters;
}
}
public class PaymentResponse
{
#region common properties
public string ResponseCode { get; set; }
public string ResponseCodeDescription { get; set; }
public string Amount { get; set; }
public string Command { get; set; }
public string Version { get; set; }
public string OrderInfo { get; set; }
public string MerchantID { get; set; }
#endregion
#region on-successful-payment properties
public string BatchNo { get; set; }
public string CardType { get; set; }
public string ReceiptNo { get; set; }
public string AuthorizeID { get; set; }
public string MerchTxnRef { get; set; }
public string AcqResponseCode { get; set; }
public string TransactionNo { get; set; }
#endregion
public string Message { get; set; }
public PaymentResponse(HttpRequestBase Request)
{
Func<string, string> GetQueryStringValue = key =>
{
if (Request.QueryString.AllKeys.Contains(key))
{
var result = Request.QueryString[key];
return result;
}
return "Unknown";
};
// Get the standard receipt data from the parsed response
this.ResponseCode = GetQueryStringValue("vpc_TxnResponseCode");
this.ResponseCodeDescription = PaymentHelperMethods.getResponseDescription(ResponseCode);
this.Amount = GetQueryStringValue("vpc_Amount");
this.Command = GetQueryStringValue("vpc_Command");
this.Version = GetQueryStringValue("vpc_Version");
this.OrderInfo = GetQueryStringValue("vpc_OrderInfo");
this.MerchantID = GetQueryStringValue("vpc_Merchant");
// only display this data if not an error condition
if (this.ResponseCode.Equals("7"))
{
this.BatchNo = GetQueryStringValue("vpc_BatchNo");
this.CardType = GetQueryStringValue("vpc_Card");
this.ReceiptNo = GetQueryStringValue("vpc_ReceiptNo");
this.AuthorizeID = GetQueryStringValue("vpc_AuthorizeId");
this.MerchTxnRef = GetQueryStringValue("vpc_MerchTxnRef");
this.AcqResponseCode = GetQueryStringValue("vpc_AcqResponseCode");
this.TransactionNo = GetQueryStringValue("vpc_TransactionNo");
}
var message = GetQueryStringValue("vpc_Message");
}
}

Monday, March 26, 2012

Why Invalid Object Name 'dbo.__MigrationHistory'?


Unfortunately I am using  Entity Framework in my project. After migration support added to Entity Framework I thought using this feature may help me feel a little better about  Entity Framework . But the way migration is implemented in EF shows that nothing has changed. Migration has lots of problems and bugs.
After carefully doing all the required cumbersome procedures  Entity Framework migration failed to work and I found no workaround until I strangely faced the exception 'Invalid Object Name 'dbo.__MigrationHistory''. This exception give me  a clue about the cause of my problems.
It seems that Entity Framework team do not use appropriate testing methods and they only care about the main courses and are very negligent about alternative cases. For example in this case they presumed that always the default schema is dbo. It creates __MigrationHistory on the default schema, but queries for it on the dbo schema.
After a while I find another problem. Entity Framework also tries to insert rows into __MigrationHistory without specifying schema name.

Ok, I wrote this code to make Entity Framework script generator apply 'dbo.' before the __MigrationHistory. I know this is naive solution but at least it works.


// This is for scenarios in which your default schema name is not dbo and hence Entity Framework migration is not working properly.
// WARNING : Please be advised that this is a naive solution which its purpose is to just show how it could be corrected. You may not use this code without understanding and resolving any potential problem.
// For more information go to http://samondotnet.blogspot.com.au/2012/03/why-invalid-object-name.html
namespace ES.DataAccess.Migrations
{
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Model;
using System.Data.Entity.Migrations.Sql;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<ES.Domain.ExaminationDbContext>
{
public Configuration()
{
SetSqlGenerator("System.Data.SqlClient", new SamSqlGenerator());
AutomaticMigrationsEnabled = false;
}
}
public class SamSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(System.Data.Entity.Migrations.Model.CreateTableOperation createTableOperation)
{
if (createTableOperation.Name.Contains("__MigrationHistory") && !createTableOperation.Name.StartsWith("dbo."))
{
var newCreateTableOperation = new CreateTableOperation("dbo." + createTableOperation.Name);
createTableOperation.Columns.ToList().ForEach(newCreateTableOperation.Columns.Add);
base.Generate(newCreateTableOperation);
}
else
base.Generate(createTableOperation);
}
protected override void GenerateMakeSystemTable(System.Data.Entity.Migrations.Model.CreateTableOperation createTableOperation)
{
}
protected override void Generate(System.Data.Entity.Migrations.Model.InsertHistoryOperation insertHistoryOperation)
{
if (!insertHistoryOperation.Table.StartsWith("dbo."))
insertHistoryOperation = new InsertHistoryOperation("dbo." + insertHistoryOperation.Table, insertHistoryOperation.MigrationId, insertHistoryOperation.Model, null);
base.Generate(insertHistoryOperation);
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

A WPF Technique: Decorating controls by deriving from standard control

I always consider StackPanel as Vertical StackPanel as it is Vertical by default, but why there is no layout control that is horizontal by default?. I believe it does not worth for the WPF framework to introduce one as it is very easy to write if you need one. In this post I am going to describe how easy is to create a HorizontalStackPanel and a couple of related useful notes.

Define types by deriving from WPF controls.
The simplest way to write your own controls is to derive from existing ones. You do not need to do anything else. Yes, that it, you have the same control with the same functionalities but with a different Type. For example you may derive a class from TextBlock, it still is a TextBlock but it has its own specific type, so it is behave in a slightly different in some cases, mostly when you refer the types like in a TargetType of a Style.
Here is a summary of benefits of doing so:
  • Defining some common behaviour in code and not using the styles
  • Easier targeting them in styles
  • More readable XAML code
  • Very helpful for code sharing between Silverlight and WPF
HorizontalStackPanel as an example
For example you may define a HorizontalStackPanel by deriving a type from StackPanel and setting its Orientation to Horizontal in its constructor. Actually it is not a horizontal stackpanel but it is InitialyHorizontalStackPanel as the users have the opportunity to change the orientation later using XAML or code.
Also the HorizontalAlignment in StackPanel has a default value of Stretch, you need to set it back to Left and do the same for VerticalAlignment.


public class Horizontal : StackPanel
{
    public Horizontal()
    {
        Orientation = Orientation.Horizontal;
    }
}
public class Vertical : StackPanel
{
    public Vertical()
    {
        Orientation = Orientation.Vertical;
    }
}


So now you can write these XAML code

<StackPanel>
  <StackPanel Orientation="Horizontal">
      
  </StackPanel>
</StackPanel>

This way

<s:Vertical>
  <s:Horizontal>
      
  </s:Horizontal>
</s:Vertical>

Which is much more readable and less cluttered.