Lanciare un editor fileless

Normalmente, gli editor in Visual Studio e nelle applicazioni su di esso basate sono lanciati eseguendo doppio click del mouse sul nodo relativo al file che si vuole modificare, all’interno dell’albero del progetto corrente.

A volte però, può essere necessario aggiungere all’applicazione basata su VS Shell 2008 Isolated (o a un generico progetto di estensibilità di Visual Studio 2008) che si sta sviluppando, la possibilità di lanciare un editor “fileless”, ovvero che non è associato ad alcun file nella gerarchia di progetto. Un tale editor potrebbe infatti attingere le informazioni presentate da database, da un insieme di file non appartenenti al progetto, dalla rete, etc…

Questo articolo descrive un possibile metodo per realizzare un editor fileless; questo è l’approccio che ho “trovato” io e non è assolutamente detto che sia il metodo corretto!

In questo esempio è stato usato il wizard di Visual Studio per creare un nuovo package, contenente un editor e un comando di menù, che verrà modificato in modo da lanciare l’editor senza clickare su alcun file. Inoltre, questo package è stato creato e aggiunto a un progetto Visual Studio Shell, ma può anche essere utilizzato al di fuori di Visual Studio Shell, come package per Visual Studio.

Creazione e configurazione del Package

Innanzitutto si crea da Visual Studio un nuovo progetto di tipo “Visual Studio Integration Package” (in questo caso viene aggiunto alla Solution esistente, contenente i progetti della Shell); dopodichè si seguono i vari passi di configurazione che il wizard ci propone:

  1. Selezione del linguaggio di programmazione: in questo caso C#, con l’opzione di generazione di una nuova chiave per firmare l’assembly;
  2. Informazioni di base sul package: nel mio caso ho chiamato il package “YAFilelessEditorPackage” e ho impostato il company name a “ACompany”;
  3. Opzioni del package: si selezionano “Menu Command” (servirà per lanciare l’editor) e “Custom Editor” (l’editor stesso), in questo esempio non ho incluso la “Tool Window”;
  4. Opzioni del comando: si specifica il testo del comando (in questo caso “Launch Fileless Editor”) e una stringa che costituirà l’identificatore del comando (“cmdidLaunchFilelessEditor”);
  5. Questo passo, non avendo incluso la Tool Window, viene saltato dal wizard;
  6. Opzioni dell’editor: si specifica il nome dell’editor (“Another Fileless Editor”) e il nome di default e l’estensione dei file da utilizzare; dato che stiamo realizzando un editor fileless, l’estensione sarà un’estensione inesistente (“nullext”);
  7. Progetti di test: non ho aggiunto alcun progetto di test, per cui ho lasciato le checkbox deselezionate.

Infine, per aggiungere l’editor alla shell, occorre aggiungere la dipendenza della shell dal nuovo package, clickando col tasto destro sul progetto della shell (“YAShell”), selezionando dal menu contestuale “Shell Dependencies” e selezionando il package appena realizzato nella dialog che viene presentata.

Modifiche al codice del Package

Ora che il package è stato creato dal wizard, è necessario modificare alcune classi e i relativi file.

YAFilelessEditorPackagePackage (YAFilelessEditorPackagePackage.cs)

(Ehm, lo so, da qualche parte ho messo un “package” di troppo…)

Comunque, in questa classe c’è la necessità di memorizzare un riferimento all’istanza della classe EditorFactory (servirà per poter lanciare l’editor alla selezione del comando di menù) costruita nel metodo Initialize(), pertanto ho aggiunto un campo privato:

private EditorFactory editorFactory = null;

Quindi, per memorizzare tale riferimento, nel metodo Initialize() ho sostituito la riga:

base.RegisterEditorFactory(new EditorFactory(this));

con le righe:

this.editorFactory = new EditorFactory(this);
base.RegisterEditorFactory(this.editorFactory);

Inoltre, per poter lanciare l’editor fileless, serve il riferimento a un oggetto che implementi l’interfaccia IVsUIHierarchy.

Ho quindi sostituito:

public sealed class YAFilelessEditorPackagePackage : Package

con:

public sealed class YAFilelessEditorPackagePackage : Package, IVsUIHierarchy

facendo generare a Visual Studio l’implementazione di tutti i metodi delle interfacce IVsUIHierarchy e IVsHierarchy.
Non so quanto sia corretto fare in modo che il package implementi tale interfaccia, ma così facendo sembra funzionare.

Infine, è necessario modificare il metodo di callback (MenuItemCallback()) utilizzato dal package per eseguire il comando, in modo che lanci l’editor, anzichè presentare una semplice message box, come avviene nell’implementazione di default:

/// <summary>
/// This function is the callback used to execute a 
/// command when the menu item is clicked.
/// See the Initialize method to see how the menu item 
/// is associated to this function using the
/// OleMenuCommandService service and the MenuCommand class.
/// </summary>
private void MenuItemCallback(object sender, EventArgs e)
{
    // Ottiene il riferimento alla shell.
    IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));

    // Un puntatore all'interfaccia IVsHierarchy è necessario affinchè
    // la chiamata ai metodi CreateEditorInstance() e CreateDocumentWindow() 
    // abbia successo.
    // Per questo motivo, la classe del package è stata modificata in modo da
    // implementare tale interfaccia.
    IVsUIHierarchy hierarchy = this;

    // Parametri di output per il metodo CreateEditorInstance().
    string caption;
    IntPtr ppunkDocView;
    IntPtr ppunkDocData;
    Guid guid;
    int pgrfCDW;

    // Crea l'istanza dell'editor.
    int createEditorResult = this.editorFactory.CreateEditorInstance(
        (uint)__VSCREATEEDITORFLAGS.CEF_OPENFILE,
        "",
        "",
        hierarchy,
        0,
        IntPtr.Zero,
        out ppunkDocView,
        out ppunkDocData,
        out caption,
        out guid,
        out pgrfCDW);

    // Il GUID dell'editor factory (Guids.cs).
    Guid guidEditor = GuidList.guidYAFilelessEditorPackageEditorFactory;

    // Dopo l'esecuzione di CreateDocumentWindow(), l'editor frame
    // conterrà il nostro editor.
    IVsWindowFrame editorFrame = null;

    // Crea la finestra dell'editor.
    int createDocumentViewResult = uiShell.CreateDocumentWindow(
        (uint)__VSCREATEDOCWIN.CDW_fCreateNewWindow,
        "Fileless!",
        hierarchy,
        0,
        ppunkDocView,
        ppunkDocData,
        ref guidEditor,
        "pszPhysicalView",
        ref guid,
        null,
        "Yet Another Fileless Editor",
        caption,
        null,
        out editorFrame);

    // Mostra la finestra dell'editor.
    if (editorFrame != null)
    {
        editorFrame.Show();
    }
}

EditorPane (EditorPane.cs) e MyEditor (MyEditor.cs)

Queste classi costituiscono l’editor vero e proprio e offrono un punto di partenza per capire come sviluppare il proprio editor e come questo interagisca con la shell. L’implementazione generata dal wizard è tutt’altro che un editor fileless, ma è un editor di testo rich text che si appoggia su file, pertanto bisogna prestare particolare attenzione a tutti i metodi (in particolare della classe EditorPane) che interagiscono col presunto file associato all’istanza corrente dell’editor.

Giusto per rendere un minimo interattivo l’editor, ho cambiato l’implementazione del metodo CanEditFile() della classe EditorPane nel seguente modo:

private bool CanEditFile()
{
    return true;
}

A questo punto, se tutto è andato per il verso giusto, sarà possibile lanciare l’editor dalla shell in esecuzione, mediante il comando “Launch Fileless Editor” che abbiamo inserito nel package:

L'editor fileless, lanciato dall'applicazione basata su VS Shell
Fig. 11: L’editor fileless, lanciato dall’applicazione basata su VS Shell

Come al solito, se qualche gentile lettore fosse a conoscenza di un metodo migliore per lanciare un editor fileless, è pregato di scriverlo in un commento! 😉

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.