|
Introduzione:
In questo articolo costruiremo un Add-in per Expression Web 1 e 2, in particolare useremo la stessa dll creata per l'add-in per Windows Live Writer che potete trovare nella pagina Uitlity per gli sviluppatori di questo sito per generare Html formattato a partire da codice VB, C# ecc. in una pagina web creata con Expression Web.
Expression Web fa parte della famiglia Microsoft Office e come gli altri prodotti espone delle funzionalità che possono essere intercettate ed usate costruendo un apposita dll COM. Quindi il nostro obbiettivo è creare una dll COM con Visual Studio 2005/2008 e poi registrarla in modo che sia vista da Expression Web.
Il progetto:
Creiamo un nuovo progetto Visual Studio di tipo "Class Library". Visual Studio crea in automatico una classe chiamata "Class1.vb" o "Class1.cs". Rinominiamo il file in "Connect.vb" o "Connect.cs" e anche la classe nel codice:
//C#
public class Connect
'VB
Public Class Connect
Per cominiciare, aggiungiamo alcune reference al progetto.
Va ricordato che tutti gli add-ins COM implementano l'interfaccia IDTExtensibility2, quindi dobbiamo referenziare il componente "extensibility".
Fra i 3 proposti prendiamo quello nella cartella commons files anche se fondamentalmente sono tutti e 3 uguali.

Aggiungiamo il reference alla libreria principale di Office che è una libreria COM, quindi selezioniamo l'apposito tab:

Aggiungiamo i reference agli oggetti COM Microsoft "Expression Web 12.0 Web Object Reference Library" e "Microsoft Expression Web 12.0 Page Object Reference Library" e infine alla dll "ProgrammerPasterControls.dll".
Trasciniamo la dll "CSharpFormat.dll" che fornisce i servizi di Programmer paster nel progetto e impostiamogli le prorietà "Build Action" = "Content" e "Copy to Output Directory" = "Copy Always":

Per funzionare con Expression Web la dll che stiamo generando deve essere una dll COM. Con Visual Studio si può ottenere un output COM compatibile, configurando il progetto come segue:
- Dal menu di Visual Studio selezioniamo "Project" > "ProgrammerPasterExpWeb... Properties..."
- Selezioniamo il pannello "Compile" se usiamo VB o "Build" se usiamo C#.
- Nella combo "Configuration" selezioniamo "All Configurations"
- Spuntiamo la casella "Register for COM interop"
- Selezioniamo il pannello "Application"
- Permiamo il bottone "Assembly Information"
- E spuntiamo la casella Make Assembly COM-Visible
Adesso la nostra dll può funzionare anche come oggetto COM.
Registrare la dll COM:
Come tutte le dll COM la nostra dll deve essere registrata per essere riconosciuta, inoltre la dobbiamo registrare a questa chiave:
//Expression Web 1.0
HKEY_CURRENT_USER\Software\Microsoft\Expression\Web Designer\Addins
//Exprwssion Web 2.0
HKEY_CURRENT_USER\Software\Microsoft\Expression\Web 2.0\Addins
In modo che Expression Web la riconosca. Visual Studio registra per noi la dll che compiliamo come COM Visible in automatico, solo se cambiassimo PC dobbiamo ricordarci di registrarla. Per la chiave relativa ad Expression web apriamo regedit, ci spostiamo sulla cartella sopra indicata, creiamo una nuova chiave e la chiamiamo "ProgrammerPasterExpWebVB.Connect" o "ProgrammerPasterExpWebCS.Connect" e aggiungiamo i seguenti valori:
- Una DWORD chiamata "CommandLineSafe" col valore 0. (Indica se volete o no che Expression Web carichi l'add-in quando viene lanciato via linea di comando)
- Una stringa chiamata "Description" in cui scrivete la descrizione del vostro Add-in.
- Una stringa chiamata "FriendlyName" in cui scrivete la descrizione che appariarà nella finestra "Tools" > "Add-ins.." di Expression Web.
- Una DWORD chiamata "LoadBehavior" col valore 3 (Che indica ad Expression Web che l'Add-in deve essere connesso e caricato. Altri valori possbili sono: 0 che indica che l'add-in è disconnesso, 1 - che indica che l'add-in è connesso, 2 - l' add.in sarà caricato allo startup dell'applicazione, 8 - l'add-in sarà caricato via programma o dal utente, 16 - l'add-in è caricato la prima volta che Expression Web parte, e scaricato quando viene chiuso).
Fatto questo è possibile esportare la chiave appena creata per distribuire l'addin, oppure crearla in fase di setup.
L'interfaccia Extensibility.IDTExtensibility2:
Per implementare l'interfaccia Extensibility.IDTExtensibility2, chiediamo a Visual Studio di implementare per noi l'iterfaccia in automatico utilizzando gli appositi smart tag che ci vengono evidenziati quando aggiungiamo alla classe l'implementazione dell'interfaccia. Verranno creati 5 metodi vuoti in VB e 5 metodi che lanciano un'eccezione in C# (in questo caso rimuoviamo le righe di codice che lanciano l'eccezione).
Creiamo un oggetto "Microsoft.Expression.Interop.WebDesigner.Application" che ci servirà per gestire l'applicazione Expression Web e gli eventi che solleva durante il suo normale funzionamento:
//C#
private Microsoft.Expression.Interop.WebDesigner.Application ewApp;
'VB
Private WithEvents ewApp As Microsoft.Expression.Interop.WebDesigner.Application
Implementiamo il metodo "OnConnection" (che viene chiamato quando l'add-in viene connesso all'applicazione) e assegnamo l'istanza dell'applicazione alla variabile globale creata in precedenza:
//C#
ewApp = (Microsoft.Expression.Interop.WebDesigner.Application)Application;
'VB
ewApp = CType(Application, Microsoft.Expression.Interop.WebDesigner.Application)
Il compito dell'add-in ProgrammerPaster è quello di trasformare una stringa di codice C#, VB, HTML, Sql, o Batch in una stringa HTML colorata in base ad uno schema CSS che assegna un formato predefinito a: definizioni, parole chiave, commenti del linguaggio selezionato. Questo add-in è pratico e lo usiamo anche per scrivere le sezioni di codice dell'articolo corrente. In particolare è utile sia mentre scriviamo nella finestra di design sia nella finestra del codice html. Quindi vogliamo collegarlo al menu di queste 2 finestre. Per farlo, è necessario trovare i nomi delle barre degli strumenti, per fare questo bisogna far partire il nostro progetto in Debug, (come farlo è specificato più avanti) fermarsi in debug e controllare la proprietà ewApp.CommandBars. Potremo così scoprire i nomi delle le 2 finestre che ci interessano e creare 2 costanti:
//C#
//Finestra di scrittura normale
private const string VIEWNORMAL = "Normal Page View Context Menu";
//Finestra di codice HTML
private const string VIEWHTML = "Html Page View Context Menu";
'VB
'Finestra di scrittura normale
Private Const VIEWNORMAL As String = "Normal Page View Context Menu"
'Finestra di codice HTML
Private Const VIEWHTML As String = "Html Page View Context Menu"
Estraiamo le "CommandBar" che ci interessa modificare e inseriamo i due nuovi bottoni, uno per menu, dopo averli dichiarati come globali alla classe:
//C#
private CommandBarButton mCbBtnNormalView;
private CommandBarButton mCbBtnHtmlView;
...
CommandBar cbNormalView = ewApp.CommandBars[VIEWNORMAL];
CommandBar cbHtmlView = ewApp.CommandBars[VIEWHTML];
mCbBtnNormalView = (CommandBarButton)cbNormalView.Controls.Add(MsoControlType.msoControlButton, 1, null,
cbNormalView.Controls.Count - 1, true);
mCbBtnHtmlView = (CommandBarButton)cbHtmlView.Controls.Add(MsoControlType.msoControlButton, 1, null,
cbHtmlView.Controls.Count - 1, true);
'VB
Dim mCbBtnNormalView As CommandBarButton
Dim mCbBtnHtmlView As CommandBarButton
...
Dim cbNormalView As CommandBar = ewApp.CommandBars(VIEWNORMAL)
Dim cbHtmlView As CommandBar = ewApp.CommandBars(VIEWHTML)
mCbBtnNormalView = DirectCast(cbNormalView.Controls.Add(MsoControlType.msoControlButton, 1, Nothing, _
cbNormalView.Controls.Count - 1, True), CommandBarButton)
mCbBtnHtmlView = DirectCast(cbHtmlView.Controls.Add(MsoControlType.msoControlButton, 1, Nothing, _
cbHtmlView.Controls.Count - 1, True), CommandBarButton)
Impostiamo la proprietà "Caption" dei bottoni creati a "Programmer Paster". Questa sarà la descrizione che comparirà sul Menu delle finestre.
Il problema successivo è produrre l'icona che comparirà accanto all'etichetta del bottone appena creata. Questa icona deve essere un immagine COM OLE non gestita (più precisamente una: OLE-provided picture object). Fortunatamente "System.Windows.Forms.AxHost" ci mette a disposizione un set di funzioni ActiveX e fra queste c'è una (protected) che ci permettone di convertire un oggetto immagine "System.Drawing.Image" in un "OLE-provided picture object". Aggiungiamo quindi al nostro progetto un reference alla dll "System.Windows.Forms" e creiamo una nuova classe che chiameremo "ImageConverter". Questa classe dovrà derivare da "System.Windows.Forms.AxHost" in modo da poter esporre la funzione che ci interessa:
//C#
internal class ImageConverter : System.Windows.Forms.AxHost
{
private ImageConverter() : base(null)
{
}
public static stdole.IPictureDisp Convert(System.Drawing.Image image)
{
return (stdole.IPictureDisp)AxHost.GetIPictureDispFromPicture(image);
}
}
'VB
Public Class ImageConvert
Inherits Windows.Forms.AxHost
Public Sub New()
MyBase.New(Nothing)
End Sub
Public Shared Function Convert(ByVal image As System.Drawing.Image) As stdole.IPictureDisp
Return DirectCast(Windows.Forms.AxHost.GetIPictureDispFromPicture(image), _
stdole.IPictureDisp)
End Function
End Class
Creiamo l'immagine che ho precedentemente inserito nelle resources del progetto per assegnarla ai due bottoni (mi raccomando, l'immagine per funzionare deve essere una bitmap 16x16):
//C#
stdole.IPictureDisp pictureDisp = null;
pictureDisp = ImageConverter.Convert(Resources.Azzan);
mCbBtnNormalView.Picture = pictureDisp;
mCbBtnHtmlView.Picture = pictureDisp;
'VB
Dim pictureDisp As stdole.IPictureDisp = Nothing
pictureDisp = ImageConvert.Convert(My.Resources.Azzan)
mCbBtnNormalView.Picture = pictureDisp
mCbBtnHtmlView.Picture = pictureDisp
Dopo avere creato un istanza della form "FrmConversion" dichiarata a livello globale della classe, assegnamo ad entrambi gli eventi "click" dei bottoni il metodo "cbBtn_Click":
//C#
private FrmConversion mFrmConversion;
...
mCbBtnNormalView.Click += new _CommandBarButtonEvents_ClickEventHandler(cbBtn_Click);
mCbBtnHtmlView.Click += new _CommandBarButtonEvents_ClickEventHandler(cbBtn_Click);
mFrmConversion = new FrmConversion();
'VB
Private mFrmConversion As FrmConversion
...
AddHandler mCbBtnNormalView.Click, AddressOf cbBtn_Click
AddHandler mCbBtnHtmlView.Click, AddressOf cbBtn_Click
mFrmConversion = New FrmConversion()
Una volta che l'addin verrà disconnesso, dobbiamo fare le pulizie, in modo da liberare la memoria e riportare Expression Web nello stato precedente al caricamento del nostro addin. Per fare ciò implementiamo il metodo "OnDisconnection" e rimuoviamo i due bottoni inseriti nel metodo "OnConnection":
//C#
void Extensibility.IDTExtensibility2.OnDisconnection
(Extensibility.ext_DisconnectMode RemoveMode, ref Array custom)
{
if (mCbBtnNormalView != null) mCbBtnNormalView.Delete(true);
if (mCbBtnHtmlView != null) mCbBtnHtmlView.Delete(true);
}
'VB
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, _
ByRef custom As System.Array) _
Implements Extensibility.IDTExtensibility2.OnDisconnection
If (Not mCbBtnNormalView Is Nothing) Then
mCbBtnNormalView.Delete(True)
End If
If (Not mCbBtnHtmlView Is Nothing) Then
mCbBtnHtmlView.Delete(True)
End If
End Sub
Implementiamo il metodo "cbBtn_Click":
Siamo pronti per interagire con l'IDE di Expression Web. Come prima cosa svuotiamo la textbox della form conversion e poi la lanciamo in modalità dialog. Digitiamo o incolliamo il codice da trasformare in HTML nella apposita textbox e premiamo OK.

Prendiamo la stringa di codice tradotta dalla proprietà "ConvertedText" dell'istanza della form e la assegnamo ad una nuova istanza di una stringa che chiamiamo "newContent". Verifichiamo se l'oggetto Application ("ewApp") ci ritorna il documento attivo, se l'oggetto esiste, possiamo interagire col text editor di Expression Web.
Creiamo un oggetto "IHTMLTxtRange" che rappresenta il testo correntemente selezionato sul documento attivo e tramite il metodo "pasteHTML" gli passiamo la stringa "newContent". Il testo selezionato verrà sostituito dalla nostra nuova stringa.
//C#
mFrmConversion.txtCode.Text = String.Empty;
DialogResult result = mFrmConversion.ShowDialog();
if (result == DialogResult.OK)
{
string newContent = mFrmConversion.ConvertedText;
if (ewApp.ActiveDocument != null)
{
IHTMLTxtRange objSelection;
objSelection = (IHTMLTxtRange)ewApp.ActiveDocument.selection.createRange();
objSelection.pasteHTML(newContent);
}
}
'VB
mFrmConversion.txtCode.Text = String.Empty
Dim result As DialogResult = mFrmConversion.ShowDialog()
If result = DialogResult.OK Then
Dim newContent As String = mFrmConversion.ConvertedText
If ewApp.ActiveDocument IsNot Nothing Then
Dim objSelection As IHTMLTxtRange
objSelection = DirectCast(ewApp.ActiveDocument.selection.createRange(), IHTMLTxtRange)
objSelection.pasteHTML(newContent)
End If
End If
Debug dell'addin
Per eseguire il debug dell'addin, dobbiamo configurare Visual Studio in modo che lanci Expression Web. Per fare ciò andiamo neel proprietà del progetto sulla tab "Debug". Nella sezione "Start action" selezioniamo l'opzione "Start external program" ed inseriamo il percorso in cui abbiamo installato il file exe di Expression Web, ad esempio: "C:\Program Files\Microsoft Expression\Web 2\WebDesigner\EXPRWD.EXE". A questo punto possiamo lanciare il debug e provare la classe che abbiamo generato.
Possibili problemi con Windows Vista
Se per caso ci capita di ottenere il seguente errore in fase di compilazione:
Error 1 Cannot register assembly "C:\Progetti\47_DotNetwork\Articoli\ProgrammerPExpWeb\Sorgente\V2\ProgrammerPasterExpWebCSharp\ProgrammerPasterExpWebCSharp\bin\Release\ProgrammerPasterExpWebCSharp.dll" - access denied. Please make sure you're running the application as administrator. Access to the registry key 'HKEY_CLASSES_ROOT\ProgrammerPasterExpWebCSharp.Connect' is denied. C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets 3019 9 ProgrammerPasterExpWebCSharp
Avviamo Visual Studio come Administrator, riapriamo il progetto e riproviamo a compilare.

Conclusioni
Creare un addin per Expression Web non è poi così difficile. L'oggetto Application("ewApp") espone inoltre molti eventi tramite i quali possiamo interagire con l'IDE, e tramite il modello office VBA possiamo creare ed eseguire macro, insomma abbiamo tutto quello che ci serve per estendere Expression Web come vogliamo.
|