Monday, 5 June 2017

A simple PIVOT in c# built on the ExpandoObject

In many applications you have to make the decision whether to add numerous analysis fields onto an entity (effectively limiting the number and type available) or use a couple of extra tables, one for the analysis field details and one for the contents of these fields.

For the Nominal Accounts in ossAccounts I have chosen the latter which gives anyone taking on the system far more flexibility but with a little more complexity (mostly for me since they will have a simplified API to work with in the end).

This is fine for general processing but there are times when you wish to display all of the additional categories as though they are part of the NominalAccount, for example a list of accounts.  After all this is how the user envisages the data.

In the past I have made extensive use of Visual Foxpro and could create pivots reasonable easily.  However it appears that doing so in c# is a completely different kettle of fish even if the team behind VFP created linq.  There appears to be very few solutions and they tend to be based on the properties of DataTables not the IEnumerables that I am using. Casting backwards and forwards to DataTables is do-able but not great.

My solution is a collection of ExpandoObjects that have had all the relevant properties added.  It isn't going to win any efficiency awards but for relatively small datasets is ok.

Cut down versions of the Entity classes...

public class NominalAccount : BaseEntity
{
    [Key]
    public int NomincalAccountID { get; set; }

    [...]
}

public class NominalCategory : BaseEntity
{
    [Key]
    public int NominalCategoryID { get; set; }

    [Required]
    [StringLength(30)]
    public string Description { get; set; }

    [...]
}

public class NominalCodeCategoryValue : BaseEntity
{
    [Key]
    public int NominalCodeCategoryValueID { get; set; }

    [Required]
    [StringLength(30)]
    public string CategoryValue { get; set; }

    [...]
}
   
internal Dictionary CreateExtendedNominalAccountObjects(List nominalCategories, List nominalAccounts, List nominalCategoryValues)
{
   // Create object based on Categories
   // index will be NominalAccountID
   var extendedNominalAccounts = new Dictionary();

   dynamic nominalObject = new ExpandoObject();

   // Add all the categories to the ExpandoObject
   foreach (var nominalCategory in nominalCategories)
   {
       ((ExpandoObject)nominalObject).AddProperty(nominalCategory.Description, "");
   }

   // Create dataset for nominals
   foreach (var nominalAccount in nominalAccounts)
   {
      var neo = ExpandoObjectExtensions.CloneExpando(nominalObject);

      ((ExpandoObject)neo).AddProperties(nominalAccount);

      extendedNominalAccounts.Add(nominalAccount.NomincalAccountID, neo);
   }

   // Loop through the Nominal Category details and update the appropriate nominal ExpandoObject

   foreach (var ncv in nominalCategoryValues)
   {
      var nomObj = extendedNominalAccounts[ncv.NominalAccountID];

      var categoryDesc = (from nc in nominalCategories
                    where nc.NominalCategoryID == ncv.NominalCategoryID
                    select new { nc.Description }).Single();

      nomObj.UpdateProperty(categoryDesc.Description, ncv.CategoryValue);

      extendedNominalAccounts[ncv.NominalAccountID] = nomObj;
    }

     return extendedNominalAccounts;
 }
The AddProperties, UpdateProperties extensions...

   public static class ExpandoObjectExtensions
    {
        public static void AddProperty(this ExpandoObject expando, string propertyName, object propertyValue)
        {
            // ExpandoObject supports IDictionary so we can extend it like this
            var expandoDict = expando as IDictionary;
            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                expandoDict.Add(propertyName, propertyValue);
        }

        public static void UpdateProperty(this ExpandoObject expando, string propertyName, object propertyValue)
        {
            // ExpandoObject supports IDictionary so we can extend it like this
            var expandoDict = expando as IDictionary;
            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                throw new SettingsPropertyNotFoundException("Property "+propertyName.Trim()+" missing.");
        }

        public static void AddProperties(this ExpandoObject expando, T obj)
        {
            var props = obj.GetType().GetProperties();

            foreach (var propertyInfo in props)
            {
                expando.AddProperty(propertyInfo.Name,propertyInfo.GetValue(obj));
            }
        }

        /// 
        ///  Creates a clone of an ExpandoObject
        ///  https://stackoverflow.com/q/22826610/82536
        /// 
        /// The object to clone
        /// A clone of the given ExpandoObject.
        public static ExpandoObject CloneExpando(this ExpandoObject expando)
        {
            var newExpando = new ExpandoObject();

            foreach (var kvp in (IDictionary)expando)
            {
                ((IDictionary)newExpando).Add(kvp);
            }

            return newExpando;
        }
    }

Monday, 27 February 2017

Fully tailorable form text (wpf)

How do you get fully tailorable text on a window using wpf (c#,xaml)?

Painfully!

There are functions built into the system by MS that are designed to deal with localisation problems but this is at the dll level.  What I need for ossAccounts is a simple method for the vertical market developer (or systems admin) to be able to edit a table and change any field, either to allow additional languages or just tailoring the solution.  For example some accounting systems use 'Nominal Ledger', others 'General Ledger' etc.

It is particularly problematic when working with datagrids since the headers can not be simply bound.

The following is using a modified version of the GenericCRUDControl by Abdulrahman Emad and  Muhammad Magdi (found here ) but should transfer to other grids easy enough.  While there may be other approaches (if someone knows a better one, please tell!) it does work.

<CustomDataGridColumn BindingExpression="Name" Width="Auto">
       <CustomDataGridColumn.Header>                
                <TextBlock Text="{Binding DataContext.F[Name], 
                                                     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
                                                     Mode=OneWay}"/>                
        </CustomDataGridColumn.Header>
</CustomDataGridColumn>

Where F[Name] is :-

public Dictionary F { get; set; } = new LocalisationDictionary();

on the DataContext class (in the above on the CRUDWorkspaceViewModel). Note that the way I am handling multiple windows means that the ancestor I need for the DataContext is a UserControl, yours may be different.

LocalisationDictionary then returns the appropriate text based on the string it receives, the users language etc.

public class LocalisationDictionary : Dictionary
{
     public new string this[string key] => "Foo_"+key; // Some method here.
}

Tuesday, 20 December 2016

Vertical market developers should only have to specialise in their vertical market.

Supplying a software solution for a vertical market is a demanding undertaking. Often the potential client base is very restricted and the margins available are small. This is even more so if their product targets a niche group.

Many of these packages were lovingly created by someone within (or working closely with) the market in question. These people saw a problem and believed, with their unique experience, they could find a solution.
More often than not the people who forged these solutions are experts in their field, not in the more generic fields of stock control and accounting. They may know how to use an accounting system but this is not their area of speciality.
This has generally led to two situations, the specialist creates their own accounts system which takes a lot of time and resources or they use an established third party product with its costs, limitations and demands (for example staff certification).
We believe we have a third solution.....
A flexible free, copyright free open source accounts package.
A solution that is reasonably tailorable out of the box but one that can be amended (even re-branded!) by the vertical market solution provider in any way they want! If they just want the menuing system, take the menuing system! If they want to build their product seamlessly into our system, go right ahead!
What does this give them?
  • Allows them to focus on their specialist area(s). 
  • Gives them the flexibility to amend the system to fit their needs. 
  • Reduces development costs as no licence fees and no backdoor staff certification.
  • Reduce the costs passed on to their customers making them more competitive or increasing margins.
  • No risk of the system becoming unavailable forcing them to redevelop onto another platform.

Data not displaying in Datagrid but columns do when using C#, WPF, XAML and a Struct collection for the itemsource

In the ossAccounts system I had the following local struct defined to make it easier to display some information (linq left outer join) on a DataGrid.

        public struct ListItem
        {
            public int              OrganisationAnalysisCodeId;
            public string         Code;
            public bool           Used;
            public DateTime  StartDate;
            public string         StringValue;
            public float           NumberValue;
            public bool           BoolValue;
        }

While the DataGrid could see the columns and the number of rows all the cells remained empty!

After a day trying everything I could think of I eventually found that adding the {get; set;} to the end of each member sorted out the problem.

        public struct ListItem
        {
            public int              OrganisationAnalysisCodeId  {get; set;}
            public string         Code  {get; set;}
            public bool           Used  {get; set;}
            public DateTime  StartDate  {get; set;}
            public string         StringValue  {get; set;}
            public float           NumberValue  {get; set;}
            public bool           BoolValue  {get; set;}
        }

I am guessing that it has something to do with accessibility levels but that is purely a guess.

Anyway, I hope this will spare someone from hours of headbanging.


Friday, 3 January 2014

CA Etrust 8.1 Gui not starting due to iTechnology iGateway error

The following is a forum post taken from a long dead spiceworks thread... saved for posterity and those of us still using old software!

The iTechnology iGateway 4.2 service would not start with error 7034.

LilJon Feb 23, 2012 at 5:30 AM
The solution to this problem, is to reinstall Java Runtime.
Even if you think your Java Runtime is installed fine, reinstall it and choose the "repair" option.
Reboot your PC/server, and the iTechnology Gateway error should disappear.
This will then stop it "hanging" for a minute or two during logon.
Hope this info is helpful :)

Much thanks to LilJon.

Monday, 24 September 2012

Android app using LibGdx cant find activity or Game class from startup activity

A couple of quick notes.

The first is to really make sure your manifest is correct and it seems to me that not using the . shortcut in the Android:Name tag seems to be more reliable.

Secondly, if you find you can't access your game class (but you dont get a compile error) make sure you are paying attention to this line in on the Libgdx project setup page...

Note: Java 1.7 is currently problematic in combination with Android. Please make sure your project uses Java 1.6

Thursday, 26 July 2012

Error 1606. Could not access network location \Hewlett-Packard\\.

This took a while to sort out on a new system!

The following is the fix taken from Espol on the MediaBrower web site.

  • Open regedit goto HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall if there is an InstallLocation string listed there (not within any of the folders under Uninstall), just delete it 
  • If you have a 64bit Windows, also goto HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall repeat the same thing here.

It was at this second location I found the offending entry. So many thanks to Espol.