Compartir a través de


Introducción a WPF

En este tutorial paso a paso se muestra cómo enlazar tipos POCO a controles WPF en un formulario "main-detail". La aplicación usa las API de Entity Framework para rellenar objetos con datos de la base de datos, realizar un seguimiento de los cambios y conservar los datos en la base de datos.

El modelo define dos tipos que participan en una relación de uno a varios: Categoría (principal\main) y Producto (dependiente\detalle). El marco de enlace de datos de WPF permite la navegación entre objetos relacionados: la selección de filas en la vista maestra hace que la vista de detalles se actualice con los datos secundarios correspondientes.

Las capturas de pantalla y las descripciones de código de este tutorial se toman de Visual Studio 2019 16.6.5.

Sugerencia

Puede ver el ejemplo de de este artículo en GitHub.

Requisitos previos

Debe tener instalado Visual Studio 2019 16.3 o posterior con la carga de trabajo de escritorio de .NET seleccionada para completar este recorrido. Para obtener más información sobre cómo instalar la versión más reciente de Visual Studio, consulte Instalación de Visual Studio.

Creación de la aplicación

  1. Apertura de Visual Studio
  2. En la ventana de inicio, elija Crear nuevo proyecto.
  3. Busque "WPF", elija Aplicación WPF (.NET Core) y, a continuación, elija Siguiente.
  4. En la siguiente pantalla, asigne un nombre al proyecto, por ejemplo , GetStartedWPF y elija Crear.

Instalación de los paquetes NuGet de Entity Framework

  1. Haga clic con el botón derecho en la solución y elija Administrar paquetes NuGet para la solución...

    Administración de paquetes NuGet

  2. Escriba entityframeworkcore.sqlite en el cuadro de búsqueda.

  3. Seleccione el paquete Microsoft.EntityFrameworkCore.Sqlite .

  4. Compruebe el proyecto en el panel derecho y haga clic en Instalar.

    Paquete sqlite

  5. Repita los pasos para buscar entityframeworkcore.proxies e instalar Microsoft.EntityFrameworkCore.Proxies.

Nota:

Cuando instalaste el paquete Sqlite, automáticamente descargó el paquete base relacionado de Microsoft.EntityFrameworkCore. El paquete Microsoft.EntityFrameworkCore.Proxies proporciona compatibilidad con datos de "carga diferida". Esto significa que cuando tienes entidades con subentidades, solo se obtienen las entidades principales en la carga inicial. Los servidores proxy detectan cuándo se realiza un intento de acceder a las entidades secundarias y los carga automáticamente a petición.

Definir un modelo

En este tutorial implementará un modelo mediante "code first". Esto significa que EF Core creará las tablas de base de datos y el esquema en función de las clases de C# que defina.

Agregue una nueva clase. Asígnele el nombre: Product.cs y rellene de esta manera:

Product.cs

namespace GetStartedWPF
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

A continuación, agregue una clase denominada Category.cs y lléntela con el código siguiente:

Category.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace GetStartedWPF
{
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
}

La propiedad Products de la clase Category y la propiedad Category de la clase Product son propiedades de navegación. En Entity Framework, las propiedades de navegación proporcionan una manera de navegar por una relación entre dos tipos de entidad.

Además de definir entidades, debe definir una clase que derive de DbContext y exponga las propiedades TEntity< de DbSet>. Las propiedades de DbSet<TEntity> permiten al contexto saber qué tipos desea incluir en el modelo.

Una instancia del tipo derivado DbContext administra los objetos de entidad durante el tiempo de ejecución, lo que incluye rellenar objetos con datos de una base de datos, seguimiento de cambios y conservación de datos en la base de datos.

Agregue una nueva ProductContext.cs clase al proyecto con la siguiente definición:

ProductContext.cs

using Microsoft.EntityFrameworkCore;

namespace GetStartedWPF
{
    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }

        protected override void OnConfiguring(
            DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(
                "Data Source=products.db");
            optionsBuilder.UseLazyLoadingProxies();
        }
    }
}
  • DbSet informa a EF Core de qué entidades de C# se deben asignar a la base de datos.
  • Hay varias maneras de configurar EF Core DbContext. Puede leer sobre ellos en: Configuración de dbContext.
  • En este ejemplo se usa la OnConfiguring anulación para especificar un archivo de datos Sqlite.
  • La UseLazyLoadingProxies llamada indica a EF Core que implemente la carga diferida, de modo que las entidades secundarias se carguen automáticamente al ser accedidas desde la entidad principal.

Presione CTRL+MAYÚS+B o diríjase a Generar > solución para compilar el proyecto.

Sugerencia

Descubre las diferentes formas de mantener sincronizados su base de datos y los modelos de EF Core: Administración de esquemas de base de datos.

Carga diferida

La propiedad Products de la clase Category y la propiedad Category de la clase Product son propiedades de navegación. En Entity Framework Core, las propiedades de navegación proporcionan una manera de navegar por una relación entre dos tipos de entidad.

EF Core permite cargar automáticamente entidades relacionadas desde la base de datos la primera vez que se accede a la propiedad de navegación. Con este tipo de carga (denominada carga diferida), tenga en cuenta que, si el contenido aún no está en el contexto, la primera vez que acceda a cada propiedad de navegación, se ejecutará una consulta independiente en la base de datos.

Cuando se utilizan tipos de entidad "Objeto C# antiguo sin formato" (POCO), EF Core logra la carga diferida mediante la creación de instancias de tipos de proxy derivados en tiempo de ejecución y luego sobrescribe las propiedades virtuales en sus clases para añadir el enlace de carga. Para obtener la carga diferida de objetos relacionados, debe declarar los captadores de propiedades de navegación como públicos y virtuales (reemplazables en Visual Basic) y la clase no debe estar sellada (NotOverridable en Visual Basic). Al usar Database First, las propiedades de navegación se convierten automáticamente en virtuales para habilitar la carga diferida.

Enlazar objeto a controles

Agregue las clases definidas en el modelo como orígenes de datos para esta aplicación WPF.

  1. Haga doble clic en MainWindow.xaml en el Explorador de soluciones para abrir el formulario principal.

  2. Elija la pestaña XAML para editar el XAML.

  3. Inmediatamente después de la etiqueta de apertura Window, agregue las siguientes fuentes para conectarse a las entidades de EF Core.

    <Window x:Class="GetStartedWPF.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:GetStartedWPF"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="categoryViewSource"/>
            <CollectionViewSource x:Key="categoryProductsViewSource" 
                                  Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
        </Window.Resources>
    
  4. Esto configura el origen de las categorías "primarias" y la segunda fuente para los productos de "detalle".

  5. A continuación, agregue el siguiente marcado a la XAML después de la etiqueta de apertura Grid.

    <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" 
              EnableRowVirtualization="True" 
              ItemsSource="{Binding Source={StaticResource categoryViewSource}}" 
              Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding CategoryId}"
                                Header="Category Id" Width="SizeToHeader"
                                IsReadOnly="True"/>
            <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                                Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
    
  6. Tenga en cuenta que CategoryId se establece en ReadOnly porque la base de datos lo asigna y no se puede cambiar.

Agregar una cuadrícula de detalles

Ahora que la cuadrícula existe para mostrar categorías, la cuadrícula de detalles se puede agregar para mostrar productos. Agregue esto dentro del Grid elemento , después del elemento categories DataGrid .

MainWindow.xaml

<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" 
          EnableRowVirtualization="True" 
          ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}" 
          Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected" 
          RenderTransformOrigin="0.488,0.251">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

Por último, agregue un Save botón y asocie el evento de clic a Button_Click.

<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" 
        Click="Button_Click" Height="20" Width="123"/>

La vista de diseño debe tener este aspecto:

Captura de pantalla de WPF Designer

Adición de código que controla la interacción de datos

Es el momento de agregar algunos controladores de eventos a la ventana principal.

  1. En la ventana XAML, haga clic en el <elemento Window> para seleccionar la ventana principal.

  2. En la ventana Propiedades , elija Eventos en la parte superior derecha y, a continuación, haga doble clic en el cuadro de texto situado a la derecha de la etiqueta Cargada .

    Propiedades de la ventana principal

Esto le lleva al código detrás del formulario; ahora editaremos el código para usar ProductContext para realizar el acceso a datos. Actualice el código como se muestra a continuación.

El código declara una instancia de ejecución prolongada de ProductContext. El ProductContext objeto se usa para consultar y guardar datos en la base de datos. El método Dispose() en la instancia ProductContext se llama desde el método sobrescrito OnClosing. Los comentarios de código explican lo que hace cada paso.

MainWindow.xaml.cs

using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GetStartedWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ProductContext _context =
            new ProductContext();

        private CollectionViewSource categoryViewSource;

        public MainWindow()
        {
            InitializeComponent();
            categoryViewSource =
                (CollectionViewSource)FindResource(nameof(categoryViewSource));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // this is for demo purposes only, to make it easier
            // to get up and running
            _context.Database.EnsureCreated();

            // load the entities into EF Core
            _context.Categories.Load();

            // bind to the source
            categoryViewSource.Source =
                _context.Categories.Local.ToObservableCollection();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // all changes are automatically tracked, including
            // deletes!
            _context.SaveChanges();

            // this forces the grid to refresh to latest values
            categoryDataGrid.Items.Refresh();
            productsDataGrid.Items.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            // clean up database connections
            _context.Dispose();
            base.OnClosing(e);
        }
    }
}

Nota:

El código usa una llamada para EnsureCreated() compilar la base de datos en la primera ejecución. Esto es aceptable para demostraciones, pero en las aplicaciones de producción debería considerar las migraciones para administrar el esquema. El código también se ejecuta sincrónicamente porque usa una base de datos DE SQLite local. En escenarios de producción que normalmente implican un servidor remoto, considere la posibilidad de usar las versiones asincrónicas de los Load métodos y SaveChanges .

Prueba de la aplicación WPF

Compile y ejecute la aplicación presionando F5 o seleccionando Depurar Iniciar depuración>. La base de datos debe crearse automáticamente con un archivo denominado products.db. Escriba un nombre de categoría y presione Entrar y agregue productos a la cuadrícula inferior. Haga clic en Guardar y observe la actualización de cuadrícula con los identificadores proporcionados por la base de datos. Resalte una fila y presione Eliminar para quitar la fila. La entidad se eliminará al hacer clic en Guardar.

Ejecución de la aplicación

Notificación de cambio de propiedad

Este ejemplo se basa en cuatro pasos para sincronizar las entidades con la interfaz de usuario.

  1. La llamada _context.Categories.Load() inicial carga los datos de categorías.
  2. Los proxies de carga perezosa cargan los datos de los productos dependientes.
  3. El seguimiento de cambios integrado de EF Core realiza las modificaciones necesarias en las entidades, incluidas las inserciones y eliminaciones, cuando _context.SaveChanges() se llama a .
  4. Las llamadas a DataGridView.Items.Refresh() fuerzan una recarga con los identificadores recién generados.

Esto funciona para nuestro ejemplo de introducción, pero puede requerir código adicional para otros escenarios. Los controles WPF representan la interfaz de usuario leyendo los campos y las propiedades de las entidades. Al editar un valor en la interfaz de usuario (UI), ese valor se pasa a la entidad. Al cambiar el valor de una propiedad directamente en la entidad, como cargarla desde la base de datos, WPF no reflejará inmediatamente los cambios en la interfaz de usuario. El motor de representación debe recibir una notificación de los cambios. El proyecto lo hizo llamando manualmente a Refresh(). Una manera fácil de automatizar esta notificación es mediante la implementación de la interfaz INotifyPropertyChanged . Los componentes de WPF detectarán automáticamente la interfaz y se registrarán para eventos de cambio. La entidad es responsable de generar estos eventos.

Sugerencia

Para obtener más información sobre cómo controlar los cambios, lea: Cómo implementar la notificación de cambio de propiedad.

Pasos siguientes

Obtenga más información sobre la configuración de dbContext.