Herencia visual en WPF: Creando una ventana especifica base

viernes, 10 de agosto de 2012

Hola a todos

En el anterior post vimos cómo crear una ventana base para una aplicación y así conseguir que todas las ventanas tuvieran el mismo aspecto visual sin tener que repetir ese código en cada ventana, bien pues en este post vamos a ver cómo darle continuidad a esta idea.
En toda aplicación tenemos un tipo de ventanas que se repite a lo largo de toda la aplicación, y donde hay cierta funcionalidad que se repite. Por ejemplo podemos tener ventanas de tipo lista o informe, ventanas selectores de registros o de búsqueda, ventanas de detalle donde se dan de alta registros o se editan.

En este ejemplo vamos a ver cómo crear una ventana base de tipo lista o informe. En esta ventana vamos a tener un Grid, una caja de texto para filtrar, un menú a través del que se pueda exportar a Excel, Pdf o imprimir por ejemplo.
En el anterior post de esta serie vimos cómo tener una clase base y estilo por defecto personalizado. El estilo definía la propiedad template, creando un ControlTemplate nuevo para la ventana. Para esta nueva clase base vamos a crear un estilo nuevo, pero heredando del que habíamos creado antes para heredar el ControlTemplate y las demás propiedades definidas en él.
Necesitamos añadir controles a nuestro estilo nuevo, pero no a través del  ControlTemplate  porque no se puede extender, es único para cada control o estilo. Lo que sí podemos hacer es utilizar la propiedad content para añadir controles, pero tenemos que dar la posibilidad a las clases derivadas de poder añadir sus propios controles.
Para conseguir ésto, vamos a crear una propiedad de dependencia por cada zona que vamos a crear para añadir controles. Vamos a ver cómo nos queda el estilo:


 

    
    
    
        
        
    
    
        
        
    

    
    

    
        
            
                
                    
                    
                    
                    
                    

                


                
                    
                        
                            
                            
                            
                            
                            
                        


                        
                        

                        
                        
                        
                        
                    

                
                

                
                
                    
                
            
        

    

    
    
    
        
    


Como vemos en el código, hemos definido la propiedad Content en el estilo. Hemos definido una caja de texto para el filtro y un Grid. Fijaros que hemos creado dos ContentControl y su propiedad Content está enlazada a las unas propiedades ButtonRegion y FilterRegion, éstas son propiedades de dependencia creadas en la clase WindowListBase para que sean asignadas desde la clase derivada y que cada ventana ponga ahí lo que crea oportuno. También es importante destacar la propiedad adjunta (Attached Property) del grid,  local:DataGridEx.ColumnsSource y la propiedad de dependencia de la ventana base que le da valor, Columns. Cada ventana podrá definir las columnas en el grid que necesite y al grid se le asigna a través del Attached Property.

Veamos la clase WindowListBase :




    /// 
    /// Ventana base de tipo lista
    /// 
    public class WindowListBase : WindowBase
    {
        /// 
        /// Contructor estático donde se asigna el estilo por defecto
        /// 
        static WindowListBase()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowListBase), new FrameworkPropertyMetadata(typeof(WindowListBase)));
        }

        #region Regiones
            public object FilterRegion
            {
                get { return (object)GetValue(FilterRegionProperty); }
                set { SetValue(FilterRegionProperty, value); }
            }

            public static readonly DependencyProperty FilterRegionProperty =
                DependencyProperty.Register("FilterRegion", typeof(object),
                typeof(WindowListBase), new UIPropertyMetadata());

            public object ButtonRegion
            {
                get { return (object)GetValue(ButtonRegionProperty); }
                set { SetValue(ButtonRegionProperty, value); }
            }

            public static readonly DependencyProperty ButtonRegionProperty =
                DependencyProperty.Register("ButtonRegion", typeof(object),
                typeof(WindowListBase), new UIPropertyMetadata());
        #endregion

        #region Regiones
            public ObservableCollection Columns
            {
                get { return (ObservableCollection)GetValue(ColumnsProperty); }
                set { SetValue(ColumnsProperty, value); }
            }

            public static readonly DependencyProperty ColumnsProperty =
                DependencyProperty.Register("Columns", typeof(ObservableCollection),
                typeof(WindowListBase), new UIPropertyMetadata(new ObservableCollection ()));
        #endregion

    }

Ahora veamos la clase que contiene la Attached Property

 /// 
    /// Clase que contiene la  Attached Property
    /// 
    public class DataGridEx
    {
        public static readonly DependencyProperty ColumnsSourceProperty =
            DependencyProperty.RegisterAttached("ColumnsSource",
            typeof(ObservableCollection), typeof(DataGridEx),
                new FrameworkPropertyMetadata(null,
                    new PropertyChangedCallback(OnColumnsSourceChanged)));

        public static ObservableCollection GetColumnsSource(DependencyObject d)
        {
            return (ObservableCollection)d.GetValue(ColumnsSourceProperty);
        }

        public static void SetColumnsSource(DependencyObject d, ObservableCollection value)
        {
            d.SetValue(ColumnsSourceProperty, value);
        }

        /// 
        /// Metodo que es invocado cuando la propiedad ColumnsSource cambia
        /// 
        /// control donde cambia la propiedad
        /// contiene información del cambio
        private static void OnColumnsSourceChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            DataGrid gridView = d as DataGrid;
            ObservableCollection newColumns = e.NewValue as ObservableCollection;

            gridView.Columns.Clear();
            foreach (var col in newColumns)
            {
                gridView.Columns.Add(col);

            }
        }
    }


Cuando la Attached Property cambia añadimos cada columna de la propiedad al grid.

Ahora creamos una nueva ventana en el proyecto principal y le indicamos que herede de la clase base:

   
    
        
        
        
    


Y en cs:

    public partial class MainWindow : WindowListBase
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }


Ahora mismo simplemente heredando de WindowListBase y añadiendo columnas el aspecto es el siguiente:


Ahora podemos añadir a la ventana sus propios controles en la regiones habilitadas por las propiedades de dependencia:




    
        
            
            
            
        
    
    
    
    
        
        
        
    
    
    
        
    



Y entonces nos quedaría así:


Una vez realizado ésto sólo me gustaría recalcar que, utilizando MVVM, se puede desde el estilo hacer Binding desde los controles al ViewModel. Y por otro lado que, al haber rellenado la propiedad Content en el estilo, sólo se pueden utilizar las regiones habilitadas por propiedades de dependencia para añadir controles desde la ventana. Bueno en realidad como con cualquier estilo, podemos dar valor a la propiedad Content de la ventana, pero perdiendo la del estilo.

Código fuente

Libros Relacionados

WPF 4.5 Unleashed

Pro WPF 4.5 in C#: Windows Presentation Foundation in .NET 4.5 4th Edition (Professional Apress)

1 comentario:

  1. Que tal jorge, muy buen post, me ayudo mucho, solo que tuve problemas a la hora de encontrar como incluir las imagenes en la biblioteca, tuve que cambiarles el tipo, como se incluye el resources a la hora de compilar...
    en el post anterior, no lo especifica, sugiero lo incluyas...

    Muchas Gracias!

    ResponderEliminar