Archive for the ‘ Silverlight ’ Category

HTML5/Javascript as Assembly Language

This is the continuation of this post on the future of Microsoft development.

Scott Hanselman had an interesting post about how HTML5 and Javascript are assembly language for the web.  I won’t repeat his post (you should read it yourself), but basically it’s Turing Complete so you can write any program in it, it will be runnable by any browser soon, and we don’t necessarily need to read it.  There are already options to program in higher-level languages and compile to Javascript.

Okay, so in my last post I commented on the whole Microsoft-HTML5-Silverlight debate.  Here’s my subversive idea:

We’re already seeing systems where the build target is variable.  For example, Microsoft’s recent release of Visual Studio Lightswitch can build your business apps for the desktop or the web.  And they’ve stated there’s no reason it couldn’t compile to HTML5.

Microsoft should implement the CLR in Javascript.  Let me repeat that.  Microsoft should implement the CLR in Javascript.  And the Silverlight framework.

Imagine if you had a choice of deployments!  If speed is an absolute necessity, run your application in the Silverlight plugin.  If portability is the desire, just reference a javascript file that implements the Silverlight framework.

Think about it:

  • XAML is much more productive than HTML.  C# is much more productive than Javascript.  You could use the right tools to build your site or app, and the right tools to deploy your site or app.
  • Universal Silverlight.  It would run on an iPad, or an Android phone, or any future device that implements HTML5. (Which will be all of them.)  No need to make deals with the other platforms.  Silverlight would be able to run anywhere.
  • No more need for the plugin.  Some corporate environments don’t allow it.  Some folks at home don’t want to install it.  But it wouldn’t be necessary anymore.
Microsoft has always focused on developers and made tools for professionals.  This would be an amazing way to make professional development possible in any environment.  Can it be done?  It would certainly be a task to reimplement the CLR.  But the Mono team has done it.  It’s not impossible.  And it would make a huge number of Microsoft .NET-based systems (even existing ones) instantly web-enabled and portable across all mobile devices.  Microsoft:  Go for it!  C# is for coding.  Javascript is for execution.  Shake the world up.
Advertisements

HTML5 and Silverlight (It’s been a long summer)

Ever since Microsoft unveiled an early build of Windows 8 in a video back in June, there has been rampant speculation on what the emphasis on HTML5 and Javascript means for the future of software development in the Microsoft world.  Specifically, what happens to Silverlight?  What happens to .NET?

There has been commentary from just about every corner:  from toolset makers such as Telerik, to ex-Microsofties, such as Scott Barnes.

And of course Microsoft has been pretty silent on all this.  The BUILD conference is coming up in about 2 weeks, but we’ve been waiting all summer, and it’s quite fun to practice Kremlinology anyway, right?

There seem to be several main theories on Microsoft’s plans:

  • .NET and Silverlight are going to wither on the vine.  The future is HTML5, Javascript, and C/C++.  The Windows division has taken over and they never appreciated .NET.
  • .NET and Silverlight will continue to exist, but they are going to be lesser stars in the MS galaxy.
  • .NET and XAML are at the heart of Windows 8 and something called Project Jupiter.  They’ll get to play on an even field with C++ at last.
Since the evidence is so scarce, we should focus on what Microsoft has actually said.
  • The future of the web is HTML5
  • SharePoint is big, and it’s going to the cloud with Office 365
  • Office 15 is on the way and SharePoint is the nerve center of Office
Okay, no point in belaboring it.  HTML5 is going to be a big focus.  SharePoint is a HUGE business for Microsoft, and it’s founded on .NET.  Throw in Dynamics and a bunch of other MS products.  .NET is not going away.
I’m excited to see what Microsoft unveils at the BUILD keynote on the 13th.
Next post:  What I HOPE Microsoft is thinking about.

Silverlight Pivot Viewer – Automatic Pivot Collection Generation via Customized Pauthor tool

    The Microsoft Silverlight Pivot Viewer is a new innovative way to look at a massive amount of data in a fast visually comparative way. PivotViewer utilizes Microsoft’s Deep Zoom technology which enables it to take advantage of high resolution imagery without taking a hit on performance.

    Lately, we have been utilizing PivotViewer for Business Intelligence applications. If you are using PivotViewer to simply navigate and categorize images, the images themselves aren’t very important. However, if you are intending to utilize PivotViewer for a BI application, you will want to communicate the business information visually, that is the point after all, isn’t it? There are several ways to generate PivotViewer collections. The pivot collection creator for excel (here) provides a fast, simple way to generate PivotViewer collections. The main drawback of the excel tool is that the images for each item must already exist, plus this is still a very manual process. This is where the Pauthor tool comes into play.

    The Pauthor tool is an open source command line tool for generating pivot collections from various formats, such as excel, csv, and cxml formats. Pauthor allows for the generation of pivot images from an html template, as well as the creation of new data sources and target sources that implement an OLE DB driver. We created a SQL Server datasource based on the OleDBCollectionSource.  This allowed us to use tables, or views, or even stored procedures to generate the pivot collection facet categories, items, and collection text.

public class SqlCollectionSource : OleDbCollectionSource
    {
        /// <summary>
        /// Creates a new Excel collection source and sets its <see cref="BasePath"/>.
        /// </summary>
        /// <param name="basePath">the path to the Excel file containing the collection's data</param>
        public SqlCollectionSource(String serverName, String dbName, String collectionTableName, String facetTableName, String itemTableName)
            : base(String.Format(ConnectionStringTemplate, serverName, dbName), ".")
        {
            m_serverName = serverName.ToLowerInvariant();
            m_dbName = dbName.ToLowerInvariant();
            m_collectionTableName = collectionTableName.ToLowerInvariant();
            m_facetTableName = facetTableName.ToLowerInvariant();
            m_itemTableName = itemTableName.ToLowerInvariant();
        }

        protected override void LoadHeaderData()
        {
            this.ConnectionString = String.Format(ConnectionStringTemplate, m_serverName, m_dbName);
            this.UpdateDataQueries();

            base.LoadHeaderData();
        }

        private void UpdateDataQueries()
        {
            //String connectionString = String.Format(ConnectionStringTemplate, this.BasePath);
            using (OleDbConnection connection = new OleDbConnection(this.ConnectionString))
            {
                connection.Open();
                DataTable schema = connection.GetOleDbSchemaTable(
                    OleDbSchemaGuid.Tables, new Object[] { null, null, null, "TABLE" });
                
                String firstTableName = null;
                foreach (DataRow row in schema.Rows)
                {
                    String table = row["Table_Name"].ToString();
                    String lowerTable = table.ToLowerInvariant();
                    if (lowerTable == m_collectionTableName)
                    {
                        this.CollectionDataQuery = String.Format(CommandTemplate, table);
                    }
                    if (lowerTable == m_facetTableName)
                    {
                        this.FacetCategoriesDataQuery = String.Format(SortedCommandTemplate, table);
                    }
                    if (lowerTable == m_itemTableName)
                    {
                        this.ItemsDataQuery = String.Format(SprocTemplate, table);
                    }
                    if (firstTableName == null) firstTableName = table;
                }

                schema = connection.GetOleDbSchemaTable(
                    OleDbSchemaGuid.Procedures, new Object[] { null, null, m_itemTableName, null });

                
                foreach (DataRow row in schema.Rows)
                {
                    String table = row["Procedure_Name"].ToString();
                    table = table.Remove(table.IndexOf(';'));
                    String lowerTable = table.ToLowerInvariant();
                    
                    if (lowerTable == m_itemTableName)
                    {
                        this.ItemsDataQuery = String.Format(SprocTemplate, table);
                    }
                
                }

                if (this.ItemsDataQuery == null)
                {
                    this.ItemsDataQuery = String.Format(CommandTemplate, firstTableName);
                }
            }
        }

        private const String ConnectionStringTemplate = "Provider=SQLNCLI10; Server={0};" +
            "Database={1}; Trusted_Connection=yes;";

        private const String CommandTemplate = "SELECT * FROM [{0}]";
        private const String SortedCommandTemplate = "SELECT * FROM [{0}] order by sort";
        private const String SprocTemplate = "exec [{0}]";
        private String m_serverName = string.Empty;
        private String m_dbName = string.Empty;
        private String m_collectionTableName = string.Empty;
        private String m_facetTableName = string.Empty;
        private string m_itemTableName = string.Empty;
    }

Customizing the HTML template functionality

    The base HTML template functionality allows you to specify an html file incorporating meta tags like {name} {href} and any other custom pivot facet categories your items use within the html itself. Pauthor then iterates through the collection item datasource and creates a new html file for each one by replacing all of these tags with the item data source values. A web browser control is then run within the code to load up the newly generated html file and save an image out to the file system. Deep zoom images are then created for the pivot collection based off of this saved custom image as well as the xml of the pivot collection itself. We also customized the html template functionality allowing for a template query string to be used.

Example: /html-url http://www.example.com/PivotCard?itemPrimaryKey={itemPrimaryKey}

    This immediately increases the flexibility of the images we create, as we can now generate any html we want using standard web technology based on the values we receive off of the query string as opposed to scattering meta tags in an html file. Managing a web page which generates these Pivot Images is much easier to maintain than trying to generate images manually using a graphics library. You can imagine the time involved in changing an image template, moving lines around, drawing text with wrapping capabilities, etc, each time your Pivot Card format changes.

Pivot Collection Generation

    Pivot collection generation is now a breeze! There are no longer any manual steps with having our data in a database and images automatically generated and saved on the web server which serves up the pivot collection.  Collection generation can now be kicked off with a nightly scheduled task or even more frequently if needed. We are currently working on creating a means of generating pivot cards on a one off basis so that the collection can be maintained in near real time without incurring the cost of generating the entire collection each time an item changes. Stay tuned!

Pixel Shaders w/ Source (And a demo!)

In an earlier post, I showed some still images of Silverlight pixel shaders and how we used the awesome tool Shazzam in our development.  Today I’d like to post the code and show some interesting uses of the shaders.  (Live!)

Let’s start with the code for the Telescopic Blur effect.  Here’s the relevant HLSL:

/// &lt;summary&gt;Center X of the Zoom.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;1&lt;/maxValue&gt;
/// &lt;defaultValue&gt;0.5&lt;/defaultValue&gt;
float CenterX : register(C0);

/// &lt;summary&gt;Center Y of the Zoom.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;1&lt;/maxValue&gt;
/// &lt;defaultValue&gt;0.5&lt;/defaultValue&gt;
float CenterY : register(C1);

/// &lt;summary&gt;Amount of zoom blur.&lt;/summary&gt;
/// &lt;minValue&gt;0&lt;/minValue&gt;
/// &lt;maxValue&gt;3&lt;/maxValue&gt;
/// &lt;defaultValue&gt;2.5&lt;/defaultValue&gt;
float BlurAmount : register(C2);

sampler2D input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR

{
    float4 c = 0;
    uv.x -= CenterX;
    uv.y -= CenterY;

    float distanceFactor = pow(pow(uv.x,2) + pow(uv.y, 2),2);

    for(int i=0; i < 15; i++)
    {
        float scale = 1.0 - distanceFactor * BlurAmount * (i / 30.0);
        float2 coord = uv * scale;
        coord.x += CenterX;
        coord.y += CenterY;
        c += tex2D(input,coord);
    }

    c /= 15;
    return c;
}

It takes 3 inputs.  By altering the center of the zoom, you can do some fun stretching motions.  As you’ll see in the demo below, it turned out to be a neat way to make text fly into view.  You can also alter the amount of zoom blur.  I’ll leave it up to the reader to play with this within Shazzam.

Next we have the underwater effect:

sampler2D input : register(s0);

float Timer : register(C0);

static const float2 poisson[12] =
{
        float2(-0.326212f, -0.40581f),
        float2(-0.840144f, -0.07358f),
        float2(-0.695914f, 0.457137f),
        float2(-0.203345f, 0.620716f),
        float2(0.96234f, -0.194983f),
        float2(0.473434f, -0.480026f),
        float2(0.519456f, 0.767022f),
        float2(0.185461f, -0.893124f),
        float2(0.507431f, 0.064425f),
        float2(0.89642f, 0.412458f),
        float2(-0.32194f, -0.932615f),
        float2(-0.791559f, -0.59771f)
};

float4 main(float2 uv : TEXCOORD) : COLOR
{
	float2 Delta = { sin(Timer + uv.x*23 + uv.y*uv.y*17)*0.02 , cos(Timer + uv.y*32 + uv.x*uv.x*13)*0.02 };

    float2 NewUV = uv + Delta;

	float4 Color = 0;
	for (int i = 0; i < 12; i++)
	{
	   float2 Coord = NewUV + (poisson[i] / 50);
       Color += tex2D(input, Coord)/12.0;
     }
     Color += tex2D(input, uv)/4;
     Color.a = 1.0;
     return Color;
}

Its input is a timer value, so that it can be animated.  If you compile this into a Silverlight effect, you can make the timer be a DependencyProperty, which allows the effect to be animated by Storyboard.  I love XAML.

While this is a “passable” underwater effect (and I’m still working on a better one), I found an unanticipated use for it.  But first, a digression.

A year or two ago, LucasArts re-released their classic game The Secret of Monkey Island for the XBox.  I played this game extensively as a teenager, so of course I downloaded the new version and played through again, to see what they had done with the new capabilities of modern hardware.  I wasn’t disappointed.  While upgrading the look, they had stayed faithful to the design spirit of the original.  And one of the most impressive effects was the Ghost Pirate LeChuck.  He had a wavy, wispy quality.  (As shown here.)  It was fluid and animated and very impressive, and at the time I didn’t know how they did it.

Fast forward a year or two and we’re working on the underwater shader.  On a whim, I applied it to a white square in front of a black background.  Whoah.  Now I knew.  So we put together a quick ghost demo using the underwater effect and you can see it, along with the telescopic effect used as a transition, by clicking here.

Click for Pixel Shaders Demo

Click for Pixel Shaders Demo

Comparable WeakReferences in C#

C# provides us with a WeakReference object.  This object behaves like a normal reference, except that it does not prevent garbage collection of the target instance it refers to.  So if every other strong reference to an instance goes away or is nulled out, the .NET garbage collector will collect that instance, without regard to the weak references.  WeakReferences also provide an IsAlive property so you can check if the target still exists.

The WeakReference class has a weakness in certain situations that I’d like to address.  Let me start with an example.

Suppose you have two weak references and you want to know if they point to the same instance.  Something like this (very naive) example:

object instance1 = new object();

WeakReference ref1 = new WeakReference(instance1);
WeakReference ref2 = new WeakReference(instance1);

if (ref1 == ref2)
{
    // Do something
}

This will actually fail every time.  It’s comparing the references to the WeakReferences.  Because ref1 is a reference to a WeakReference instance, as is ref2.  What we really want is to compare the targets.  (Usually.)

So here I present the EquatableWeakReference class.  It behaves just like a WeakReference, but if you compare two of them, it compares the targets.

public class EquatableWeakReference
    {
        #region Private Members

        private int _targetHashCode;
        private WeakReference _weakReferenceToTarget;

        private void setTarget(object target)
        {
            _targetHashCode = target.GetHashCode();
            _weakReferenceToTarget = new WeakReference(target);
        }

        #endregion

        #region Public Interface

        public EquatableWeakReference(object target)
        {
            setTarget(target);
        }

        public object Target
        {
            get { return _weakReferenceToTarget.Target; }
            set
            {
                setTarget(value);
            }
        }

        public bool IsAlive
        {
            get
            {
                return _weakReferenceToTarget.IsAlive;
            }
        }

        public override int GetHashCode()
        {
            return _targetHashCode;
        }

        public override bool Equals(object obj)
        {
            return _targetHashCode == obj.GetHashCode();
        }

        #endregion
    }

As you can see, it has a similar constructor, and the same properties as the WeakReference.  It wraps the WeakReference, rather than inheriting from it, because we originally wrote this for use in Silverlight and you can’t inherit from WeakReference in Silverlight.

When you set the target, it records the target’s HashCode.  This is a unique* identifier of that object instance.  It uses those HashCodes to do comparisons.  It also overrides GetHashCode to return the target’s HashCode, rather than the EquatableWeakReference’s HashCode.  This also provides the advantage that if you use an EquatableWeakReference as a key in a Dictionary, it will be treated as the same key as a real reference to that object, because Dictionaries use the HashCode to index values.

(Which brings us to the whole reason for creating this.  We needed to make a WeakDictionary.  A Dictionary that would allow values to be garbage collected if the key instance was no longer alive.  This was a single-point fix to a widespread memory leak in an open-source library we were working with.  Oh, memory leaks… How you amuse me…)

 

* – It’s not really 100% unique, but it was good enough for our purposes.

MVVM Enabling Database Driven Silverlight Applications

One of the more exciting projects we are working on here at Affirma is a line-of-business solution for a client that includes talking to a SQL Server backend.  This was a great opportunity to put our own MVVM framework to the test in developing a data-driven Silverlight client that would increase productivity and visibility for our customer.

There are a number of different topics from our lessons learned that could be covered in our blog, and I will definitely cover as many as possible in the future, but for now I just wanted to highlight how we were able to put together a group of technologies in a very quick timeframe to provide great value to our customers.

If you are unfamiliar with MVVM, don’t fret!  You can read this quick overview from Joel Wetzel on the topic to get a quick understanding, but for now just think of it as a group of patterns and practices wrapped into a framework that allows us to build testable and reusable components with great flexibility.  It’s also a topic we will cover in blog posts in the future.

For this project, we used the following technologies:

  • Microsoft SQL Server 2008
  • Silverlight 4 (including the Toolkit)
  • Silverlight Unit Testing Framework
  • Windows Communication Foundation
  • DevExpress Silverlight Controls
  • .NET 4.0, ASP.NET 3.5/4.0, RhinoMocks, nUnit, MSTest, and others

The requirement from our customer was to provide an interface for their managers to modify and insert business data in monthly batches that would then report up to business operations for accounting and other purposes.  The managers were also familiar with WPF clients and so Silverlight seemed like a great choice for their intranet needs.  To understand how all these pieces fit together, here is a pretty little architecture diagram.

Now for the tricky part.  Silverlight runs on the client UI thread and cannot talk directly to SQL or other server code that requires a network connection.  So how is this accomplished?  With a little asynchronous, anonymous delegate gravy!!

using System;
using System.Collections.Generic;
using Affirma.Silverlight.Services;

namespace SilverlightLOB.DomainServices
{
    public interface IDomainService : IClientService
    {
        void Add(BusinessObject businessObject, Action<IResult<bool>> action);
        void GetItemsFor(Store store, Action<IResult<IEnumerable<BusinessObject>>> action);
    }

    public class DomainService  : IDomainService
    {
        public void Add(BusinessObject businessObject, Action<IResult<bool>> action)
        {
			// our web service proxy
            var client = new DataServiceClient();

            client.AddCompleted +=
                (obj, args) =>
                {
                    if (args.Error != null)
                        Logging.Log.Debug("Error in Add - " + args.Error.Message);

                    action.Invoke(new SimpleServiceResult { Error = args.Error });
                };

            client.AddAsync(businessObject);
        }

        public void GetItemsFor(Store store, Action<IResult<IEnumerable<BusinessObject>>> action)
        {
            var client = new DataServiceClient();

            client.GetItemsForCompleted +=
                (obj, args) =>
                {
                    if (args.Error != null)
                        Logging.Log.Debug("Error in GetItemsFor - " + args.Error.Message);

                    var result = new EnumerableServiceResult<BusinessObject> {Error = args.Error};
                    if (result.Success)
                        result.Value = args.Result;

                    action.Invoke(result);
                };

            client.GetItemsForAsync(store);
        }
    }
}

Easy!  This domain service lives on the client UI thread and will execute asynchronous requests to our WCF service, and invoke the result back onto our UI thread thanks to the provided callback.  This means the Silverlight client UI can continue to execute requests and other user interactions while the service requests process and returns a value.  From our view model, we can easily wire up to this domain service and execute the appropriate asynchronous method.

public ICommand AddCommand
{
	return ConstructCommand(() => AddCommand, AddHandler);
}

void AddHandler()
{
	_service.Add(ObjectToInsert,
		result => {
			if (result.Success) MessageBox.Show("Success!");
		};
}

Undo/Redo and Encapsulated Data Changes in Silverlight

We’re currently working on a Silverlight application that needed a robust undo/redo system.  Every change a user could make to data needed to be able to be undone.  At Affirma, we have a set of tools we’ve built for Silverlight – using an MVVM and Service-Oriented pattern – but this was not a tool already in it!  No matter, the great thing about a tool like an undo/redo system is that it must be a general tool, and so it could be added to our toolkit for use on many projects.

The way we chose to go about making this system was to abstract out the idea of a “Change”.  In the abstract, a change would be an encapsulated something (also known as a class, but the dramatic form is fun) that knew how to alter data both forwards and backwards.  Okay, that’s pretty abstract, so how about an example.  Let’s say we have a DeleteChange that can remove a piece of data from a repository.  This DeleteChange would need to know what piece of data it had to delete, it would need a way to delete that data, it would need to keep track of that data so the change could be undone, and it would need a way to add the data back for an undo.

Okay, some code:

namespace Affirma.Silverlight.Changes
{
    public abstract class Change
    {}

    /// <summary>
    /// A change does and undoes work on a target.
    /// </summary>
    public abstract class Change<TargetType> : Change
        where TargetType : class
    {
        /// <summary>
        /// An implementation should override this method and do work on the target here.
        /// </summary>
        public abstract void Do(TargetType target);

        /// <summary>
        /// An implementation should override this method and undo work on the target here.
        /// </summary>
        public abstract void Undo(TargetType target);

        /// <summary>
        /// An implementation should override this method and notify any listeners of the change here.
        /// </summary>
        public abstract void Notify(TargetType target);

        /// <summary>
        /// An implementation should override this method to finalize doing and undoing the action here.
        /// </summary>
        /// <param name="target"></param>
        public abstract void Finalize(TargetType target);
    }
}

A few notes:

1 – You would make a child class for each type of change you might want to make to your data.

2 – The changes are strongly typed (through generics) to the data object they will act upon.

3 – You implement methods for doing the change and undoing it.

4 – We added a Notify method.  In practice, often other systems would need to know about the change, so this is where you could notify them.

5 – We often found that we needed to “preview” a change, but not finalize it.  An example would be dragging an element around the screen.  We might need to “test the waters” of the change as we drag the element around, but we don’t want to finalize the change until we drop the element.  That’s why we have a finalize method.

6 – You’re probably getting the feeling about now that this system requires some discipline.  It does.  You MUST NOT alter data anywhere but inside a change.  However, we found that in practice, using that discipline up front saved us a lot of time down the road.  We always knew where to look to find where the data changed.  Data changes weren’t hiding in corners of the system.  And there was fantastic encapsulation.  Clients shouldn’t know what it takes to change the data.  They usually shouldn’t even know about all the data!  They just know they want a change, and these classes encapsulate it.

Okay, so the next step.  We wrote a service (internal to the Silverlight application) to manage these changes.  Here’s the interface:

namespace Affirma.Silverlight.Services
{
    /// <summary>
    /// An IChangeManagerService is used to manage data changes in systems where undo/redo
    /// functionality is desired. It is a generic interface, and the generic type is the type of
    /// the target that all the changes will act upon.  Generally, the target will be the root
    /// of a model graph.  The changes act upon this target to do their work, undo it, and redo it.
    /// </summary>
    /// <typeparam name="TargetType">The type of the target that all the changes will act upon.  Generally, the target will be the root of a model graph.</typeparam>
    public interface IChangeManagerService<TargetType> : IClientService
        where TargetType : class
    {
        /// <summary>
        /// Registers the target for all the changes.  This must be done before changes can be done.
        /// </summary>
        /// <param name="target"></param>
        void RegisterTarget(TargetType target);

        /// <summary>
        /// Performs a change, but does not commit it yet.
        /// </summary>
        void PreviewChange(Change<TargetType> newChange);

        /// <summary>
        /// Commits the currently previewed change.  It will be added to the undo stack.
        /// </summary>
        void CommitChange();

        /// <summary>
        /// Previews and commits a change in one step
        /// </summary>
        void CommitChange(Change<TargetType> newChange);

        /// <summary>
        /// Undoes the previewed change.
        /// </summary>
        void CancelChange();

        /// <summary>
        /// Undoes the most recent change.
        /// </summary>
        void Undo();

        /// <summary>
        /// Redoes the most recently undone change.
        /// </summary>
        void Redo();
    }
}

Notes on this:

1 – Currently, this service only supports one target.  It could be expanded to have multiple targets and you would tell it what target the change should act upon.

2 – The interface is a higher abstraction than that of the changes.  For example, preview.  Previewing a change calls Do and Notify, but not Finalize.  If you preview a second time before finalizing, it calls, Undo on the old change, and then Do and Notify on the new one.  Finalize gets called when you commit the change.  This is to support the previously-mentioned drag-and-drop scenario.

3 – The ChangeManagerService keeps track of changes that have been done and undone in two stacks.  It support infinite undo, but we could put a limit in.

The end result of all this work:  Encapsulated Object-Oriented changes.  Do/Undo/Redo capability.  And the ChangeManagerService is ready and waiting for more projects!

Next time I’ll talk about an extra we just added to the ChangeManagerService.  Object-Oriented business rules. Awesome.

%d bloggers like this: