Interfaces “Dockables” con AvalonDock

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:

<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»> <Grid> <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:

<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»> <Grid> <Grid.RowDefinitions> <RowDefinition Height=»50″/> <RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> <ToolBar Grid.Row=»0″> <Button> <Image Source=»/DockReader;component/open.png» Height=»32″ Width=»32″></Image> </Button> </ToolBar> <!– Docking Manager –> <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:

image 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 –> <ad:DockingManager Grid.Row=»1″ Name=»dockManager»> <StackPanel Orientation=»Horizontal»> <ad:DocumentPane Width=»200″> <ad:DocumentContent> <Label>Documento 1</Label> </ad:DocumentContent> <ad:DockableContent> <Label>Documento 2</Label> </ad:DockableContent> </ad:DocumentPane> <ad:DockablePane Width=»80″> <ad:DockableContent> <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:

<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»> <Grid> <Grid.RowDefinitions> <RowDefinition Height=»50″/> <RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> <ToolBar Grid.Row=»0″> <Button x:Name=»cmdAbrir» Click=»cmdAbrir_Click»> <Image Source=»/DockReader;component/open.png» Height=»32″ Width=»32″></Image> </Button> </ToolBar> <!– Docking Manager –> <ad:DockingManager Grid.Row=»1″ Name=»dockManager»> <ad:ResizingPanel Orientation=»Horizontal»> <ad:DocumentPane x:Name=»docsPane»> </ad:DocumentPane> <ad:DockablePane> <ad:DockableContent> <DockPanel> <Label DockPanel.Dock=»Top»>
Ficheros Abiertos:</Label> <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<string> que está enlazada a la listbox:

private ObservableCollection<string> files; public Window1() { InitializeComponent(); this.files = new ObservableCollection<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”…

image image

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

Saludos!

3 comentarios sobre “Interfaces “Dockables” con AvalonDock”

  1. Hola, tengo una inquietud, como puedo incluir en un documentmanager para mostrar un Windows1, Windows2, o como mostrar un Page1, Page2. como gestiono mis Formularios window, o page, que tengo creadas e instanciarlas o utilizarlas con AvalonDock. gracias de antemano a mis dudas.

  2. que tal Eduard,

    He leído tu post y me a despegado muchas dudas pero me hiso formularle algunas preguntas :),

    Estoy creando mis DocuentContent, como objetos para después invocarlos desde mi aplicación pongo el ejemplo




    cuando mando a cargar algún documentContent donde se encuentra mi
    dockingManager no tengo problema y lo cargo de esta manera

    var doc = new DocumentCatalogoSocio(){ Title = «catalog» };
    doc.Show(dockManager);
    doc.Activate();

    el problema lo tengo cuando quiero agregar un nuevo
    documentContent interactuando con mi menú, el menú es un userControl y
    realizo las operaciones del llenado sobre este pero no me muestra nada

    tendrás un tip ya que no he dado con la solución, mi método para llenar el
    docPanel es este

    MDICRM crm = new MDICRM();
    var docUsuario = new DocumentCatalogoUsuario() { Title = «usuarios» };
    docUsuario.Show(crm.dockManager);
    docUsuario.Activate();

    saludos y gracias

    Jorge Coria

  3. que tal Eduard,

    He leído tu post y me a despegado muchas dudas pero me hiso formularle algunas preguntas :),

    Estoy creando mis DocuentContent, como objetos para después invocarlos desde mi aplicación pongo el ejemplo




    cuando mando a cargar algún documentContent donde se encuentra mi
    dockingManager no tengo problema y lo cargo de esta manera

    var doc = new DocumentCatalogoSocio(){ Title = «catalog» };
    doc.Show(dockManager);
    doc.Activate();

    el problema lo tengo cuando quiero agregar un nuevo
    documentContent interactuando con mi menú, el menú es un userControl y
    realizo las operaciones del llenado sobre este pero no me muestra nada

    tendrás un tip ya que no he dado con la solución, mi método para llenar el
    docPanel es este

    MDICRM crm = new MDICRM();
    var docUsuario = new DocumentCatalogoUsuario() { Title = «usuarios» };
    docUsuario.Show(crm.dockManager);
    docUsuario.Activate();

    saludos y gracias

    Jorge Coria

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *