Blogs

Gary's Blog

February 2010 - Posts

  • Keeping Fit With XAF #2

         

    In the first post in this series we looked at creating a WeightRecord. Today, we are going to look at adding TrainingProgrammes and TrainingRecords. First, however, we are going to tidy up the WeightRecord from last time and implement a suggestion from Chris Royle, to add BMI to the class.

    So once we’ve added BMI and take input of height in metres and weight in kilos, our class now looks like this:

    using System;
    using DevExpress.Xpo;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    
    namespace Solution12.Module
    {
        [DefaultClassOptions]
        public class WeightRecord : BaseObject
        {
            public WeightRecord(Session session) : base(session) { }
    
            private DateTime _Date;
            public DateTime Date
            {
                get
                {
                    return _Date;
                }
                set
                {
                    SetPropertyValue("Date", ref _Date, value);
                }
            }
    
            private double _HeightInMetres;
            public double HeightInMetres
            {
                get
                {
                    return _HeightInMetres;
                }
                set
                {
                    SetPropertyValue("HeightInMetres", ref _HeightInMetres, value);
                }
            }
    
            private int _WeightInKilos;
            public int WeightInKilos
            {
                get
                {
                    return _WeightInKilos;
                }
                set
                {
                    SetPropertyValue("WeightInKilos", ref _WeightInKilos, value);
                }
            }
            
            [Persistent]
            public double BMI
            {
                get
                {
                    return (WeightInKilos == 0 || HeightInMetres == 0) ? 0 :
                        WeightInKilos / (HeightInMetres * HeightInMetres);
                }
            }
        }
    }

    Now we want to add two more classes. TrainingProgramme which will record activity on a particular exercise machine and TrainingRecord, which will record the date on which a particular TrainingProgramme was executed. These two classes look like this:

    using System;
    using DevExpress.Xpo;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    
    namespace Solution12.Module
    {
        [DefaultClassOptions]
        public class TrainingProgramme : BaseObject
        {
            public TrainingProgramme(Session session) : base(session) { }
    
            private string _Machine;
            public string Machine
            {
                get
                {
                    return _Machine;
                }
                set
                {
                    SetPropertyValue("Machine", ref _Machine, value);
                }
            }
    
            private int _Level;
            public int Level
            {
                get
                {
                    return _Level;
                }
                set
                {
                    SetPropertyValue("Level", ref _Level, value);
                }
            }
    
            private int _DurationInMinutes;
            public int DurationInMinutes
            {
                get
                {
                    return _DurationInMinutes;
                }
                set
                {
                    SetPropertyValue("DurationInMinutes", ref _DurationInMinutes, value);
                }
            }
    
            private TrainingRecord _TrainingRecord;
            [Association("TrainingRecord-TrainingProgrammes")]
            public TrainingRecord TrainingRecord
            {
                get
                {
                    return _TrainingRecord;
                }
                set
                {
                    SetPropertyValue("TrainingRecord", ref _TrainingRecord, value);
                }
            }
        }
    }
    using System;
    using DevExpress.Xpo;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    
    namespace Solution12.Module
    {
        [DefaultClassOptions]
        public class TrainingRecord : BaseObject
        {
            public TrainingRecord(Session session) : base(session) { }
    
            private DateTime _Date;
            public DateTime Date
            {
                get
                {
                    return _Date;
                }
                set
                {
                    SetPropertyValue("Date", ref _Date, value);
                }
            }
    
            [Association("TrainingRecord-TrainingProgrammes")]
            public XPCollection<TrainingProgramme> TrainingProgrammes
            {
                get
                {
                    return GetCollection<TrainingProgramme>("TrainingProgrammes");
                }
            }
        }
    
    }

    Giving us the following UI:

    image

    image

    So, that about wraps it up for today. Next time we’ll tidy up the UI and add reporting to our fitness application. Until then happy XAFing! :-)

  • Keeping Fit With XAF #1

         

    Hello XAFers! So January’s passed and all those great New Year’s resolutions for getting fit have fallen by the wayside. They have haven’t they, come on you can tell me? Yeah I thought so. Well don’t worry ‘cos I’m here to help. By, here to help, what I mean is that I too have a New Year’s resolution for losing weight and getting fit. Of course, when I start to lose motivation I turn to XAF to help me out, I mean what could be better than having a hot piece of software to help you? Okay, I admit, a hot personal trainer would be better, but it is what it is right?

    Anyway, today we’ll begin a short series of posts on an application that I threw together (yes I do mean threw together and not developed) to help me measure my weight lose and fitness program. So, the first order of business is going to be to record my weight as it heads down towards my target. Let’s add a domain object to our application and add properties for the date the record was made and my weight that day, in pounds:

    using System;
    using DevExpress.Xpo;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    
    namespace Fitness.Module
    {
        [DefaultClassOptions]
        public class WeightRecord : BaseObject
        {
            public WeightRecord(Session session) : base(session) { }
    
            private DateTime date;
            public DateTime Date
            {
                get
                {
                    return date;
                }
                set
                {
                    SetPropertyValue("Date", ref date, value);
                }
            }
    
            private int lBS;
            public int LBS
            {
                get
                {
                    return lBS;
                }
                set
                {
                    SetPropertyValue("LBS", ref lBS, value);
                }
            }
       }
    }

    Okay, well working in pounds is fine for my American cousins, but here in the UK we work in stones and in Europe they work in Kilos, so we should really show those values too. We don’t want to input that information three times though and we don’t have to as we can calculate both of these fields from the lBS field. To do that, modify the class definition like so:

    using System;
    using DevExpress.Xpo;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    
    namespace Fitness.Module
    {
        [DefaultClassOptions]
        public class WeightRecord : BaseObject
        {
            public WeightRecord(Session session) : base(session) { }
    
            private DateTime date;
            public DateTime Date
            {
                get
                {
                    return date;
                }
                set
                {
                    SetPropertyValue("Date", ref date, value);
                }
            }
    
            private int lBS;
            public int LBS
            {
                get
                {
                    return lBS;
                }
                set
                {
                    SetPropertyValue("LBS", ref lBS, value);
                }
            }
    
            [Persistent]
            public double Stones
            {
                get
                {
                    const double STONES_PER_POUND = 0.0714285714;
                    return Math.Round((lBS == 0) ? 0 : lBS * STONES_PER_POUND,2);
                }
            }
    
            [Persistent]
            public double Kilos
            {
                get 
                {
                    const double KILOS_PER_POUND = 0.45359237;
                    return Math.Round((lBS == 0) ? 0 : lBS * KILOS_PER_POUND,2);
                }
            }
        }
    }

    We mark the property with the persistent attribute as we want the value to be saved in the database. Notice there is no setter though, we just do the calculation in the getter. There is one more thing we need to do for this to work and that is to set the ImmediatePostData attribute to true on those calculated fields in the WeightRecord node on the Model Editor. First find these fields in the Model Editor:

    image

    Then set the ImmediatePostDataAttribute to true:

    image

    This attribute specifies whether the property value is updated immediately when changes occur in the current Property Editor's bound control.

    Now, let’s fire up the application and we can see that when we enter an amount for the pounds, we get a value calculated for stones and kilos too:

    image

    Looking at the list view, we can see that these values are persisted to the database:

    image

    Well that wraps it up for this post, next time we’ll add some training programs to the application – until then, happy XAFing! :-)

  • XAF 10.1 Sneak Peek

         

    One of the major features that you will see as of 10.1 is the Typed Application Model.

    As you will know XAF allows developers to define business class and then builds the http://documentation.devexpress.com/#Xaf/CustomDocument2580 from these declarations and generates a UI based on them. Thereafter if you want to change the UI you simply customise the Application Model using the http://documentation.devexpress.com/#Xaf/CustomDocument2582. The Application Model is therefore a cornerstone of XAF and we have re-designed it from the bottom up in 10.1.

    Firstly, let’s take a look at what the Application Model is like now, prior to 10.1. It is currently defined by it’s schema and looks something like this:

    <Element Name="Application">
    <
    Element Name="Views">
    <
    Element Name="ListView">
    <
    Element Name="Variants" >
    <
    Attribute Name="Current" />
    <
    Element Name="Variant" >
    <
    Attribute Name="ID" />
    <
    Attribute Name="Caption" />
    <
    Attribute Name="ViewID" />
    </
    Element>
    </
    Element>
    </
    Element>
    </
    Element>
    </
    Element>

    This snipped declares the Variants node, as a child node of the ListView node. As you can see, in this simplified example, the only thing that attributes haveare names. There are no types declared. So, even if the ID attribute is intended to hold an integer and Caption to hold a string, there is really no difference. The untyped Application Model stores its data as a set of string. For a caption this is fine, for an integer though, it’ll first need to be converted to a string representation. When retrieving its value, it’ll need to be converted again from a string to an integer. What’s more, you need to remember that an integer is stored there, because all the Application Model stores are plain strings. What a PITA!

    Now let’s take a look at how the Application Model worked under the hood. This’ll be a pretty simplified look but it’ll do for our purposes. When an application is started, the types info subsystem (see http://documentation.devexpress.com/#Xaf/CustomDocument3224) collects metadata on all the business classes declared in the application. After this, the Application Model generation is started. Firstly, the BOModel node’s child nodes are generated and filled with values from the types info subsystem. Then all the other Application Model’s nodes and their attributes are filled with data.

    After this initial generation is complete, the Application Model is filled with custom data from all the Modules used in the application.

    At this point we have the initial Application Model, that part which can’t be changed by the user. Then the Application Model is filled with each user’s customizations . Now, the Application Model generation is complete, and we can use our application.

    Now that we know how our untyped Application Model works, we can see the disadvantages:

    1. We COMPLETELY generate ALL THE Application Model at application startup. This is slow.
    2. Memory consumption is far from being optimal. Each attribute contains a string value, not a reference. Usually, there are a lot of duplicate info. For example, a string holding the name of a business class can be found in: types info subsystem, a BOModel node’s attribute, an attribute of each of the Views declared for the class (usually, there are several Views for each business type, at least two or three) and so on.
    3. The Application Model is untyped, and working with it isn’t straightforward. Though we can use node wrappers, the process is generally bulky.

    So, in general, the current Application Model, is suboptimal. :-)

    But it is improved in 10.1 and here’s how. What we’ve done is to do away with the schema and we now use interfaces instead. So the example above would now be declared like so:

    public interface IModelViewVariants : IModelNode
    {
    IModelVariants Variants { get; set; }
    }

    public interface IModelVariants : IModelNode,
    IModelList<IModelVariant>
    {
    IModelVariant Current { get; set; }
    }
    public interface IModelVariant : IModelNode
    {
    int Id { get; set; }
    string ViewID { get; set; }
    string Caption { get; set; }
    }

    Now as you see, attributes not only have names, but they also have types. Also, there’s no need to use node wrappers or convert attribute values to and from string representations. That is much better, don’t you think?

    The basic Application Model structure is described by the base IModelApplication interface. This interface defines the root Application node (its attributes and child nodes). Each Module can also extend the Application Model. This is done by specifying additional interfaces derived from the IModelNode interface.

    So, firstly, when an application is started, we collect all the interfaces that define the Application Model. Then we compile an object implementing all these interfaces. This object represents the Application Model. At this point the Application Model doesn’t hold any actual data.

    Secondly, we fill the Application Model with layers. Our new typed Application Model consists of unmerged layers. Each layer represents a separate data set. Each Module is represented by its own data layer. So, for each Module we create a separate layer and fill it with data supplied by the Module. We also create the user customizations layer and load the user customizations into it. Note that the base Application Model layer defined by the IModelApplication interface doesn’t contain any data at this point.

    At this point, the Application Model is ready to be used. When, for example, the Logon Detail View needs to be invoked, the Application Model is accessed, to retrieve the Detail View’s layout. The following code snippet illustrates this:

    IModelView modelView = 
    Application.ModelApplication.Views[
    “Logon_DetailView”];

    Since the Views node hasn’t been generated yet, a Views generator is invoked which in turn invokes the BOModel generator. As a result, the Views and BOModel nodes’ child nodes are created. Note however, that the created child nodes are NOT filled with data. Then, since we asked for the “Logon_DetailView” data, this node gets populated with data on the base layer. After this, XAF displays the specified View using the data.

    As you can see, the base layer of the Application Model is populated with data on-demand. In other words, if you launch an XAF application, and use only a Contact List View then only the Contact_ListView node will be populated with data. All the other View nodes of the Application Model will remain uninitialized.

    A note on ASP.NET Web XAF applications. Now, if users haven’t customized the Application Model, a single Application Model instance is shared between all the users. Moreover, even if users have customized the Application Model, the common part of it is still represented by a single instance which is also shared between the users.

    Now that we know how our typed Application Model works, we can outline its advantages:

    1. We never completely generate all the Application Model. It’s created on demand (kind of a lazy initialization). This is fast (or at least we hope it will be).
    2. Memory consumption is much more optimal. Each attribute holds a reference, not another value copy. So, for example, ideally, a string holding the name of a business class could only be found in the types info subsystem. In ASP.NET Web XAF applications, most of the Application Model (and sometimes ALL the Application Model) is shared between users.
    3. The Application Model is typed, and working with it becomes more straightforward. No more need for string conversions and use of additional artificial wrappers.

    Okay, so let’s finish up with some before and after examples:

    Defining a key:

    Before:

    <Element Name="Variant" KeyAttribute="ID" />
    After:
    [KeyProperty("Id")]
    public interface IModelEditorStateRule : IEditorStateRule {
    string Id { get; set; }
    }

    Declaring multiple child nodes:

    Before:

    <Element Name="ListView">
    <
    Element Name="Variants">
    <
    Element Name="Variant" Multiple="True" />

    After:

    public interface IModelViewVariants : IModelNode {
    IModelVariants Variants { get; set; }
    }
    public interface IModelVariants :
    IModelNode, IModelList<IModelVariant> {
    }
    public interface IModelVariant : IModelNode { .. }

    Declaring a localisable attribute:

    Before:

    <Element Name="Variant">
    <
    Attribute Name="Caption" IsLocalized="True" />

    After:

    public interface IModelVariant : IModelNode {
    [Localizable(true)]
    string Caption { get; set; }
    }

    Specifying an image for an node:

    Before:

    <Element Name="Variant" ImageName="ModelEditor_ListView">

    After:

    [ImageName("ModelEditor_ListView")] 
    public interface IModelVariant : IModelNode {...

    Specifying the display property:

    Before:

    <Element Name="Variant" DisplayAttribute="Caption" />

    After:

    [DisplayProperty("Caption")]
    public interface IModelVariant : IModelNode {...

    Specifying a required attribute:

    Before:

    <Element Name="Variant" >
    <
    Attribute Name="ViewID" Required="True" />

    After:

    public interface IModelVariant : IModelNode {
    [Required()]
    string ViewID { get; set; }

    Specifying a child node’s Index attribute used to order child nodes:

    Before:

    <Element Name="Variant" >
    <
    Attribute Name="Index" IsNewNode="True"/>

    After:

    Nothing to do here as the base IModelNode interface already declares such an attribute.

    Using an enumeration to specify the possible values for an attribute displayed in a dropdown:

    Before:

    <Element Name="NavigationItems" >
    <
    Attribute Name=""
    DefaultChildItemsDisplayStyle=""""
    Choice=""List,LargeIcons=""""/>

    After:

    public interface IModelNavigationItems : IModelNode {
    ItemsDisplayStyle DefaultChildItemsDisplayStyle { get; set; }

    Getting data from the Application Model. Retrieving a node:

    Before:

    DictionaryNode viewsNode = 
    Application.Model.RootNode.GetChildNode("Views");
    DictionaryNode viewNode =
    viewsNode.FindChildNode(
    BaseViewInfoNodeWrapper.IdAttribute, viewId);

    After:

    IModelView modelView =
        Application.ModelApplication.Views[viewId];

    Getting data from the Application Model. Retrieving an attribute value:

    Before:

    DictionaryNode viewNode = 
    viewsNode.FindChildNode(
    BaseViewInfoNodeWrapper.IdAttribute, viewId);

    string myCustomFilter =
    viewNode.GetAttributeValue("MyCustomFilter");

    After:

    IModelViewMyExtention myModelView = 
    (IModelViewVariants)
    Application.ModelApplication.Views[viewId];

    string myCustomAttrValue = myModelView.MyCustomFilter;

     

    Well that’s all for this post, until next time – happy XAF-ing! :-)

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.