Posts Tagged ‘C#’

WPF XBAP: Import your Contacts from Outlook (demo)

Friday, November 6th, 2009
Autor: goncalo.chaves


 

[PT] (for a English version, see bellow on this page, please)

Olá a todos,

Recentemente num projecto interno nós estávamos a necessitar de utilizar algum tipo de activeX numa página Web, para que fosse possível a importação dos contactos do Outlook, tal como no linkedIn.

Portanto, para quê um activeX quando temos as aplicações XBAP? Hum… parece-me bem. Então aqui fica um pouco da experiência que tivemos no desenvolvimento desta brincadeira. Em primeiro lugar, não esquecer que se a nossa aplicação vai interagir com um dos membros do Microsoft Office, as Visual Tools for Office têm as bibliotecas certas para isto!

Eis os nossos passos:

1) Importação da biblioteca do Outlook para a nossa aplicação:

using Microsoft.Office.Interop.Outlook;

2) Criação dos tipos necessários para a transmissão com o Outlook e leitura dos dados:

private MAPIFolder oContactsFolder = null;
//load outlookcontacts
           var oApp = new Microsoft.Office.Interop.Outlook.Application();
           NameSpace oNS = oApp.GetNamespace("MAPI");
           oContactsFolder = oNS.PickFolder();
      
           string filter = "[MessageClass] = \"IPM.Contact\"";
           Items oContactItems = oContactsFolder.Items.Restrict(filter);
 
           foreach (ContactItem oContact in oContactItems)
           {
               var item = new EmailContact();
              if (oContact != null)
               {
                   item.ContactEmail = oContact.Email1Address;
                   item.ContactName = oContact.FullName;
                   //before add the email let's see that's is valid
                   if (ValidateEmailAdd(item.ContactEmail) && !String.IsNullOrEmpty(item.ContactEmail))
                   {
                       contactList.Add(item);
                   }
               }

3) Definição da interface e da lógica da aplicação

Para tal escolhemos a XBAP para que nos seja possível aplicar todas as funcionalidades que o WFP nos oferece, mas para tal existem algumas notas importantes a ter em consideração:

- A XBAP deverá correr em modo de “full trust”, e para tal é necessário um certificado digital que assine o código da aplicação, e obviamente o nosso browser deverá confiar no mesmo.

image thumb WPF XBAP: Import your Contacts from Outlook (demo)

Para tal, criámos um certificado fullsix CA, algo que fomos seguindo aqui:

http://msdn.microsoft.com/en-us/library/aa194055%28office.11%29.aspx

Outro post com informações muito importantes sobre esta parte:

http://blogs.microsoft.co.il/blogs/maxim/archive/2008/03/05/wpf-xbap-as-full-trust-application.aspx

Para a extracção do certificado seguimos:

http://blogs.microsoft.co.il/blogs/maxim/archive/2008/03/31/how-to-run-wpf-xbap-application-in-full-trust-mode-post-2-certificate-extraction.aspx

Para automatizar este processo, desenvolvemos uma pequena aplicação de consola que permite o download e a instalação silenciosa do mesmo. Assim o certificado fullsix CA fica instalado no computador, que assina o certificado que usamos para assinar o código da nossa XBAP application.

image thumb1 WPF XBAP: Import your Contacts from Outlook (demo)

Aqui fica o código que usámos:

try
           {
 
               var myStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("InstallXBAPOutlookCert.Console.f6signing.cer");
 
               byte[] b = new byte[myStream.Length];
 
               myStream.Read(b, 0, b.Length);
 
               X509Certificate2 cert = new X509Certificate2(b);
 
               X509Store store = new X509Store(StoreName.AuthRoot, StoreLocation.LocalMachine);
 
               store.Open(OpenFlags.ReadWrite);
 
               store.Add(cert);
 
               store.Close();
 
               store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine);
 
               store.Open(OpenFlags.ReadWrite);
 
               store.Add(cert);
 
               store.Close();
 
               System.Console.WriteLine("Certificate Successfully Installed...!");
 
               System.Console.Read();
           }
           catch (Exception ex)
           {
               
               System.Console.WriteLine("Error " + ex.ToString());

}

Não esquecer de embeber o ficheiro de manifesto da aplicação dentro do ficheiro exe da aplicação, isto é necessário para utilizar os direitos de Administrador da máquina, para que seja possível fazer a instalação do certificado. É necessário alterar este manifesto como se segue:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

E para embeber o manifesto no exe:

image thumb2 WPF XBAP: Import your Contacts from Outlook (demo)

O utilizador vê o ficheiro .exe no browser e se no seu caso a empresa possuir um certificado assinado por uma das bem conhecidas CA’s não deverá existir qualquer problema, no nosso caso, os utilizadores deverão confiar no nosso certificado, para que seja possível visualizar a demo correctamente :)

image thumb3 WPF XBAP: Import your Contacts from Outlook (demo)

Depois o certificado fullsix CA fica instalado no computador tal como se pode ver abaixo:

image thumb4 WPF XBAP: Import your Contacts from Outlook (demo)

Agora a aplicação XBAP está devidamente assinada e é confiada pelo browser e tudo corre em modo full trust.

Nota:

- WPF threading module. É um pouco doloroso entender para quem está mais familiriarizado com o modo de threading do silverlight como é o nosso caso. Em WPF existem algumas diferenças, o que significia que é necessário pensar sobre esta questão para que não se bloqueie a thread da Interface. Imaginando por exemplo uma animação de loading enquando a comunicação com o Outlook é feita. Um bom artigo para ajudar nesta questão:

http://msdn.microsoft.com/en-us/library/ms741870.aspx

Para evitar o bloqueio da thread principal da interface, lançamos o método de interacção com o Outlook noutra thread da seguinte forma:

var myDispatcher = new Thread(new ParameterizedThreadStart(s => { StartRetriveContacts(); }));

Depois no regresso á thread original:

private void StartRetriveContacts()
       { …
UIDispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { ProcessContacts(contactList); },
                                    contactList);

Usando este tipo de “roteamento” entre as threads permite-nos fazer um update constante na interface de modo a dar feedback constante ao utilizador sobre as operações que decorrem em background.

E pronto :) é isto, podem testar. http://labs.fullsix.pt/projects/xbapoutlookdemo/demo.htm e já agora dar uma vista de olhos nos outros projectos que estão no fullsix Labs.

Queria deixar também uma nota de agradecimento aos meus colegas Antoine e Fiel pela ajuda no desenvolvimento.

Merci.

 

[EN]

Hi all,

In order for a internal project we need some kind of an activeX in a web page from Outlook contact email addresses importation, such has like linkedIn has.

So why use a activeX when we have XBAP apps? Sweet hum? Here is how we’ve done the little app. First of all keep in mind this is something to interop with an Microsoft Office application, right? The Visual Tools for Office have the right libraries for it!

Steps:

1) Import the Microsoft Interop Namespace:

using Microsoft.Office.Interop.Outlook;

easy as butter

2) Create the right types to exchange with outlook and load content

private MAPIFolder oContactsFolder = null;
//load outlookcontacts
           var oApp = new Microsoft.Office.Interop.Outlook.Application();
           NameSpace oNS = oApp.GetNamespace("MAPI");
           oContactsFolder = oNS.PickFolder();

           string filter = "[MessageClass] = \"IPM.Contact\"";
           Items oContactItems = oContactsFolder.Items.Restrict(filter);

           foreach (ContactItem oContact in oContactItems)
           {

               var item = new EmailContact();

               if (oContact != null)
               {
                   item.ContactEmail = oContact.Email1Address;
                   item.ContactName = oContact.FullName;

                   //before add the email let's see that's is valid

                   if (ValidateEmailAdd(item.ContactEmail) && !String.IsNullOrEmpty(item.ContactEmail))
                   {
                       contactList.Add(item);
                   }
               }

3) Define your UI and the rest of the behaviors

We choose a XBAP application in order to have the WPF capabilities in a regular web page but you must be aware of some important settings:

- XBAP app needs to run in a full trust mode, so that requires a DigitalCertificate to sign your code and the browser needs to trust on that.

image thumb WPF XBAP: Import your Contacts from Outlook (demo)

So we created a fullsix CA certificate. Something that follows here:

http://msdn.microsoft.com/en-us/library/aa194055%28office.11%29.aspx 

You can see a quite nice post’s about this at:

http://blogs.microsoft.co.il/blogs/maxim/archive/2008/03/05/wpf-xbap-as-full-trust-application.aspx 

and for the certificate extraction

http://blogs.microsoft.co.il/blogs/maxim/archive/2008/03/31/how-to-run-wpf-xbap-application-in-full-trust-mode-post-2-certificate-extraction.aspx

to automate this process we develop a simple console application that you download first and installs on your computer our fullsix CA certificate, that signs the code sign certificate that we use previously to sign the xbap code.

image thumb1 WPF XBAP: Import your Contacts from Outlook (demo)

Here is the code for that:

try
           {

               var myStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("InstallXBAPOutlookCert.Console.f6signing.cer");

               byte[] b = new byte[myStream.Length];

               myStream.Read(b, 0, b.Length);

               X509Certificate2 cert = new X509Certificate2(b);

               X509Store store = new X509Store(StoreName.AuthRoot, StoreLocation.LocalMachine);

               store.Open(OpenFlags.ReadWrite);

               store.Add(cert);

               store.Close();

               store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine);

               store.Open(OpenFlags.ReadWrite);

               store.Add(cert);

               store.Close();

               System.Console.WriteLine("Certificate Successfully Installed...!");

               System.Console.Read();
           }
           catch (Exception ex)
           {

               System.Console.WriteLine("Error " + ex.ToString());
           }

Don’t forget to embed the application manifest in the exe file, you need that for auto Admin rights permissions to install :) like this in your manifest file:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

and to embed it on the exe:

image thumb2 WPF XBAP: Import your Contacts from Outlook (demo)

The user see’s the .exe on the browser and if your company has a certificate that’s signed by one of very well known CA you should haven’t any trouble. In this case our users must have trust on us :)  

image thumb3 WPF XBAP: Import your Contacts from Outlook (demo)

 

then you have the fullsix CA in your machine as you can see here:

image thumb4 WPF XBAP: Import your Contacts from Outlook (demo)

So now XBAP application is signed and User grant a full trust environment for it.

Note:

- WPF threading module. It’s some kind of “pain”, for those who are more familiar with Silverlight like me, in WPF there are some differences, which means that you have to think about threading block handling, in order to avoid frozen the main UI thread, that can being display, for example, a loading animation of the contact import progress. for that there are also a nice article:

 http://msdn.microsoft.com/en-us/library/ms741870.aspx 

To avoid the main UI thread block we launched the Outlook interop method in another thread, here’s the way:

var myDispatcher = new Thread(new ParameterizedThreadStart(s => { StartRetriveContacts(); }));

then to get back on the main UI:

private void StartRetriveContacts()
       { …
UIDispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { ProcessContacts(contactList); },
                                    contactList);

Using this kind of “routed threading model” we were able to provide an UI update with some animation and application update state.

And that’s it..! you can try for your self 

http://labs.fullsix.pt/projects/xbapoutlookdemo/demo.htm , and take a look into our others fullsix labs projects.

Also a thanks to Antoine and Fiel for their help on this development.

Silverlight: Colors in Hexadecimal values

Monday, November 2nd, 2009
Autor: goncalo.chaves


 

[PT]

Gostaria de deixar um pequeno truque para se trabalhar com cores nos controlos de Siverlight. Imaginem que instanciam um controlo como por exemplo uma textbox:

var newTextBox = new TextBox();

Agora, gostaria de mudar a color de fundo para preto, vermelho, verde, etc…

Então será necessário escrever algo como: myTextBox.Background = new Brush(Opacity bla blabla, etc); etc, e mais bla… Mas eu só queria algo como .Background = Colors.Red;

Ok, eis algo que pode ajudar e muito. Criem um método para extraír de um valor Hexadecimal a vossa cor como por ex:

private SolidColorBrush GetColorFromHex(string myColor)
{
    return new SolidColorBrush(
        Color.FromArgb(
            Convert.ToByte(myColor.Substring(1, 2), 16),
            Convert.ToByte(myColor.Substring(3, 2), 16),
            Convert.ToByte(myColor.Substring(5, 2), 16),
            Convert.ToByte(myColor.Substring(7, 2), 16)
        )
    );
}

Agora, é mais simples atribuir ao nosso controlo:

newTextBox.Background = GetColorFromHex("#00FFFFFF");

Cool! não?

[EN]

Just a tip, if you want in your code work with an color in Hexadecimal format maybe a little complicate.

Imagine that you create a new control such as:

var newTextBox = new TextBox();

Now you want give a color to it, right? So to do something like myTextBox.Background = new Brush(Opacity … etc, etc, etc). :s I just want a simple red, white… or something similar…

Here’s a tip, create a method to extract a color from an Hexadecimal value:

private SolidColorBrush GetColorFromHex(string myColor)
      {
          return new SolidColorBrush(
              Color.FromArgb(
                  Convert.ToByte(myColor.Substring(1, 2), 16),
                  Convert.ToByte(myColor.Substring(3, 2), 16),
                  Convert.ToByte(myColor.Substring(5, 2), 16),
                  Convert.ToByte(myColor.Substring(7, 2), 16)
              )
          );
      }

now it’s more simple to use on your control:

newTextBox.Background = GetColorFromHex("#00FFFFFF");

Cool! Isn’t it?

Silverlight: Creating Silverlight animations with code

Monday, November 2nd, 2009
Autor: goncalo.chaves


 

[PT]

Na semana passada estive a desenvolver uma aplicação em Silverlight para um cliente, e com algumas modificações que foram solicitadas, foi necessário desenvolver uma animação totalmente dinâmica… portanto o Blend deixou de servir para o efeito e tudo passou a ser feito por código C#.

Ok, então comecei por tentar mapear a mesma estrutura de código na parte de animação em XAML mas em C#. As linhas de código que seguem, serviram para fazer uma pequena animação de fade in e fade out a um usercontrol, instanciado no momento de execução e adicionado ao layoutroot. Uma vez que esta animação seria usada várias vezes.

// Dymanic Storyboard with the animation
var currentStb = new Storyboard();

//EasyDoubleKeyFrames
var stbkey = new EasingDoubleKeyFrame();
stbkey.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
stbkey.Value = 0;
var stbkey2 = new EasingDoubleKeyFrame();
stbkey2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(3));
stbkey2.Value = 1;
var stbkey3 = new EasingDoubleKeyFrame();
stbkey3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(6));
stbkey3.Value = 0;

//DoubleAnimationKeyFrame declaration
var myDoubleAnim = new DoubleAnimationUsingKeyFrames();
myDoubleAnim.KeyFrames.Add(stbkey);
myDoubleAnim.KeyFrames.Add(stbkey2);
myDoubleAnim.KeyFrames.Add(stbkey3);

//wich property is targeted: opacity
Storyboard.SetTargetProperty(myDoubleAnim, new PropertyPath("(UIElement.Opacity)"));

//adding into the layout resources
this.Resources.Add("uniqueId"+_countName.ToString(),currentStb);

//adding into the current layoutRoot
currentStb.Children.Add(myDoubleAnim);
currentStb.SetValue(Storyboard.TargetNameProperty, newTextBox.Name);
//adding some logical stuff then the stb begins
currentStb.Begin();

Recapitulando, não é assim tão complicado como estava à espera, ou pelo menos no primeiro contacto, mas não é obvio, o truque reside em “copiar” a mesma estrutura que o Blend gera em XAML, mas desta em código. Portanto inicialmente define-se os keyframes que contêm os tempos de animação e o values para as propriedades que pretendemos afectar ao controlo animado. Seguidamente, definimos os “alvos” para os DoubleAnimationKeyFrame que compõe a metada da animação em questão, e por fim, o nosso storyboard recebe esta informação e define qual é o tipo de objecto “afectado”, neste caso, um UIElement, e que tipo de animação será feita. Daí a afectação da propriedade Opacity.

É importante reter o seguinte, após criar um storyboard por esta maneira, é necessário dar-lhe um nome único e adicioná-lo aos resources do nosso layoutRoot. É só desta forma que tudo funcionará correctamente, e uma vez que a nossa animação fica a pertencer aos recursos locais do controlo, podemos usar a mesma várias vezes na aplicação :)

Espero que vos seja útil, e deixou uma leitura muito recomendável sobre este assunto.

http://msdn.microsoft.com/en-us/library/cc189069(VS.95).aspx 

Merci.

[EN]

Last week I was developing a Silverlight application for a customer, and after a few modifications it was necessary to make an dynamic animation … so no storyboards with Blend… get in the code and do everything with C#.

Okay… so I started by mapping the same XAML animation structure, into the code. The next few lines show us a simple fade in and fade out animation effect with a new usercontrol (a simple textbox) created at the moment and placed on LayoutRoot. This animation is used several times, during the application.

// Dymanic Storyboard with the animation
var currentStb = new Storyboard();

//EasyDoubleKeyFrames
var stbkey = new EasingDoubleKeyFrame();
stbkey.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
stbkey.Value = 0;
var stbkey2 = new EasingDoubleKeyFrame();
stbkey2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(3));
stbkey2.Value = 1;
var stbkey3 = new EasingDoubleKeyFrame();
stbkey3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(6));
stbkey3.Value = 0;

//DoubleAnimationKeyFrame declaration
var myDoubleAnim = new DoubleAnimationUsingKeyFrames();
myDoubleAnim.KeyFrames.Add(stbkey);
myDoubleAnim.KeyFrames.Add(stbkey2);
myDoubleAnim.KeyFrames.Add(stbkey3);

//wich property is targeted: opacity
Storyboard.SetTargetProperty(myDoubleAnim, new PropertyPath("(UIElement.Opacity)"));

//adding into the layout resources
this.Resources.Add("uniqueId"+_countName.ToString(),currentStb);

//adding into the current layoutRoot
currentStb.Children.Add(myDoubleAnim);
currentStb.SetValue(Storyboard.TargetNameProperty, newTextBox.Name);
//adding some logical stuff then the stb begins
currentStb.Begin();

To recap, it’s not so complicated, first we create the storyboard, then the keyframes that contains the times and the values for the animation sequence, then we define the target properties, such as the DoubleAnimation, the Opacity property target and the storyboard to apply on the current usercontrol or the UIElement.

Just regard that after you create an Storyboard this has to belong into the current layout resource in order to work, with a unique Id. So that can be re-used in your application.

A recommended lecture:

http://msdn.microsoft.com/en-us/library/cc189069(VS.95).aspx 

Thanks.

Regular expressions…!

Friday, October 30th, 2009
Autor: cesar.silva


Há relativamente pouco tempo atrás, pediram-me para incluir em dada página de um dos nossos clientes uma secção que fosse buscar tweets de uma dada conta. Como seria de esperar encontrei no site do Twitter informação sobre as libraries existentes para C# e depois de alguma leitura acabei por utilizar aquela que me pareceu melhor ir de encontro às minhas necessidades, que foi o TweetSharp. O site por si já faz um excelente trabalho a explicar como a API funciona pelo que não vou perder muito tempo com isso.

Fiz o que tinha a fazer e reparei que a estrutura de cada tweet vinha com a propriedade Text, sem qualquer tipo de formatação (e ainda bem), o que me fez deparar com uma necessidade: como formatar os links dos tweets?! Como já devem ter visto, cada tweet pode conter referências a @users, #trends ou http://weblinks, e como tal costumam ser links.

Para mim tornou-se claro que este era um problema a ser resolvido com regular expressions.

Geek Stuff!

As regular expressions permitem-nos procurar padrões em variáveis de texto e determinar se pretendemos executar acções sobre as mesmas caso esse mesmo padrão seja encontrado. Para isso, utilizamos a classe Regex.

Para efeito de exemplo, criei a classe TweetFormatter com o método Twitterize que converte a esta variável RawTweet num tweet formatado com links.

RawTweet = "@csilva parece que o @rfiel andou a comentar sobre #silverlight ultimamente. Mais informações em http://silverlight.net/";
Tweet = TweetFormatter.Twitterize(RawTweet);

Utilizando o método Replace da classe Regex, conseguimos substituir todas as ocorrências que obedeçam ao padrão por nós definido através de uma regular expression, por uma expressão por nós definida através de um MatchEvaluator, que não é nada mais que um delegate que recebe o Match encontrado na expressão original.

internal static string Twitterize(string rawTweet)
{

  // Return the tweet itself if null or empty
  if(String.IsNullOrEmpty(rawTweet))
      return rawTweet;

  // Replace all the user links
  var twitterUserRegex = new Regex(@"(?<startPattern>(^|[^a-z0-9])@)(?<twitterUser>\w+)");
  var resultTweet = twitterUserRegex.Replace(rawTweet, TwitterUserEvaluator);

  // Replace all the trend links
  var twitterTrendRegex = new Regex(@"(?<startPattern>(^|\s))\#(?<twitterTrend>\w+)");
  resultTweet = twitterTrendRegex.Replace(resultTweet, TwitterTrendEvaluator);

  return resultTweet;
}

Com efeito, reparem que estou a utilizar 2 métodos diferentes, para determinar a expressão pela qual um @user ou uma #trend serão substituídos.

private static string TwitterUserEvaluator(Match currentMatch)
{
  var userTwitterLink =
    String.Format("{0}<a href=\"http://twitter.com/{1}\" target=\"_blank\">{1}</a>",
      currentMatch.Groups["startPattern"],
      currentMatch.Groups["twitterUser"]);
  return userTwitterLink;
}

private static string TwitterTrendEvaluator(Match currentMatch)
{
  var userTwitterLink =
    String.Format("{0}<a href=\"http://twitter.com/#search?q=%23{1}\" target=\"_blank\">#{1}</a>",
      currentMatch.Groups["startPattern"],
      currentMatch.Groups["twitterTrend"]);
  return userTwitterLink;
}

Para quem quiser os ficheiros com a implementação deste exercício, pode retirá-los em Source Code.

Wicked Tools!

Para quem não conhecer fica aqui a sugestão: existe uma ferramenta muito boa não só para testar como para aprender a trabalhar com regular expressions que se chama Expresso e foi desenvolvida pela Ultrapico.

Sorted…!!! :)

Thursday, October 29th, 2009
Autor: cesar.silva


Uma vez numa entrevista de trabalho perguntaram-me:

“Se tiveres um problema em mãos e não souberes como o resolver, o que fazes?!”

Comecei por achar a pergunta um tanto ou quanto estranha e vaga mas era nitidamente uma daquelas que poderia fazer a diferença entre ser contratado ou não. A resposta que o entrevistador estava à procura era:

“Quando tudo o resto falhar e todos os que conhecer não souberem… vou ao Google! Se não estiver no Google, então é porque não existe…”

Há uns tempos atrás andei à procura de uma forma de ordenar listas/colecções/arrays por múltiplas colunas. Nesta altura Linq não era opção. Como qualquer bom programador, fui ao Google procurar por quem já tivesse partido pedra e encontrei um artigo muito interessante:

Sorting with Objects on multiple fields
http://www.codeproject.com/KB/recipes/Sorting_with_Objects.aspx

“Nada como o belo de um copy/paste para dentro do nosso projecto para resolver o problema!” – pensei.

O código estava interessante, eu faria algumas coisas de forma diferente e rapidamente deu para perceber que não funcionava assim tão bem como haviam vendido. A ordenação só funcionava correctamente para o primeiro campo. Como o meu amigo Antoine diria:

“Não são bugs… são novas e entusiasmantes oportunidades de refactoring!” :)

Dito, feito… e resolvi criar a minha própria classe de ordenação utilizando reflection e alguma recursividade.

Geek stuff!

Aquilo que eu queria era no fundo ordenar um array de objectos (que a título de exemplo são os developers da fullsix)

// Initialize array
var fullsixEmployees = new[] {
    new FullsixEmployee {
        Id = 1,
        Name = "César Silva",
        Birthdate = new DateTime(1977, 7, 16),
        IsPortuguese = true
    },
    new FullsixEmployee {
    ...
    ...
    

A ideia era conseguir dizer de alguma forma que queria ordenar este array por um número ilimitado de propriedades e determinar a direcção dessa ordenação.

// Sort the fullsix employee's array
Array.Sort(fullsixEmployees, new MultiColumnComparer<FullsixEmployee>("IsPortuguese DESC, Birthdate"));

A “magia” reside basicamente nos 2 métodos que determinam a comparação entre as propriedades de cada objecto. Ao invocar o método Sort da classe Array, passamos como argumentos o array a ordenar, seguido de um IComparer<T> que neste caso é a nossa classe MultiColumnComparer.

public int Compare(T x, T y)
{

  // Initialize the source object's type
  var columnsOrder = GetColumnsOrder(this.OrderExpression);
  var comparissonResult = 0;

  if (0 < columnsOrder.Count)
      comparissonResult = ColumnCompare(x, y, columnsOrder, 0);

  return comparissonResult;
}

Após esta primeira invocação há que chamar o segundo método de nome ColumnCompare de forma a permitir a recursividade entre as várias propriedades (caso a comparação de determinada propriedade resulte em igualdade).

public int ColumnCompare(T x, T y, IList<Column> columnsOrder, int columnIndex)
{

  // Get the object's type
  var sourceType = x.GetType();
  var columnProperty = sourceType.GetProperty(columnsOrder[columnIndex].Name);

  // Initialize the comparisson result object
  var comparissonResult = 0;
  comparissonResult = Comparer.DefaultInvariant.Compare(columnProperty.GetValue(x, null), columnProperty.GetValue(y, null));

  // Invert the order if descending
  if (columnsOrder[columnIndex].Direction.Equals(ColumnDirection.Descending))
      comparissonResult = -comparissonResult;

  // If the properties are equal then go to the next property (if available)
  if (comparissonResult.Equals(0) && columnIndex < columnsOrder.Count - 1)
      return ColumnCompare(x, y, columnsOrder, columnIndex + 1);

  return comparissonResult;
}

Através de reflection conseguimos aceder às propriedades dos objectos a serem comparados e através de um ciclo recursivo aceder aos valores de cada uma delas para estabelecer as comparações. O ciclo mantém-se enquanto existir igualdade entre as propriedades a serem comparadas e a direcção da comparação é determinada pela negação do resultado, caso seja descendente.

Acabou por ser um exercício engraçado para brincar mais uma vez com a ordenação de listas/colecções/arrays.

Para quem quiser os ficheiros com a implementação deste exercício, pode retirá-los em Source code.


Better Tag Cloud