This Social Movement...

I’m not a fan of how things seam to be going with all these people sharing instant thoughts, comments, and links via Facebook, Twitter, Buzz, etc...

One of these days I was reading this post by Joel Spolsky and I found myself sharing his ideas about Twitter and this new “instant society”...

Having said this, my own company keeps investing more resources in sharing information using Facebook and Twitter...

So, I will give it a try for some time... In “consumer mode”. :)

http://www.facebook.com/people/Hugo-Ribeiro/626367682

http://twitter.com/hgr2012

Posted 18 March 10 10:05 by hgr | 0 Comments   
Filed under
Work Item Manager

This tool by Telerik is a gem that made my work with TFS a lot easier and enjoyable:

TFS Work Item Manager & TFS Project Dashboard

Go get it. It’s free.

Posted 03 December 09 10:58 by hgr | 0 Comments   
Filed under , ,
Running that VM Faster

I’ve been running Windows 7 for a while and one of the things that I decided to try is to have all the development environment on a virtual machine.

That’s because my Windows 7 is running on 64 bits and I still need to run a lot of things on 32 bits.

The problem with virtual machines on this scenario is performance.

This little tweak makes my VMWare virtual machine fly. So, if anyone is having issues, try this out.

mainmem.usenamedfile="FALSE"

Posted 21 October 09 12:16 by hgr | 0 Comments   
Filed under
GASP@Porto: RIA Services

This week – tomorrow (21) to be more precise – GASP will hold a new meeting here in our premises.

This session will be about RIA Services and will be conducted by Nuno Godinho, expert on Silverlight and RIA Services and ASP.NET MVP.

If anyone wants to join us in the discussion, feel free to request it at GASP LinkedIn Group.

Posted 20 October 09 02:41 by hgr | 0 Comments   
Filed under , ,
LOB

Great presentation about technologies (becoming) available for developing Line-of-Business Applications (whatever you may think that is…):

http://www.slideshare.net/simonguest/next-generation-lob-line-of-business-applications

Cool stuff there.

Posted 20 October 09 02:30 by hgr | 0 Comments   
Filed under ,
Embarrassing…

It’s absolutely incredible that I published only 6 posts this year.

The scenario is even worst if I think that 3 were just to put Feedburner running again and other 2 were just notes about something that as little to do with my work…

Well, this is the 7th… And it’s worst than the previous 6…

Well… management is really making me miserable. :)

Posted 25 September 09 11:44 by hgr | 1 Comments   
Filed under
Why Google Rocks…

Well, it’s not because of Google Docs, not because of Chrome, Wave, Mail, or whatever they do “outside the box”.

It’s just because of this:

Google

Google

The best search engine out there. Ever!

Posted 25 September 09 11:37 by hgr | 1 Comments   
Filed under
FeedBurner and the 512K Maximum File Size III

That’s it! The feed is back online.

Posted 10 July 09 10:54 by hgr | 0 Comments   
Filed under
FeedBurner and the 512K Maximum File Size II

It didn’t. I think that it’s just another post that needs to go...

Posted 10 July 09 10:50 by hgr | 0 Comments   
Filed under
FeedBurner and the 512K Maximum File Size

Apparently FeedBurner doesn’t swallow RSS sources that exceed 512K...

Since mine is now at 522K – I suppose because of the source code size in previous posts – this post is just a try to make the older posts go away so that the file size decreases... :)

Let’s hope it will work…

Posted 10 July 09 10:49 by hgr | 0 Comments   
Filed under
Add-in Express

These past weeks I’ve playing around with Excel add-ins.

Developing an Excel add-in with ribbon customizations, formulas, and task panes is not an easy task.

VSTO doesn’t help a lot. So I’ve been using this great toolset from Add-in Express:

Add-in Express

Try it out if you’re thinking of doing something with Office…

Posted 08 July 09 10:56 by hgr | 0 Comments   
Filed under ,
No Configuration Enterprise Library

From time to time I end up starting a new C# project and bumping into the same old infrastructure requirements like exception handling, logging, caching, etc.

At that point I always get back to Microsoft Enterprise Library... :)

I really like EntLib but this time I had other requirements that prevented me from having a configuration file.

So how the heck can you use EntLib without configuration? It turns out that it’s relatively easy but not so well documented, so that even Google had trouble finding me the solution. :)

I’ll leave it here also for future reference...

Scenario 1 – No Configuration Caching Block

Using the caching block with configuration is very simple. You create the configuration and then simply use the CacheFactory to create the manager. Something like this:

   1: ICacheManager productsCache = CacheFactory.GetCacheManager();
   2: productsCache.Add(product.ProductID, product)

To use it without configuration is a little harder.

First you need to use the CacheManagerFactory to create a factory passing in the configuration settings. Then you use that factory to create the manager.

The magic happens when you create your own class implementing the IConfigurationSource interface (available in Microsoft.Practices.EnterpriseLibrary.Common).

The final code is as follows:

   1: using Microsoft.Practices.EnterpriseLibrary.Caching.Configuration;
   2: using Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations;
   3:  
   4: namespace MyApplication
   5: {
   6:     internal class CacheServiceConfigurationSource : IConfigurationSource
   7:     {
   8:         #region Members
   9:  
  10:         private int expirationFrequencyInSeconds;
  11:         private int maximumElementsBeforeScavenging;
  12:  
  13:         #endregion
  14:  
  15:         #region Constructors
  16:  
  17:         private CacheServiceConfigurationSource()
  18:         {
  19:         }
  20:  
  21:         public CacheServiceConfigurationSource(int expirationFrequencyInSeconds, int maximumElementsBeforeScavenging)
  22:             : this()
  23:         {
  24:             this.expirationFrequencyInSeconds = expirationFrequencyInSeconds;
  25:             this.maximumElementsBeforeScavenging = maximumElementsBeforeScavenging;
  26:         }
  27:  
  28:         #endregion
  29:  
  30:         #region Public Methods
  31:  
  32:         public void Add(IConfigurationParameter saveParameter, string sectionName, ConfigurationSection configurationSection)
  33:         {
  34:         }
  35:  
  36:         public void AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
  37:         {
  38:         }
  39:  
  40:         public ConfigurationSection GetSection(string sectionName)
  41:         {
  42:             if (sectionName.Equals("cachingConfiguration", StringComparison.CurrentCulture))
  43:             {
  44:                 CacheManagerSettings result = new CacheManagerSettings();
  45:                 result.DefaultCacheManager = "DefaultCacheManager";
  46:  
  47:                 CacheStorageData storage = new CacheStorageData();
  48:                 storage.StorageEncryption = string.Empty;
  49:                 storage.Name = "NullStorage";
  50:                 storage.Type = typeof(NullBackingStore);
  51:                 result.BackingStores.Add(storage);
  52:  
  53:                 CacheManagerData manager = new CacheManagerData();
  54:                 manager.Name = "DefaultCacheManager";
  55:                 manager.NumberToRemoveWhenScavenging = 10;
  56:                 manager.MaximumElementsInCacheBeforeScavenging = this.maximumElementsBeforeScavenging;
  57:                 manager.ExpirationPollFrequencyInSeconds = this.expirationFrequencyInSeconds;
  58:                 manager.CacheStorage = "NullStorage";
  59:                 result.CacheManagers.Add(manager);
  60:  
  61:                 return result;
  62:             }
  63:  
  64:             return null;
  65:         }
  66:  
  67:         public void Remove(IConfigurationParameter removeParameter, string sectionName)
  68:         {
  69:         }
  70:  
  71:         public void RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
  72:         {
  73:         }
  74:  
  75:         #endregion
  76:     }
  77: }
  78:  
  79: using System;
  80: using System.Configuration;
  81: using System.Text;
  82: using Microsoft.Practices.EnterpriseLibrary.Caching;
  83: using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
  84:  
  85: namespace MyApplication
  86: {
  87:     public static class CacheService
  88:     {
  89:         #region Members
  90:  
  91:         private static ICacheManager manager;
  92:  
  93:         #endregion
  94:  
  95:         #region Properties
  96:  
  97:         private static ICacheManager Manager
  98:         {
  99:             get
 100:             {
 101:                 if (manager == null)
 102:                 {
 103:                     CacheManagerFactory factory = new CacheManagerFactory(new CacheServiceConfigurationSource(Context.Current.Settings.Cache.ExpirationFrequencyInSeconds, Context.Current.Settings.Cache.MaximumElementsBeforeScavenging));
 104:                     manager = factory.CreateDefault();
 105:                 }
 106:  
 107:                 return manager;
 108:             }
 109:         }
 110:  
 111:         #endregion
 112:  
 113:         #region Public Methods
 114:  
 115:         public static void AddData<T>(string key, T value)
 116:         {
 117:             AddData<T>(key, value, Context.Current.Settings.Cache.ExpirationTimeInSeconds);
 118:         }
 119:  
 120:         internal static void AddData<T>(string key, T value, int expirationInSeconds)
 121:         {
 122:             // Expiration handler
 123:  
 124:             SlidingTime expirationHandler = new SlidingTime(new TimeSpan(0, 0, expirationInSeconds), DateTime.Now);
 125:  
 126:             // Add it
 127:  
 128:             Manager.Add(key, value, CacheItemPriority.Low, null, expirationHandler);
 129:         }
 130:  
 131:         #endregion
 132:     }
 133: }

Notice the GetSection method in CacheServiceConfigurationSource and the Manager property in the CacheService class.

Scenario 2 – No Configuration Logging Block

The logging application block is similar.

With configuration you use the Logger class.

Without configuration you must create a LogWriter using your own IConfigurationSource also.

   1: using System;
   2: using System.Configuration;
   3: using System.Text;
   4: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   5: using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;
   6: using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters;
   7: using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;
   8:  
   9: namespace MyApplication
  10: {
  11:     internal class LoggingConfigurationSource : IConfigurationSource
  12:     {
  13:         #region Public Methods
  14:  
  15:         public void Add(IConfigurationParameter saveParameter, string sectionName, ConfigurationSection configurationSection)
  16:         {
  17:         }
  18:  
  19:         public void AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
  20:         {
  21:         }
  22:  
  23:         public ConfigurationSection GetSection(string sectionName)
  24:         {
  25:             if (sectionName.Equals("loggingConfiguration", StringComparison.CurrentCulture))
  26:             {
  27:                 // Logging configuration
  28:  
  29:                 LoggingSettings result = new LoggingSettings();
  30:                 result.DefaultCategory = "General";
  31:                 result.TracingEnabled = true;
  32:                 result.LogWarningWhenNoCategoriesMatch = true;
  33:  
  34:                 // Listners
  35:  
  36:                 FormattedEventLogTraceListenerData listener = new FormattedEventLogTraceListenerData();
  37:                 listener.Name = "EventLogListener";
  38:                 listener.Source = "Office Extensions";
  39:                 listener.Formatter = "TextFormatter";
  40:                 listener.Log = "MyApplication";
  41:                 listener.MachineName = string.Empty;
  42:                 listener.TraceOutputOptions = System.Diagnostics.TraceOptions.None;
  43:                 listener.Filter = System.Diagnostics.SourceLevels.All;
  44:                 listener.ListenerDataType = typeof(FormattedEventLogTraceListenerData);
  45:                 listener.Type = typeof(FormattedEventLogTraceListener);
  46:                 result.TraceListeners.Add(listener);
  47:  
  48:                 TraceListenerReferenceData listenerReference = new TraceListenerReferenceData();
  49:                 listenerReference.Name = "EventLogListener";
  50:  
  51:                 // Formatters
  52:  
  53:                 StringBuilder template = new StringBuilder();
  54:                 template.AppendLine("Timestamp: {timestamp}");
  55:                 template.AppendLine("Message: {message}");
  56:                 template.AppendLine("Category: {category}");
  57:                 template.AppendLine("Priority: {priority}");
  58:                 template.AppendLine("EventId: {eventid}");
  59:                 template.AppendLine("Severity: {severity}");
  60:                 template.AppendLine("Title:{title}");
  61:                 template.AppendLine("Machine: {machine}");
  62:                 template.AppendLine("Application Domain: {appDomain}");
  63:                 template.AppendLine("Process Id: {processId}");
  64:                 template.AppendLine("Process Name: {processName}");
  65:                 template.AppendLine("Win32 Thread Id: {win32ThreadId}");
  66:                 template.AppendLine("Thread Name: {threadName}");
  67:                 template.AppendLine("Extended Properties: {dictionary({key} - {value})}");
  68:  
  69:                 TextFormatterData formatter = new TextFormatterData();
  70:                 formatter.Name = "TextFormatter";
  71:                 formatter.Type = typeof(TextFormatter);
  72:                 formatter.Template = template.ToString();
  73:                 result.Formatters.Add(formatter);
  74:  
  75:                 // Category Sources
  76:  
  77:                 TraceSourceData exceptionsSource = new TraceSourceData();
  78:                 exceptionsSource.TraceListeners.Add(listenerReference);
  79:                 exceptionsSource.Name = "Exceptions";
  80:                 exceptionsSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
  81:                 result.TraceSources.Add(exceptionsSource);
  82:  
  83:                 TraceSourceData traceSource = new TraceSourceData();
  84:                 traceSource.TraceListeners.Add(listenerReference);
  85:                 traceSource.Name = "Trace";
  86:                 traceSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
  87:                 result.TraceSources.Add(traceSource);
  88:  
  89:                 TraceSourceData generalSource = new TraceSourceData();
  90:                 generalSource.TraceListeners.Add(listenerReference);
  91:                 generalSource.Name = "General";
  92:                 generalSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
  93:                 result.TraceSources.Add(generalSource);
  94:  
  95:                 // Special Sources
  96:  
  97:                 result.SpecialTraceSources.AllEventsTraceSource.Name = "AllEvents";
  98:                 result.SpecialTraceSources.AllEventsTraceSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
  99:  
 100:                 result.SpecialTraceSources.NotProcessedTraceSource.Name = "NotProcessed";
 101:                 result.SpecialTraceSources.NotProcessedTraceSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
 102:  
 103:                 result.SpecialTraceSources.ErrorsTraceSource.Name = "LoggingErrorsAndWarnings";
 104:                 result.SpecialTraceSources.ErrorsTraceSource.DefaultLevel = System.Diagnostics.SourceLevels.All;
 105:                 result.SpecialTraceSources.ErrorsTraceSource.TraceListeners.Clear();
 106:                 result.SpecialTraceSources.ErrorsTraceSource.TraceListeners.Add(listenerReference);
 107:  
 108:                 // Return
 109:  
 110:                 return result;
 111:             }
 112:  
 113:             return null;
 114:         }
 115:  
 116:         public void Remove(IConfigurationParameter removeParameter, string sectionName)
 117:         {
 118:         }
 119:  
 120:         public void RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
 121:         {
 122:         }
 123:  
 124:         #endregion
 125:     }
 126: }
 127:  
 128: using System;
 129: using System.Diagnostics;
 130: using System.Globalization;
 131: using Microsoft.Practices.EnterpriseLibrary.Logging;
 132:  
 133: namespace MyApplication
 134: {
 135:     public static class LoggingHandler
 136:     {
 137:         #region Constants
 138:  
 139:         private const string CategoryExceptions = "Exceptions";
 140:         private const int PriorityExceptions = 1000;
 141:         private const int EventIdExceptions = 1000;
 142:         private const string TitleExceptions = "Errors Log";
 143:         private const TraceEventType SeverityExceptions = TraceEventType.Error;
 144:  
 145:         #endregion
 146:  
 147:         #region Members
 148:  
 149:         private static LogWriter writer;
 150:  
 151:         #endregion
 152:  
 153:         #region Properties
 154:  
 155:         internal static LogWriter Writer
 156:         {
 157:             get
 158:             {
 159:                 if (writer == null)
 160:                 {
 161:                     writer = CreateWriter();
 162:                 }
 163:  
 164:                 return writer;
 165:             }
 166:         }
 167:  
 168:         #endregion
 169:  
 170:         #region Public Methods
 171:  
 172:         public static void Exception(Exception ex)
 173:         {
 174:             // Validation
 175:  
 176:             if (ex == null)
 177:             {
 178:                 throw new ArgumentNullException();
 179:             }
 180:  
 181:             // Log
 182:  
 183:             WriteEntry(CategoryExceptions, TitleExceptions, ex.ToString(), PriorityExceptions, EventIdExceptions, SeverityExceptions);
 184:         }
 185:  
 186:         #endregion
 187:  
 188:         #region Private Methods
 189:  
 190:         private static void WriteEntry(string category, string title, string message, int priority, int eventId, TraceEventType severity)
 191:         {
 192:             LogEntry entry = new LogEntry();
 193:             entry.Categories.Add(category);
 194:             entry.TimeStamp = DateTime.Now;
 195:             entry.EventId = eventId;
 196:             entry.Message = message;
 197:             entry.Priority = priority;
 198:             entry.Severity = severity;
 199:             entry.Title = title;
 200:  
 201:             Writer.Write(entry);
 202:         }
 203:  
 204:         private static LogWriter CreateWriter()
 205:         {
 206:             LogWriterFactory factory = new LogWriterFactory(new LoggingConfigurationSource());
 207:             return factory.Create();
 208:         }
 209:  
 210:         #endregion
 211:     }
 212: }

I hope this helps someone in the future.

It turns out that the greater strength of EntLib – configuration – is also, at least in my opinion, its greater weakness. Just think of how many “real-world applications” would you want to have the user messing with the configuration file to setup event viewer listeners and things like that. :)

DSL Tools #14 - Double-click in the Model Explorer

Every DSL Tools project generates this handy model explorer where you can browse the domain classes that are on your domain model.

Something like this:

Model Explorer Preview

This is particularly helpful for example to change the properties of a particular domain class. You browse for in the explorer, click on it, and simply edit the properties.

Now there is a fundamental feature that the guys at Microsoft missed in the explorer. The ability to double click a domain class there and have the domain model view show that domain class centered. A feature very useful if you have a model with a few dozens of domains classes (which is very typical).

So I put my mind to implement such behavior on my various DSL explorers. It turns out that it isn't very difficult.

1. Determine the explorer class name

The model explorer class is generated as part of your DSLPackage project. The name of the class depends on the name of your DSL. To find out the correct name, browse the DSLPackage project, under GeneratedCode, and you'll find a file named ModelExplorer.cs. The name of the explorer is the name of the first class on that file.

In my example that is UserInterfaceModelExplorer:

   1: /// <summary>
   2: /// Double-derived class to allow easier code customization.
   3: /// </summary>
   4: internal partial class UserInterfaceModelExplorer : UserInterfaceModelExplorerBase
   5: {
   6:     /// <summary>
   7:     /// Constructs a new UserInterfaceModelExplorer.
   8:     /// </summary>
   9:     public UserInterfaceModelExplorer(global::System.IServiceProvider serviceProvider)
  10:         : base(serviceProvider)
  11:     {
  12:     }
  13: }

2. Create a custom class with that same name

Under the DSLPackage, you will need to create a new custom partial class with the name of your model explorer. All the magic will happen here.

3. Override the CreateElementVisitor method

Here you should subscribe to the ObjectModelBrowser.DoubleClick event:

   1: /// <summary>
   2: /// Executed when the model explorer creates the tree element visitor.
   3: /// </summary>
   4: /// <returns>The tree element visitor.</returns>
   5: protected override IElementVisitor CreateElementVisitor()
   6: {
   7:     // Subscribe double click in the tree view
   8:  
   9:     this.ObjectModelBrowser.DoubleClick += new EventHandler(this.ObjectModelBrowser_DoubleClick);
  10:  
  11:     // Default behavior
  12:  
  13:     return base.CreateElementVisitor();
  14: }

4. Implement the corresponding event handler

The magic consists in:

  • First, finding the element that is selected in the explorer,
  • Second, getting the corresponding model element,
  • And, finally, selecting that shape in the model viewer.

Check the full source code bellow for the implementation of these steps.

5. Transform All templates

Before you test the new behavior of the model explorer, you'll need to transform all the templates to let the DSL know that you have customized your model explorer.

And that's it. Double-click any domain class in the explorer and the model viewer will be updated accordingly.

Full Source Code

   1: using System;
   2: using System.Collections.ObjectModel;
   3: using Microsoft.VisualStudio.Modeling;
   4: using Microsoft.VisualStudio.Modeling.Diagrams;
   5: using Microsoft.VisualStudio.Modeling.Shell;
   6:  
   7: namespace MyDSL.UserInterface
   8: {
   9:     /// <summary>
  10:     /// Custom behavior for UserInterfaceModelExplorer.
  11:     /// </summary>
  12:     internal partial class UserInterfaceModelExplorer
  13:     {
  14:         #region Public Methods
  15:  
  16:         /// <summary>
  17:         /// Executed when the model explorer creates the tree element visitor.
  18:         /// </summary>
  19:         /// <returns>The tree element visitor.</returns>
  20:         protected override IElementVisitor CreateElementVisitor()
  21:         {
  22:             // Subscribe double click in the tree view
  23:  
  24:             this.ObjectModelBrowser.DoubleClick += new EventHandler(this.ObjectModelBrowser_DoubleClick);
  25:  
  26:             // Default behavior
  27:  
  28:             return base.CreateElementVisitor();
  29:         }
  30:  
  31:         #endregion
  32:  
  33:         #region Private Methods
  34:  
  35:         /// <summary>
  36:         /// Selects the specified shape using the given modeling document data.
  37:         /// </summary>
  38:         /// <param name="shapeElement">The shape element that should be selected.</param>
  39:         /// <param name="docData">The modeling document data.</param>
  40:         private static void SelectShape(ShapeElement shapeElement, DocData docData)
  41:         {
  42:             // Validation
  43:  
  44:             if (shapeElement == null)
  45:             {
  46:                 throw new ArgumentNullException("shapeElement");
  47:             }
  48:  
  49:             if (docData == null)
  50:             {
  51:                 throw new ArgumentNullException("docData");
  52:             }
  53:  
  54:             // Select the shape
  55:  
  56:             ModelingDocView docView = docData.DocViews[0];
  57:             if (docView != null)
  58:             {
  59:                 docView.SelectObjects(1, new object[] { shapeElement }, 0);
  60:             }
  61:         }
  62:         
  63:         /// <summary>
  64:         /// Gets the first shape the represents the specified model element.
  65:         /// </summary>
  66:         /// <param name="modelElement">The model element whose shape will be returned.</param>
  67:         /// <returns>The first shape the represents the specified model element.</returns>
  68:         private static ShapeElement GetModelElementFirstShape(ModelElement modelElement)
  69:         {
  70:             // Presentation elements
  71:  
  72:             LinkedElementCollection<PresentationElement> presentations = PresentationViewsSubject.GetPresentation(modelElement);
  73:             foreach (ModelElement element in presentations)
  74:             {
  75:                 ShapeElement shapeElement = (element as ShapeElement);
  76:                 if (shapeElement != null)
  77:                 {
  78:                     return shapeElement;
  79:                 }
  80:             }
  81:  
  82:             // Default result
  83:  
  84:             return null;
  85:         }
  86:  
  87:         /// <summary>
  88:         /// Return the model element which is the parent of the specified model element considering that
  89:         /// this element is placed in a compartment.
  90:         /// </summary>
  91:         /// <param name="modelElement">The model element whose parent will be returned.</param>
  92:         /// <returns>
  93:         /// The model element which is the parent of the specified model element considering that
  94:         /// this element is placed in a compartment.
  95:         /// </returns>
  96:         private static ModelElement GetCompartmentElementFirstParent(ModelElement modelElement)
  97:         {
  98:             // Get the domain class associated with model element.
  99:  
 100:             DomainClassInfo domainClass = modelElement.GetDomainClass();
 101:             if (domainClass != null)
 102:             {
 103:                 // A element is only considered to be in a compartment if it participates in only 1 embedding relationship
 104:                 // This might be wrong for some models
 105:  
 106:                 if (domainClass.AllEmbeddedByDomainRoles.Count == 1)
 107:                 {
 108:                     // Get a collection of all the links to this model element
 109:                     // Since this is in a compartment there will only be one
 110:  
 111:                     ReadOnlyCollection<ElementLink> links = DomainRoleInfo.GetAllElementLinks(modelElement);
 112:                     if (links.Count == 1)
 113:                     {
 114:                         // Get the model element participating in the link that isn't the current one
 115:                         // That will be the parent
 116:                         // Probably there is a better way to achieve the same result
 117:  
 118:                         foreach (ModelElement linkedElement in links[0].LinkedElements)
 119:                         {
 120:                             if (!modelElement.Equals(linkedElement))
 121:                             {
 122:                                 return linkedElement;
 123:                             }
 124:                         }
 125:                     }
 126:                 }
 127:             }
 128:  
 129:             // Default result
 130:  
 131:             return null;
 132:         }
 133:  
 134:         #endregion
 135:  
 136:         #region Event Handlers
 137:  
 138:         private void ObjectModelBrowser_DoubleClick(object sender, EventArgs e)
 139:         {
 140:             // Get the node selected in the model explorer tree
 141:  
 142:             ModelElementTreeNode node = (this.ObjectModelBrowser.SelectedNode as ModelElementTreeNode);
 143:             if (node != null)
 144:             {
 145:                 // Get the corresponding model element
 146:  
 147:                 ModelElement element = node.ModelElement;
 148:                 if (element != null)
 149:                 {
 150:                     // Get the corresponding shape
 151:                     // If the model element is in a compartment the result will be null
 152:  
 153:                     ShapeElement shape = GetModelElementFirstShape(element);
 154:                     if (shape == null)
 155:                     {
 156:                         // If the element is in a compartment, try to get the parent model element to select that
 157:  
 158:                         ModelElement parentElement = GetCompartmentElementFirstParent(element);
 159:                         if (parentElement != null)
 160:                         {
 161:                             // Get the corresponding shape
 162:  
 163:                             shape = GetModelElementFirstShape(parentElement);
 164:                         }
 165:                     }
 166:  
 167:                     // Select the shape
 168:  
 169:                     if (shape != null)
 170:                     {
 171:                         SelectShape(shape, this.ModelingDocData);
 172:                     }
 173:                 }
 174:             }
 175:         }
 176:  
 177:         #endregion
 178:     }
 179: }

Special thanks are in order to Oleg Sych for his fundamental tips to get this working.

Posted 16 December 08 04:22 by hgr | 1 Comments   
Filed under ,
Agile Prototyping

One of these days I went for a periodic review meeting of the project I'm working on. We were going to discuss the implementation of a critical piece for our next major milestone.

We invited our User Experience Specialist and he was going to present us the first UI prototypes.

I was expecting some great WPF "almost-ready" screens with great user experience stuff and a wonderful look-and-feel like all the other prototypes they did before...

This was what I got to see:

Now talk about agile prototyping. :)

Posted 11 December 08 11:28 by hgr | 1 Comments   
Filed under
DSL Tools #13 - Changing the Appearance of a Shape at Runtime

Finally I found some time to document this simple customization to the DSL Tools designers.

The requirement is as follows:

  • I have a given domain class - called Module Reference - that has a property called Main.
  • This domain property indicates if that particular class is the "most important" of that kind in the model.
  • This domain class as an association with another domain class - called Entity. That association allows me to connect multiple entities to any given module reference.
  • All the entities connected to the main module reference need to presented with a different color (different from the default shape color defined in the DSL definition model).

The domain model is the following:

Achieving this is quite simple:

1. Create a change rule to respond to the change in the Main property of the ModuleReference domain class:

   1: using System;
   2: using Microsoft.VisualStudio.Modeling;
   3: using Microsoft.VisualStudio.Modeling.Diagrams;
   4:  
   5: namespace MyModel
   6: {
   7:     /// <summary>
   8:     /// Defines a change rule for the ModuleReference domain class.
   9:     /// </summary>
  10:     [RuleOn(typeof(Primavera.Athena.Models.Reporting.ModuleReference), FireTime = TimeToFire.TopLevelCommit)]
  11:     public class ModuleReferenceChangeRule : ChangeRule
  12:     {
  13:         #region Public Methods
  14:  
  15:         /// <summary>
  16:         /// Alerts listeners that a property for an element has changed.
  17:         /// </summary>
  18:         /// <param name="e">Provides data for the ElementPropertyChanged event.</param>
  19:         public override void ElementPropertyChanged(ElementPropertyChangedEventArgs e)
  20:         {
  21:             // Validations
  22:  
  23:             if (e == null)
  24:             {
  25:                 throw new ArgumentNullException("e");
  26:             }
  27:  
  28:             if (e.DomainProperty.Id.Equals(ModuleReference.MainDomainPropertyId))
  29:             {
  30:                 HandleMainChanged(e);
  31:             }
  32:  
  33:             // Default behavior
  34:  
  35:             base.ElementPropertyChanged(e);
  36:         }
  37:         
  38:         #endregion
  39:  
  40:         #region Private Methods
  41:  
  42:         /// <summary>
  43:         /// Handles changes in Main.
  44:         /// </summary>
  45:         /// <param name="e">The <see cref="Microsoft.VisualStudio.Modeling.ElementPropertyChangedEventArgs"/> instance containing the event data.</param>
  46:         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
  47:         private void HandleMainChanged(ElementPropertyChangedEventArgs e)
  48:         {
  49:             // Something changed?
  50:  
  51:             if (!e.OldValue.Equals(e.NewValue))
  52:             {
  53:                 // Get the current shape
  54:  
  55:                 ModuleReference moduleReference = e.ModelElement as ModuleReference;
  56:                 if (moduleReference != null)
  57:                 {
  58:                     // Update all entities colors
  59:  
  60:                     foreach (Entity entity in moduleReference.Entities)
  61:                     {
  62:                         LinkedElementCollection<PresentationElement> presentations =
  63:                             PresentationViewsSubject.GetPresentation(entity);
  64:                         EntityShape entityShape = null;
  65:  
  66:                         if (presentations.Count > 0)
  67:                         {
  68:                             entityShape = presentations[0] as EntityShape;
  69:                         }
  70:  
  71:                         if (entityShape != null)
  72:                         {
  73:                             entityShape.UpdateMainModuleReferencePresentation((bool)e.NewValue);
  74:                         }
  75:                     }
  76:                 }
  77:             }
  78:         }
  79:  
  80:         #endregion
  81:     }
  82: }

Note: I first find all the EntityShape instances that are connected with the current ModuleReference and then call the UpdateMainModuleReferencePresentation passing in the new value of the Main property.

2. Register this change rule:

   1: using System;
   2:  
   3: namespace MyModel
   4: {
   5:     /// <summary>
   6:     /// Custom ReportingModel domain model methods.
   7:     /// </summary>
   8:     public partial class ReportingModelDomainModel
   9:     {
  10:         #region Protected Methods
  11:  
  12:         /// <summary>
  13:         /// Returns the non-generated domain model types.
  14:         /// </summary>
  15:         /// <returns>An array of types.</returns>
  16:         protected override Type[] GetCustomDomainModelTypes()
  17:         {
  18:             // Return the rules that should be evaluated for
  19:             // the model
  20:  
  21:             return new System.Type[] 
  22:             {
  23:                 typeof(AssociationConnectorChangeRule), typeof(GenerateDefaultListChangeRule), typeof(ModuleReferenceChangeRule)
  24:             };
  25:         }
  26:  
  27:         #endregion
  28:     }
  29: }

3. Create a new partial class for the EntityShape class and implement the UpdateMainModuleReferencePresentation method:

   1: using System;
   2: using System.Drawing;
   3: using Microsoft.VisualStudio.Modeling.Diagrams;
   4:  
   5: namespace MyModel
   6: {
   7:     /// <summary>
   8:     /// Provides custom code for the EntityShape domain class.
   9:     /// </summary>
  10:     public partial class EntityShape
  11:     {
  12:         #region Internal Methods
  13:  
  14:         /// <summary>
  15:         /// Updates the entity presentation to reflect if the corresponding module reference is the main.
  16:         /// </summary>
  17:         /// <param name="moduleRefereceIsMain">A boolean that indicates if the entity is associated with the main module.</param>
  18:         internal void UpdateMainModuleReferencePresentation(bool moduleRefereceIsMain)
  19:         {
  20:             StyleSet style = this.StyleSet;
  21:             BrushSettings brush = style.GetOverriddenBrushSettings(DiagramBrushes.ShapeBackground);
  22:             if (brush == null)
  23:             {
  24:                 brush = new BrushSettings();
  25:             }
  26:  
  27:             if (moduleRefereceIsMain)
  28:             {
  29:                 brush.Color = Color.FromKnownColor(KnownColor.CadetBlue);
  30:             }
  31:             else
  32:             {
  33:                 brush.Color = Color.FromKnownColor(KnownColor.Lavender);
  34:             }
  35:  
  36:             style.OverrideBrush(DiagramBrushes.ShapeBackground, brush);
  37:         }
  38:  
  39:         #endregion
  40:     }
  41: }

That's it.

Posted 30 October 08 05:43 by hgr | 2 Comments   
Filed under ,
More Posts Next page »