2017 m. balandžio 11 d., antradienis

A simple way to track You application usage with DeskMetrics

Mqueue Viewer
Recently I've embarked on a journey to create my first public product - a tool for people who need to work with MSMQ messages and queues.

Here's what we managed to build https://www.mqueue.net/

A new journey - always means one thing  - a lot of unknowns and unexpected things happening, that need to be dealt with here and now. Below is such a list of problems :

  1. How to let people know - such thing exists ? 
  2. How to know if people use it ? 
    1. How often ? 
    2. From where ? 
  3. How people will pay for it ? 
  4. How to license it - and activate or deactivate features ? 
  5. How to interact with Your customers and follow up on their purchase ? 
One would expect - that for each one of these - one could easily find a product and use it. 
And of course - it's not as simple as it seems.

One particular product that stands out with it quality, ease of use and customer service that I discovered in the process and would like to describe in more detail - is 
https://deskmetrics.com/


The problem that it solves is #2. "Are people is using Your software? If so - how often ?".
Why is it a problem in the first place (one might ask) ? Just use google analytics (one might say).


In my case - to know how many people use the software, and how often - DeskMetrics was a time saver. A simple .dll, nice and clear instructions and documentation on how to plug it in and how to use it and a well thought out dashboard that gave me more than I could think of myself - with a few clicks of the button.

Why not Google ? Simple really :

  • One needs to learn their api (how to call them ect)
  • Setup process requires knowledge and time

Also - try searching for "desktop application usage metrics".. All I found was one more product...


For a new tool - the simple ability to see how many people are installing the tool, and how many of them uninstall - provides valuable insight on what's going on in the real world.

Combine that with the application crash statistics (containing details stack trace data) and it's almost like You don't need any other tool to know how Your application is doing.

Given that I only used 30% of the functionality offered - I'm quite excited to get to know the product better and see how else it may help me grow my sales



2013 m. gruodžio 17 d., antradienis

Devexpress 13.2 Review - why bother upgrading ?

In my experience, many programmers are careful about upgrading projects to the new versions, especially when they have a application running stable for a period of time.

I'll try to upgrade an existing DX 12.x ERP/CMMS application to v13.2 and share my experiences with You, so You can know what to expect, and to answer 2 basic questions:
  • What's the pain of the upgrade ? 
  • What do You actually Gain from the upgrade

First - I want to point out one very useful feature I've been waiting for ages , and one that has been requested by most of my clients: XAF "Soft Validation". That's the first "bonus" and gain in DX 13.2



One more feature I love - You no longer need to go looking for a "project converter", it's directly in VS !!!
Woo-hoo :) and it's got new look-n-feel, which is also very nice. Plus some extra features like project list to upgrade.

The Bad: You'll need to upgrade to .Net 4+
So if You're using Click-Once or DxUpgrade machine - You need to find a way of getting .Net upgraded on Your client machines (10's, 100's of them ?). For the time being that's a show stopper for me

If you have  a solution for this - plz share it in the comments (meaning - how do You make .Net Framework updates install together with application updates ? )












After the upgrade: few errors - that's expected, but really - all pretty 'basic', and can be fixed in few minutes. So breaking changes - absolutely minimal.

like namespace change for "DevExpress.Xpo.PersistentCriteriaEvaluationBehavior" -> to -> "DevExpress.Persistent.Validation.CriteriaEvaluationBehavior" 
or
"No overload for method 'ValidateAll' takes 2 arguments " - that just ask You yo add a reference to specific Objectspace .


And - Hooray - The app is running. Didn't expect that :) 1+ for Devexpress team for making the transition this fluent.
______________________________________________________________________________

So - Let's try out this 'Soft Validation' and see how it goes, and what's it worth :)


I already had an object with a Property "Description" created, and a RuleRequiredField set on it.
So All I had to do is just add the "ResultType" to the attribute descriptor. Really nice !

No breaking changes, no re-factoring, just an additional overload on the attribute constructor. 


Now, lets hit F5, and see how it works. 
I get a standard notification about the error, but it is transformed , and now has a "Ignore" button on it, allowing me to bypass the validation. 
All the icons are there, Warning signs ect.. and all this - with 1 line of code !!!

Great ! This feature alone, made my day and bought me to do the upgrade.

If You're interested in more details about this feature here's a nice post with details about WEB, Info type ect http://bit.ly/1edBp4z  and one more here.



Next comes the "Easy custom members"
So my example on this one - let's say we have a list of "Work Orders", where all of them have a field "Date Created", but what I actually want to know as a business user - is How long has that "order" been opened ? 

Well - I have to say, it's a nice way to add a calculated property, without recompile and stuff

But.. unfortunatelly..

From the business user perspective - I doubt that a person with Zero programming skills can figure this out, meaning - understand that what he or she actually needs is a DateDiff(x, y), by day, and add a proper properties to predefined place holders

From programmers perspective - well.. What I would like to do, is save this modification to the 'base' model, and have it propagated to all the application users. 



This (propagation of model changes) of course is do-able - by exploiting Expand ModelDifference but that's a topic for another post;

Verdict: This is a strong step in right direction, of enabling run-time application customization, though it might requite significant effort.


Reports 2.0
How's this useful ?

The good:

  • it allows You to 'join/merge' predefined reports made by You in VS, with 'Xaf' reports, and show them under the same 'Reports' button. More about this functionality in this video and this post 'runtime integration'. (I personally this this is great. I've personally spent way to many hour on this integration and this is really really great in terms of user experience !)
  • and what's even more exiting - it let's You edit (a copy) of the predefined report !
  • and (this is more than awesome) - You can move Your scripts to C# file - hooray :) You know longer have to code script's blindly, without the intelisense and all the help of VS environment !


Cons:

  • You can't have both v.1 and v.2 simultaneously
  • Migration support from v.1 to v.2 could be better



Let's start by Adding the new module.
Notice - the new Icon, so it's easy to recognize

Few issues on the road:

  • You have to remove existing 'Reports module' 
  • You have to export Your existing reports, before adding the new module

Verdict : I must say the 'gain' of the module is pretty huge. It comes with some trouble, but in my personal opinion - it's worth it. 

XPView Support for XPObjectSpace 

Finally :) I'm beginning to repeat myself, but still, I must mention this feature, as it has huge impact on the XAF application performance. 

Why ? 
Because - as soon as You get past the first few iteration, and the application starts it life, most probably it's gonna start accumulating data quite quickly. 
What does that mean ? - It means that as soon as You have passed the barrier of the initial application adoption by the client, the next big thing is data analysis, reporting and displaying 'the same' data in various 'Views', various 'directions', various 'cuts' or 'aggregations'

And this is where XpView supports comes in very handy. According to this blog post  makes a difference from 20.000 ms   down to 9 ms. This sounds like a huge gain for me !


"Merge Changes"  

This one - is one more for the user. And for the support team, who's likely to get much less calls from the clients with text "I can't save my changes, what do I do ? "

I won't get into much detail about this module, as there's a quite descriptive Blog here and a pro video here

It's simple to implement, simple to use, and has a potentially big impact in support, especially for the new XAF users.

Final word

What I've found so far:

  • The upgrade is as smooth as possible
  • 13.2 has modules, that solve 'huge' problems, though some might require some effort to implementing
  • You should definitely Do it ! 





2011 m. gegužės 25 d., trečiadienis

How to Include eXpandFramework module into existing XAF application

 

This post describes how to add a feature from eXpandFramework into an existing XAF application.


1. Download the latest sources or binaries from here

Make sure that DevExpress engine that You're using matches with the one that eXpand was built on

 
2. In Case You've decided to download Source's - You'll need to build them first.

First make sure thet DX version matches (if not run the DX Project converter tool)

Then run buildall32bit.cmd or buildall64bit.cmd depending on Your system.

You'll find your build results  in Xpand.DLL folder

 
3. Add necessary references to Your project.

In this case : How to Add Excel import Wizard module to an Existing XAF application.
Add references, in Your main module, to:
  • Xpand.ExpressApp.dll
  • Xpand.ExpressApp.ImportWiz.dll
   Add reference, in Your Win module, to:
  • Xpand.ExpressApp.dll
  • Xpand.ExpressApp.Win.dll
  • Xpand.ExpressApp.ImportWiz.Win.dl


Add following code to Module.Designer.cs 
//
this.RequiredModuleTypes.Add(typeof(Xpand.ExpressApp.ImportWiz.ImportWizModule))
//
Add folowing code to WinModule.Designer.cs

//
this.RequiredModuleTypes.Add(typeof(ImportWizWinModule));
//


4. Run the application and use the New Module :)



Here's a blog post with video on How it Works
and a sample Solution that You can Download

2011 m. gegužės 24 d., antradienis

2011 m. kovo 3 d., ketvirtadienis

TimeSpan Editor for XAF

Here's a simple solution for entering TimeSpan data in a casual style : " 1 day 39 hours 78 min"


The Solutions is to create a prpoperty editor that hosts simple Text editor, with some custom logic undeneath, that parses the string and converts it to TimeSpan after entering the string. And also Convert the timespan form the database to string, to be displayed in the casual format.

using System;
using System.Text.RegularExpressions;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Win.Editors;
using DevExpress.XtraEditors.Mask;
using DevExpress.XtraEditors.Repository;
 
namespace Solution2.Module.Win
{
    [PropertyEditor(typeof(TimeSpan))]
    public class DurationPropertyEditor : DXPropertyEditor
    {
        public DurationPropertyEditor(Type objectType, IModelMemberViewItem model)
            : base(objectType, model)
        {
            
        }
 
        protected override object CreateControlCore()
        {
            return new StringEdit();
        }
 
        protected override void SetupRepositoryItem(RepositoryItem item)
        {
            base.SetupRepositoryItem(item);
 
            ((RepositoryItemStringEdit)item).Mask.MaskType = MaskType.RegEx;
            ((RepositoryItemStringEdit)item).Mask.EditMask
                = @"\s*((\d?\d?\d?\s*(d(ays?)?)))?\s*((\d?\d?\s*(h(ours)?)?))?\s*(\d?\d?\s*(m(in(utes)?)?)?)?";
 
            if (Control == null) return;
 
            Control.ShowToolTips = true;
            Control.ToolTip =
                " Examples:  " + Environment.NewLine +
                " 1d                     = 1 Day" + Environment.NewLine +
                " 1 day                  = 1 Day" + Environment.NewLine +
                " 2d 5h 45 m             = 2 Days 5 Hours 45 minutes" + Environment.NewLine +
                " 2 days 4 hours 25 min  = 2 Days 4 Hours 25 minutes" + Environment.NewLine
;
            Control.EditValueChanged += Control_EditValueChanged;
        }
 
        void Control_EditValueChanged(object sender, EventArgs e)
        {
            WriteValue();
            OnControlValueChanged();
        }
 
        protected override object GetControlValueCore()
        {
            return ParseTimeSpan(Control.Text);
        }
        
        protected override void ReadValueCore()
        {
            Control.EditValue = DecodeTimeSpan((TimeSpan) PropertyValue);
            
        }
 
     
        public static TimeSpan ParseTimeSpan(string s)
        {
            const string Quantity = "quantity";
            const string Unit = "unit";
 
            const string Days = @"(d(ays?)?)";
            const string Hours = @"(h((ours?)|(rs?))?)";
            const string Minutes = @"(m((inutes?)|(ins?))?)";
            const string Seconds = @"(s((econds?)|(ecs?))?)";
 
            var timeSpanRegex = new Regex(
                string.Format(@"\s*(?<{0}>\d+)\s*(?<{1}>({2}|{3}|{4}|{5}|\Z))",
                              Quantity, Unit, Days, Hours, Minutes, Seconds),
                              RegexOptions.IgnoreCase);
            var matches = timeSpanRegex.Matches(s);
 
            var ts = new TimeSpan();
            foreach (Match match in matches)
            {
                if (Regex.IsMatch(match.Groups[Unit].Value, @"\A" + Days))
                {
                    ts = ts.Add(TimeSpan.FromDays(double.Parse(match.Groups[Quantity].Value)));
                }
                else if (Regex.IsMatch(match.Groups[Unit].Value, Hours))
                {
                    ts = ts.Add(TimeSpan.FromHours(double.Parse(match.Groups[Quantity].Value)));
                }
                else if (Regex.IsMatch(match.Groups[Unit].Value, Minutes))
                {
                    ts = ts.Add(TimeSpan.FromMinutes(double.Parse(match.Groups[Quantity].Value)));
                }
                else if (Regex.IsMatch(match.Groups[Unit].Value, Seconds))
                {
                    ts = ts.Add(TimeSpan.FromSeconds(double.Parse(match.Groups[Quantity].Value)));
                }
                else
                {
                    // Quantity given but no unit, default to Hours
                    ts = ts.Add(TimeSpan.FromHours(double.Parse(match.Groups[Quantity].Value)));
                }
            }
            return ts;
        }
 
        public static string DecodeTimeSpan(TimeSpan timeSpan)
        {
 
            var time = string.Empty;
 
            if (timeSpan.Days > 0)
                time = timeSpan.Days + " Days";
            
            
            if (timeSpan.Hours > 0)
                time += (time != string.Empty ? " " : "") + timeSpan.Hours + " Hours";
 
            
            if (timeSpan.Minutes > 0)
                time += (time != string.Empty ? " " : "") + timeSpan.Minutes + " Minutes";
 
            return time;
        }
    }
}



2010 m. rugpjūčio 23 d., pirmadienis

Merge similar referenced objects controller for XAF

I've noticed that modern application usually have Dirty dictionaries
and there are no means of cleaning those dictionaries.

What do i mean by Dirty dictionaries ? here's a sample:

You have a list of whatever type of Objects, and You what to group them by one of the fields.
In this situation We're trying to group data object by property "Class". Here's what we get:




































 If we take a look at the "Dictionary", here's what we get:

Look's like that classe`es are identical... but.
What You'd what in such situation is somehow "Join" the 2 instances. An inexperienced user would simply delete one of them. But that doesn't solve the problem.

A clever SQL script here is not really possible. Why ?
Because it's after 5 years of working with SQL I have no idea how to write simple a script to find all objects using this dictionary value. It is possible of course. but.. I've found a better way :)







Here's how it works.
1. You go the the dictionary. Select object's that You want to Join and click Merge:























Then You Get a screen that Show's what will be merged, and asks You to select one that You want to stay:
Click OK, and The Controller collects (finds) ALL referencing objects that use one of the values being merged (Joined), and replaces them with the correct value.

2010 m. rugpjūčio 18 d., trečiadienis

Excel Importer for Express Application Framework (XAF)

One more common situation when working with XAF is that in many cases You usually get a lot of data in Excel SpreadSheets,You need to import to Your New Application.

So instead of writing a separate controller for each different excel file, o forcing people to use some predefined template, here what I came up with.


Wizard Page 1 lets You select Excell 2007 or newer file, select a Sheet that You want to import data from and see a PreView of the Data:


Next You can Map Excel columns to Your object Properties (or Let the Import Wizard try to Guess the mappings)


And See the progress of data import that is done in a separate thread.

MultiEdit component for DevExpress Express Application Framework (XAF)

It is a common situation, that You want to update multiple objects.
DevExpress recommends writing a controller for each and every time You want to do so.

Well, i don't think so. I've implemented a "MultiEdit" controller, so one can easily update a list of objects.


Say You have a list of objects - like this :

and You want to update Fields like "Design pressure" for all selected objects.
Of course You can open each of them, and change them one by one. But :)

Select "MultiEdit" - that shows a Detail View for a objects selected, change the property You want, and press OK



It then Show's You what will be changed, and the progress of operation if very many objects are being updated.


TP.Shell.XAF - ERP for manufacturing facilities

TP.Shell.XAF - ERP application, with unique capabilities such like integrated ACad, Visio, Intergraph drawing Viewer, predefined custom industry specific data engineering data classes and other.

Few screenshots of the application:




Leak Detection and Repair Dashboard:



Risk Based Inspection (interactive questionnaire and Risk Matrix)


Torque Calculator


Industry specific Valve Standard object

2010 m. rugpjūčio 16 d., pirmadienis

Co2 Emission calculator

This is a small tool that I did according specific client requirements.
Tool is based on the "Carbon Footprint" method, which gives magnitudes, not the exact figures, of the carbon emission linked to the activity of a company, taking into account electricity production, transportation, raw material production, waste treatment, etc.
The objective was to supply the company with recommendations to mitigate Greenhouse Gas emissions at lowest cost.


Key features of the software:
  • Portable (single executable)
    • Doesn't require .Net framework 3.5 or SQL engine to be installed on the client machine, though both are being used by the software (which is fantastic for the developer and the customer)
    • Can be run by lowest privilege user on a Windows PC (doesn't require elevated privileges)
  • Lightweight (all the software with SQL and .Net 3.5 framework embedded inside the executable - weighs only ~60Mb)
  • Rich and Intuitive User Experience (DevExpress components)
  • MultiLanguage interface
Technology used:
  • ORM (XPO and Entity Framework)
  • Application Virtualization (XenoCode)
ScreenShots:







2010 m. vasario 2 d., antradienis

Whats this blog about ?

Well the whole idea of this blog is to post info about software I've created or have participated in the process of creation.