Generación dinámica de layouts complejos en WPF mediante Grid y código subyacente

En el desarrollo con WPF, aunque XAML es una herramienta potente para definir interfaces de forma declarativa, presenta limitaciones cuando necesitamos estructuras altamente dinámicas que dependan de lógica de negocio compleja o datos variables en tiempo de ejecución. A diferencia de frameworks web como Vue o React, XAML no posee directivas de bucle nativas para la generación de contenedores de diseño como el Grid.

Para resolver escenarios donde el número de columnas, filas y sus proporciones no se conocen hasta la ejecución, la solución más eficiente es delegar la construcción del layout al código subyacente (C#), manteniendo la flexibilidad de MVVM para el contenido de las celdas.

1. Definición del componente de visualización

Primero, crearemos un UserControl que servirá como plantilla para cada celda del Grid. Este componente se encargará de mostrar la información específica de cada segmento.

XAML del componente (CeldaInfo.xaml):

<UserControl x:Class="WpfDynamicLayout.CeldaInfo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <Border Background="#F0F8FF" BorderBrush="#CCC" BorderThickness="1" Margin="2">
            <TextBlock Text="{Binding TextoCabecera}" 
                       HorizontalAlignment="Center" 
                       VerticalAlignment="Center" 
                       FontSize="18" 
                       FontWeight="Bold"/>
        </Border>
    </Grid>
</UserControl>

ViewModel del componente:

Utilizamos el patrón MVVM básico para gestionar los datos de la celda.

using CommunityToolkit.Mvvm.ComponentModel;

namespace WpfDynamicLayout.ViewModels
{
    public class CeldaViewModel : ObservableObject
    {
        private string _textoCabecera;
        public string TextoCabecera
        {
            get => _textoCabecera;
            set => SetProperty(ref _textoCabecera, value);
        }
    }
}

Código subyacente del componente (CeldaInfo.xaml.cs):

using System.Windows.Controls;
using WpfDynamicLayout.ViewModels;

namespace WpfDynamicLayout
{
    public partial class CeldaInfo : UserControl
    {
        public CeldaInfo()
        {
            InitializeComponent();
        }

        public CeldaInfo(string identificador) : this()
        {
            var vm = new CeldaViewModel { TextoCabecera = identificador };
            this.DataContext = vm;
        }
    }
}

2. Implementación de la lógica de distribución dinámica

En la ventana principal, definimos un Grid contenedor vacío en el XAML y procedemos a inyectar la configuración mediante código.

XAML Principal:

<Window x:Class="WpfDynamicLayout.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Layout Dinámico" Height="600" Width="1000">
    <Grid x:Name="ContenedorPrincipal" ShowGridLines="False">
        <!-- El contenido se generará dinámicamente -->
    </Grid>
</Window>

Lógica de generación (MainWindow.xaml.cs):

En este bloque, definimos una estructura de datos que representa las proporciones de las filas para cada columna. Iteramos sobre esta configuración para construir el árbol visual.

using System.Windows;
using System.Windows.Controls;

namespace WpfDynamicLayout
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ConfigurarLayoutDinamico();
        }

        private void ConfigurarLayoutDinamico()
        {
            ContenedorPrincipal.Children.Clear();
            ContenedorPrincipal.ColumnDefinitions.Clear();

            // Definición de datos: cada array representa las proporciones de filas en esa columna
            float[][] configuracionColumnas = new float[][]
            {
                new float[] { 1, 1 },       // Col 0: Dos filas iguales
                new float[] { 1, 2, 1 },    // Col 1: Tres filas con proporción 1:2:1
                new float[] { 3, 7 },       // Col 2: Dos filas con proporción 3:7
                new float[] { 1 }           // Col 3: Una sola fila
            };

            for (int i = 0; i < configuracionColumnas.Length; i++)
            {
                // Crear definición de columna
                ColumnDefinition colDef = new ColumnDefinition
                {
                    Width = new GridLength(1, GridUnitType.Star)
                };
                ContenedorPrincipal.ColumnDefinitions.Add(colDef);

                // Crear un Grid interno para gestionar las filas de esta columna
                Grid gridInterno = GenerarColumnaConFilas(i, configuracionColumnas[i]);
                Grid.SetColumn(gridInterno, i);
                ContenedorPrincipal.Children.Add(gridInterno);
            }
        }

        private Grid GenerarColumnaConFilas(int indiceColumna, float[] proporcionesFilas)
        {
            Grid gridFila = new Grid { ShowGridLines = true };

            // Añadir definiciones de filas basadas en las proporciones
            foreach (float proporcion in proporcionesFilas)
            {
                gridFila.RowDefinitions.Add(new RowDefinition
                {
                    Height = new GridLength(proporcion, GridUnitType.Star)
                });
            }

            // Insertar controles personalizados en cada celda generada
            for (int j = 0; j < proporcionesFilas.Length; j++)
            {
                CeldaInfo control = new CeldaInfo($"Sección {indiceColumna}-{j}");
                Grid.SetRow(control, j);
                gridFila.Children.Add(control);
            }

            return gridFila;
        }
    }
}

Ventajas de este enfoque

  • Adaptabilidad: Permite modificar la interfaz basándose en respuestas de una API o configuraciones de usuario sin tocar el XAML.
  • Escalabilidad: La lógica de creación de filas y columnas es genérica, lo que permite manejar desde layouts simples hasta matrices complejas.
  • Mantenimiento: Al separar la visualización (UserControl) de la estructura (Lógica de Grid), se facilita la actualización de estilos sin afectar la distribución.

Etiquetas: WPF XAML C# MVVM Dynamic Layout

Publicado el 10-31 15:34