Interfaces “Dockables” con AvalonDock

 ·  ☕ 5 min  ·  ✍️ eiximenis

    Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

    Hace algún tiempo escribí como integrar AvalonDock con PRISM. En el post daba por asumidos algunos conceptos de AvalonDock, pero algunos comentarios recibidos me han pedido si puedo profundizar un poco, así que voy a ello. Vamos a ver como crear paso a paso una aplicación AvalonDock y luego, en otro post ya veremos como podemos PRISMearla… 🙂

    AvalonDock es una librería para la creación de interfaces con ventanas flotantes (al estilo del propio Visual Studio). Según su creador soporta también winforms, aunque yo siempre la he usado con WPF, así que nada puedo deciros de su integración con winforms.

    Hay un tutorial de AvalonDock en el blog de su creador (http://www.youdev.net/post/2008/09/25/AvalonDock-Tutorial.aspx) que aunque muy básico explica los conceptos clave… echadle una ojeada si os apetece 🙂

    Supongo que teneis instalada AvalonDock… si usais el instalador, os creará una toolbar en Visual Studio con los controles de AvalonDock, no és imprescindible pero ayuda 🙂

    El primer paso es crear una aplicación WPF, añadir una referencia a AvalonDock.dll y en la ventana principal, debemos añadir el componente padre de AvalonDock, el DockingManager:

    «/span>Window x:Class=”DockReader.Window1″ xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” Title=”Window1″ Height=”300″ Width=”300″ xmlns:ad=”clr-namespace:AvalonDock;assembly=AvalonDock”> «/span>Grid> «/span>ad:DockingManager Name=”dockManager”/> </Grid> </Window>

    En este caso DockManager ocupa todo el espacio disponible en la Grid del control. Si nos interesa tener algún control en la ventana principal que no participe del sistema de ventanas flotantes (p.ej. una statusbar o una ribbon siempre visibles y fijas), podemos colocarla en alguna otra fila de la grid:

    «/span>Window x:Class=”DockReader.Window1″ xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” Title=”Window1″ Height=”300″ Width=”300″ xmlns:ad=”clr-namespace:AvalonDock;assembly=AvalonDock”> «/span>Grid> «/span>Grid.RowDefinitions> «/span>RowDefinition Height=”50″/> «/span>RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> «/span>ToolBar Grid.Row=”0″> «/span>Button> «/span>Image Source=”/DockReader;component/open.png” Height=”32″ Width=”32″></Image> </Button> </ToolBar> <!– Docking Manager –> «/span>ad:DockingManager Grid.Row=”1″ Name=”dockManager”/> </Grid> </Window>

    El DockingManager por sí solo no hace nada… tenemos que rellenarlo y para ello podemos usar los dos contenidos que tiene AvalonDock:

    • DockableContent: Un DockableContent se puede “dockar” en cualquier parte del DockingManager y también puede aparecer en forma de ventana flotante.
    • DocumentContent: Los DocumentContent aparecen “fijos” en una zona, y pueden “dockarse” en cualquier parte de esta zona (y generalmente no aparecen en forma de ventanas flotantes).

    P.ej. en Visual Studio las distintas ventanas con documentos serian DocumentContents, y el restro de ventanas flotantes (p.ej. la toolbox) serian DockableContents.

    Para que AvalonDock funcione correctamente debemos incrustar los DockableContent y los DocumentContent en sus propios paneles que son DocumentPane (para contener DocumentContents) y DockablePane para contener DockableContents.

    P.ej. si colocamos un DocumentPane con dos DocumentContent dentro del DockingManager, obtenemos la siguiente interfaz:

    <td width="200" valign="top">
      <a href="/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/etomas/image_5F00_24382832.png"><img border="0" width="244" src="/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/etomas/image_5F00_thumb_5F00_6AB7A8EB.png" alt="image" height="211" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" title="image" /></a>
    </td>
    
    image

    Como podeis observar sólo con un DocumentPane ya tenemos una interfaz totalmente dockable.

    Si queremos combinar varios DocumentPane (cada uno con sus ContentPane) y/o varios DockablePane (cada uno con sus DockableContents) debemos usar un panel dentro del DockingManager. P.ej, la siguiente interfaz és el mismo ContentPane de antes y un DockableContent, todo ello dentro de un StackPanel:

    image

    El código XAML sería tal y como sigue:

    <!– Docking Manager –> «/span>ad:DockingManager Grid.Row=”1″ Name=”dockManager”> «/span>StackPanel Orientation=”Horizontal”> «/span>ad:DocumentPane Width=”200″> «/span>ad:DocumentContent> «/span>Label>Documento 1</Label> </ad:DocumentContent> «/span>ad:DockableContent> «/span>Label>Documento 2</Label> </ad:DockableContent> </ad:DocumentPane> «/span>ad:DockablePane Width=”80″> «/span>ad:DockableContent> «/span>Button>Botón Dockable</Button> </ad:DockableContent> </ad:DockablePane> </StackPanel> </ad:DockingManager>

     

     

    Esta ventana tiene el problema de que el tamaño del DocumentPane y del DockablePane está fijo… AvalonDock nos ofrece un panel (ResizingPanel) que integra un splitter… simplemente cambiando el StackPanel por un ResizingPanel nuestra interfaz ya es totalmente funcional!

    Vamos a hacer una aplicación completa: un lector de ficheros XPS. En la parte izquierda tendremos un DocumentPane con los distintos ficheros abiertos, y en la parte derecha una lista con los nombres de los ficheros abiertos.

    El XAML de la ventana principal quedaria:

    «/span>Window x:Class=”DockReader.Window1″ xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” Title=”Window1″ Height=”300″ Width=”300″ xmlns:ad=”clr-namespace:AvalonDock;assembly=AvalonDock”> «/span>Grid> «/span>Grid.RowDefinitions> «/span>RowDefinition Height=”50″/> «/span>RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> «/span>ToolBar Grid.Row=”0″> «/span>Button x:Name=”cmdAbrir” Click=”cmdAbrir_Click”> «/span>Image Source=”/DockReader;component/open.png” Height=”32″ Width=”32″></Image> </Button> </ToolBar> <!– Docking Manager –> «/span>ad:DockingManager Grid.Row=”1″ Name=”dockManager”> «/span>ad:ResizingPanel Orientation=”Horizontal”> «/span>ad:DocumentPane x:Name=”docsPane”> </ad:DocumentPane> «/span>ad:DockablePane> «/span>ad:DockableContent> «/span>DockPanel> «/span>Label DockPanel.Dock=”Top”>
    Ficheros Abiertos:</Label> «/span>ListBox x:Name=”openFiles”/> </DockPanel> </ad:DockableContent> </ad:DockablePane> </ad:ResizingPanel> </ad:DockingManager> </Grid> </Window>

    Vemos la toolbar fija (fuera del DockingManager). Luego tenemos un DocumentPane vacío (inicialmente no tenemos cargado ningún documento) y también vemos un DockablePane que tiene un DockableContent con una label y la listbox…

    En la función gestora del evento Click del botón “cmdAbrir”, mostramos un OpenFileDialog para seleccionar un fichero xps:

    private void cmdAbrir_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.AddExtension = true; ofd.DefaultExt = “.xps”; ofd.Multiselect = false; ofd.CheckFileExists = true; ofd.Filter = “Documentos XPS | *.xps”; if (ofd.ShowDialog() == true) { string s = ofd.FileName; CrearNuevoVisor(s); } }

    Finalmente la función CrearNuevoVisor es la que creará un DocumentContent con el contenido del fichero XPS:

    private void CrearNuevoVisor(string fname) { // Creamos el DocumentContent DocumentContent dc = new DocumentContent(); dc.Closed += new EventHandler(Document_Closed); dc.Title = System.IO.Path.GetFileNameWithoutExtension(fname); this.files.Add(dc.Title); // Carga el XPS y crea un DocumentViewer XpsDocument doc = new XpsDocument(fname, FileAccess.Read, CompressionOption.NotCompressed); DocumentViewer dv = new DocumentViewer(); // Muestra el XPS en el DocumentViewer dv.Document = doc.GetFixedDocumentSequence(); // Incrusta el DocumentViewer en el DocumentContent dc.Content = dv; // Incrusta el DocumentContent en el ContentPane this.docsPane.Items.Add(dc); }

    El código es realmente simple, no? Si os preguntais que es this.files, os diré que es una ObservableCollection que está enlazada a la listbox:

    private ObservableCollection«span style="color: blue">string> files; public Window1() { InitializeComponent(); this.files = new ObservableCollection«span style="color: blue">string>(); this.openFiles.ItemsSource = this.files; }

    Finalmente, en el método Document_Closed lo único que hacemos es eliminar de this.files la cadena que hemos añadido antes (no pongo el código, ya que es trivial).

    Con esto ya tenemos un lector de XPS completamente funcional y con una interfaz totalmente “dockable”…

    <td width="200" valign="top">
      <a href="/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/etomas/image_5F00_6762E4B8.png"><img border="0" width="244" src="/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/etomas/image_5F00_thumb_5F00_66B1BAD4.png" alt="image" height="185" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" title="image" /></a>
    </td>
    
    image

    En un próximo post veremos como convertir este DockReader en una aplicación compuesta usando PRISM!

    Saludos!

    Si quieres, puedes invitarme a un café xD

    eiximenis
    ESCRITO POR
    eiximenis
    Compulsive Developer