Compartir a través de


Creación de una aplicación de datos básica con WPF y Entity Framework 6

Nota:

Si ejecuta Visual Studio 2022, asegúrese de usar la versión 17.3 o posterior para este tutorial.

En este tutorial se muestra cómo crear formularios básicos sobre la aplicación de datos en Visual Studio. La aplicación usa SQL Server LocalDB, la base de datos Northwind, Entity Framework 6 (no Entity Framework Core) y Windows Presentation Foundation (WPF) para .NET Framework (no .NET Core ni .NET 5 o posterior). Muestra cómo realizar un enlace de datos básico con una vista master-detail e incluye un control BindingNavigator personalizado con botones que hacen lo siguiente: mover a primero, mover anterior, mover a siguiente, pasar a último, eliminar, agregar, establecer nuevo orden, actualizar y cancelar.

Este tutorial se centra en el uso de herramientas de datos en Visual Studio y no intenta explicar las tecnologías subyacentes en profundidad. Se supone que tiene conocimientos básicos del lenguaje de marcado de aplicaciones extensibles (XAML), Entity Framework y SQL. Aunque el código de este tutorial no muestra la arquitectura model-View-ViewModel (MVVM), que es estándar para las aplicaciones WPF, puede copiar el código con algunas modificaciones en su propia aplicación MVVM.

Para ver el código final de este tutorial, consulte Ejemplos de tutoriales de Visual Studio: EF6.

En este tutorial, usted hará lo siguiente:

  • Instalación y conexión a Northwind
  • Configuración del proyecto de aplicación de WPF
  • Creación del modelo de datos de entidad ADO.NET
  • Datos enlazan el modelo a la página XAML
  • Ajustar el diseño de la página y agregar cuadrículas
  • Agregar botones para navegar, agregar, actualizar y eliminar
  • Ejecución de la aplicación WPF

Prerrequisitos

  • Visual Studio con la carga de trabajo Desarrollo de escritorio de .NET instalada y el componente Windows Communication Foundation instalado. Para instalarlo:

    1. Abra la aplicación Instalador de Visual Studio o seleccione Herramientas>Obtener herramientas y características en el menú de Visual Studio.
    2. En el Instalador de Visual Studio, elija Modificar junto a la versión de Visual Studio que desea modificar.
    3. Seleccione la pestaña Componentes individuales y, a continuación, elija Windows Communication Foundation en Actividades de desarrollo.
    4. Seleccione Modificar.
  • SQL Server Express LocalDB. Si no tiene SQL Server Express LocalDB, puede instalarlo desde la página de descarga de SQL Server. O bien, puede instalarlo con la aplicación Instalador de Visual Studio como un componente individual.

  • Explorador de objetos de SQL Server. Para instalarlo, instale la carga de trabajo de Almacenamiento y procesamiento de datos en la aplicación Instalador de Visual Studio.

  • Herramientas de Entity Framework 6. Normalmente, esto se instala al instalar la carga de trabajo Desarrollo de escritorio de .NET.

Instalación y conexión a Northwind

En el ejemplo siguiente se usa SQL Server Express LocalDB y la base de datos de ejemplo Northwind. Si el proveedor de datos de ADO.NET para ese producto admite Entity Framework, también debe funcionar con otros productos de base de datos SQL.

Instale la base de datos de ejemplo Northwind siguiendo estos pasos:

  1. En Visual Studio, abra la ventana Explorador de objetos de SQL Server en el menú Ver . Expanda el nodo SQL Server . Haga clic con el botón derecho en la instancia de LocalDB y seleccione Nueva consulta.

    Se abre una ventana del editor de consultas.

  2. Copie el script Northwind Transact-SQL (T-SQL) en el portapapeles.

  3. Pegue el script T-SQL en el editor de consultas y elija Ejecutar.

    La consulta de script T-SQL crea la base de datos Northwind y la rellena con datos.

  4. Agregue nuevas conexiones para la base de datos Northwind.

Configuración del proyecto de aplicación de WPF

Para configurar el proyecto de aplicación WPF, siga estos pasos:

  1. En Visual Studio, cree un nuevo proyecto de aplicación WPF de C# (.NET Framework).

  2. Agregue el paquete NuGet para Entity Framework 6. En Explorador de soluciones, seleccione el nodo del proyecto. En el menú principal, elija Proyecto>Administrar paquetes NuGet.

  3. En el Administrador de paquetes NuGet, seleccione el vínculo Examinar . Busque y seleccione el paquete EntityFramework . Seleccione Instalar en el panel derecho y siga las indicaciones.

    En la ventana Salida se muestra el progreso y se le notifica cuando se completa la instalación.

    Captura de pantalla que muestra el paquete NuGet de Entity Framework.

Ahora puede usar Visual Studio para crear un modelo basado en la base de datos Northwind.

Creación del modelo de datos de entidad ADO.NET

Para crear el modelo de datos de entidad ADO.NET, siga estos pasos:

  1. Haga clic con el botón derecho en el nodo del proyecto Aplicación WPF en el Explorador de soluciones y elija Agregar>nuevo elemento. En el panel izquierdo, en el nodo C#, elija Data y, en el panel central, elija ADO.NET Entity Data Model.

    Captura de pantalla que muestra la ventana Agregar nuevo elemento con ADO.NET Entity Data Model (Modelo de datos de entidad) seleccionada.

  2. Escriba Northwind_model en Nombre y, a continuación, elija Agregar.

  3. En el Asistente para modelos de datos de entidad, elija EF Designer desde la base de datos y, a continuación, seleccione Siguiente.

    Captura de pantalla que muestra ef Designer de la base de datos seleccionada en el Asistente para modelos de datos de entidad.

  4. En Elegir la conexión de datos, seleccione la conexión LocalDB Northwind (por ejemplo, (localdb)\MSSQLLocalDB) y, a continuación, seleccione Siguiente.

  5. Si no ve una conexión:

    1. Elija Nueva conexión. Si Microsoft SQL Server no está seleccionado como origen de datos en el cuadro de diálogo Propiedades de conexión , seleccione Cambiar. En el cuadro de diálogo Elegir origen de datos , elija Microsoft SQL Server y, a continuación, seleccione Aceptar.

    2. En el cuadro de diálogo Propiedades de conexión , escriba (localdb)\MSSQLLocalDB como nombre del servidor.

    3. En Seleccionar o escribir un nombre de base de datos, seleccione Northwind y, a continuación, seleccione Aceptar.

    4. En Elegir la conexión de datos, seleccione la conexión LocalDB Northwind y seleccione Siguiente.

  6. Si se le solicita, elija la versión de Entity Framework que usa y, a continuación, seleccione Siguiente.

    Captura de pantalla que muestra las opciones de versión de Entity Framework.

  7. En la página siguiente del asistente, elija qué tablas, procedimientos almacenados y otros objetos de base de datos se incluirán en el modelo de Entity Framework. Expande el nodo dbo en el nodo Tablas de la vista de árbol. Seleccione Clientes, Detalles del pedido y Pedidos. Deje activados los valores predeterminados y seleccione Finalizar.

    Captura de pantalla que muestra los objetos de base de datos seleccionados del modelo de datos.

    El asistente genera las clases de C# que representan el modelo de Entity Framework y son los datos de Visual Studio que se enlazan a la interfaz de usuario de WPF. Crea los siguientes archivos en el proyecto:

    • El archivo .edmx describe las relaciones y otros metadatos que asocian las clases con objetos de la base de datos.

    • Los archivos .tt son plantillas T4 que generan el código que opera en el modelo y guardan los cambios en la base de datos.

    Estos archivos están visibles en el Explorador de soluciones en el nodo Northwind_model :

    Captura de pantalla que muestra los archivos del modelo de Entity Framework del Explorador de soluciones.

    El Diseñador de formularios para el .edmx archivo no se usa en este tutorial, pero puede usarlo para modificar determinadas propiedades y relaciones en el modelo.

Los .tt archivos son de uso general y debe editar uno de ellos para trabajar con el enlace de datos de WPF, que requiere ObservableCollection objetos. Siga estos pasos:

  1. En el Explorador de soluciones, expanda el nodo Northwind_model hasta que encuentre Northwind_model.tt. Haga doble clic en este archivo y realice las siguientes modificaciones:

  2. Presione F5 para compilar y ejecutar el proyecto. Cuando se ejecuta la aplicación por primera vez, las clases de modelo son visibles para el Asistente para orígenes de datos.

Ahora estás listo para enlazar este modelo a la página XAML para que puedas ver, navegar y modificar los datos.

Datos enlazan el modelo a la página XAML

Aunque es posible escribir su propio código de enlace de datos, es más fácil dejar que Visual Studio lo haga automáticamente. Para ello, siga estos pasos:

  1. En el menú principal, elija Project> nuevo origen de datos) para mostrar el Asistente para configuración del origen de datos. Dado que está enlazando a las clases de modelo, no a la base de datos, elija Objeto. Seleccione Siguiente.

    Captura de pantalla que muestra el Asistente para configuración del origen de datos con el objeto seleccionado como origen de datos.

  2. Expanda el nodo del proyecto, seleccione el objeto Customer y, a continuación, seleccione Finalizar. Los orígenes de los objetos Order se generan automáticamente a partir de la propiedad de navegación Orders en Customer.

    Captura de pantalla que muestra el objeto Customer seleccionado como origen de datos.

  3. En el Explorador de soluciones, haga doble clic en MainWindow.xaml en el proyecto para editar el CÓDIGO XAML. Cambie el Title de MainWindow a algo más descriptivo y aumente el Height y el Width a 600 y 800 (puede cambiar estos valores más adelante, si es necesario).

  4. Agregue estas tres definiciones de fila a la cuadrícula principal, una fila para los botones de navegación, una para los detalles del cliente y otra para la cuadrícula que muestra sus pedidos:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    

A continuación, se muestra cada propiedad de la Customers clase en su propio cuadro de texto individual. Siga estos pasos:

  1. En el Explorador de soluciones, haga doble clic en MainWindow.xaml para abrirlo en el diseñador.

    La pestaña Orígenes de datos aparece en el panel izquierdo de Visual Studio cerca del Cuadro de herramientas.

  2. Para abrir la ventana Orígenes de datos, seleccione la pestaña Orígenes de datos o elija Ver> otros orígenesde datos de> en el menú.

  3. En Orígenes de datos, seleccione Clientes y, a continuación, seleccione Detalles en la lista desplegable.

  4. Arrastre el nodo a la fila central del área de diseño. Si lo coloca incorrectamente, más adelante podrá especificar la fila manualmente en el XAML seleccionando Grid.Row="1".

    De forma predeterminada, los controles se colocan verticalmente en un elemento de cuadrícula, pero puede organizarlos como quiera en el formulario. Por ejemplo, podría colocar el cuadro de texto Nombre encima de la dirección. La aplicación de ejemplo de este tutorial reordena los campos y los reorganiza en dos columnas.

    Captura de pantalla en la que se muestra el enlace de origen de datos Customers a controles individuales.

    En la vista XAML, ahora puedes ver un nuevo Grid elemento en la fila 1 (la fila central) de la cuadrícula primaria. La cuadrícula primaria tiene un DataContext atributo que hace referencia a un CollectionViewSource que pertenece al Windows.Resources elemento . Dado el contexto de los datos, cuando el primer cuadro de texto se enlaza a Address, ese nombre se asigna a la propiedad Address del objeto actual Customer en CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  5. Arrastre la propiedad del objeto de la clase Order hacia la mitad inferior del formulario, para que el diseñador lo coloque en la fila 2.

    Cuando un cliente está visible en la mitad superior del formulario, quiere ver sus pedidos en la mitad inferior. Los pedidos se muestran en un único control de vista de cuadrícula. Para que el enlace de datos de detalles maestros funcione según lo previsto, es importante que se enlace a la Orders propiedad de la Customers clase , no al nodo independiente Orders .

    Captura de pantalla que muestra las clases Orders arrastradas y soltadas como una cuadrícula.

    Visual Studio ahora genera todo el código de enlace que conecta los controles de interfaz de usuario a eventos del modelo.

  6. Para ver algunos datos, escriba código para rellenar el modelo. Vaya a MainWindow.xaml.cs y agregue un miembro de datos a la MainWindow clase para el contexto de datos.

    Este objeto, que se generó automáticamente, actúa como un control que realiza un seguimiento de los cambios y eventos del modelo.

  7. Agregue CollectionViewSource miembros de datos para clientes y pedidos, y la lógica de inicialización del constructor asociado al constructor MainWindow() existente. La primera parte de la clase debe tener este aspecto:

    public partial class MainWindow : Window
    {
        NorthwindEntities context = new NorthwindEntities();
        CollectionViewSource custViewSource;
        CollectionViewSource ordViewSource;
    
        public MainWindow()
        {
            InitializeComponent();
            custViewSource = ((CollectionViewSource)(FindResource("customerViewSource")));
            ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource")));
            DataContext = this;
        }
    
  8. Si no existe, agregue una using directiva para System.Data.Entity para incorporar el método de extensión Load al ámbito.

    using System.Data.Entity;
    
  9. Desplácese hacia abajo y busque el controlador de Window_Loaded eventos. Observe que Visual Studio agregó un CollectionViewSource objeto . Este objeto representa el NorthwindEntities objeto seleccionado al crear el modelo. Porque ya lo agregaste, no lo necesitas aquí. Reemplace el código de Window_Loaded para que el método tenga este aspecto:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Load is an extension method on IQueryable,    
        // defined in the System.Data.Entity namespace.   
        // This method enumerates the results of the query,    
        // similar to ToList but without creating a list.   
        // When used with Linq to Entities, this method    
        // creates entity objects and adds them to the context.   
        context.Customers.Load();
    
        // After the data is loaded, call the DbSet<T>.Local property    
        // to use the DbSet<T> as a binding source.   
        custViewSource.Source = context.Customers.Local;
    }
    
  10. Presione F5.

    Debería ver los detalles del primer cliente que se recuperó en el CollectionViewSource y sus pedidos en la cuadrícula de datos. Corregirá el formato en la sección siguiente. También puede crear una manera de ver los demás registros y realizar operaciones básicas de creación, lectura, actualización y eliminación (CRUD).

Ajuste el diseño de la página y agregue cuadrículas para nuevos clientes y pedidos

La disposición predeterminada generada por Visual Studio no es ideal para la aplicación, por lo que proporcionamos el CÓDIGO XAML final aquí para copiarlo en el código. También necesita algunas cuadrículas para permitir que el usuario agregue un nuevo cliente o pedido.

Para agregar un nuevo cliente y pedido, cree un conjunto independiente de cuadros de texto que no estén enlazados a datos en el CollectionViewSource. Puede controlar qué cuadrícula ve el usuario en cualquier momento estableciendo la propiedad Visible en los métodos de controlador. Por último, agregue un botón Eliminar a cada fila de la cuadrícula Pedidos para permitir que el usuario elimine un pedido individual.

  1. Abra MainWindow.xaml y agregue los estilos siguientes al Windows.Resources elemento :

    <Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}">
       <Setter Property="HorizontalAlignment" Value="Left"/>
       <Setter Property="VerticalAlignment" Value="Center"/>
       <Setter Property="Margin" Value="3"/>
       <Setter Property="Height" Value="23"/>
    </Style>
    <Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}">
       <Setter Property="HorizontalAlignment" Value="Right"/>
       <Setter Property="VerticalAlignment" Value="Center"/>
       <Setter Property="Margin" Value="3"/>
       <Setter Property="Height" Value="26"/>
       <Setter Property="Width" Value="120"/>
    </Style>
    
  2. Reemplace la cuadrícula externa completa con este código.

    <Grid>
      <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
      <Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
          <TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
          <TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/>
          <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="New Order Form" FontWeight="Bold"/>
          <Label Content="Employee ID:"  Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Order Date:"  Grid.Row="2" Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_orderDatePicker" Grid.Row="2"  HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120"
                   SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Shipped Date:"  Grid.Row="4"  Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_shippedDatePicker"  Grid.Row="4"  HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Ship Via:"  Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_ShipViaTextBox"  Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Freight"  Grid.Row="6" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}"
                   Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected">
          <DataGrid.Columns>
              <DataGridTemplateColumn>
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/>
          </DataGrid.Columns>
       </DataGrid>
    </Grid>
    

Agregar botones para navegar, agregar, actualizar y eliminar

En una aplicación de Windows Forms, se le proporciona un BindingNavigator objeto con botones para navegar por filas de una base de datos y realizar operaciones CRUD básicas. Aunque WPF no proporciona un BindingNavigator, es fácil crear uno creando botones dentro de un StackPanel horizontal y asociando los botones con comandos enlazados a métodos en el archivo de código detrás.

Hay cuatro partes en la lógica de comandos:

  • Órdenes
  • Vinculaciones
  • Botones
  • Controladores de comandos en el código subyacente

Agregar comandos, enlaces y botones en XAML

  1. En el archivo MainWindow.xaml, agregue los comandos dentro del elemento Windows.Resources de la siguiente manera:

    <RoutedUICommand x:Key="FirstCommand" Text="First"/>
    <RoutedUICommand x:Key="LastCommand" Text="Last"/>
    <RoutedUICommand x:Key="NextCommand" Text="Next"/>
    <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/>
    <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/>
    <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/>
    <RoutedUICommand x:Key="UpdateCommand" Text="Update"/>
    <RoutedUICommand x:Key="AddCommand" Text="Add"/>
    <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
    
  2. Un CommandBinding asigna un evento RoutedUICommand a un método en el código subyacente. Agregue este CommandBindings elemento de la siguiente manera después de la etiqueta de cierre Windows.Resources.

    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/>
        <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/>
        <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/>
        <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/>
        <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/>
        <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/>
        <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/>
    </Window.CommandBindings>
    
  3. Agregue el StackPanel junto con los botones de navegación, agregar, eliminar y actualizar. Agregue este estilo a Windows.Resources:

    <Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="FontFamily" Value="Segoe UI Symbol"/>
        <Setter Property="Margin" Value="2,2,2,0"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="auto"/>
    </Style>
    
  4. Pegue este código justo después del RowDefinitions dentro del elemento externo Grid, hacia la parte superior de la página XAML.

     <StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin">
         <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/>
         <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
         <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/>
         <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
         <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
     </StackPanel>
    

Agregar controladores de comandos a la clase MainWindow

El código subyacente en MainWindow.xaml.cs es mínimo, excepto para los métodos add y delete:

  1. Para navegar, llame a métodos en la propiedad View de CollectionViewSource.

  2. Para realizar una eliminación en cascada en un pedido, use el DeleteOrderCommandHandler como se muestra en el ejemplo de código. Sin embargo, primero debe eliminar el Order_Details asociado al pedido.

  3. Usar UpdateCommandHandler para agregar un cliente o un pedido a la colección, o actualizar un cliente o pedido existente con los cambios que el usuario realiza en los cuadros de texto.

  4. Agregue estos métodos de controlador a la MainWindow clase en MainWindow.xaml.cs. Si su CollectionViewSource en la tabla Customers tiene un nombre diferente, debe ajustar el nombre en cada uno de los métodos:

    private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToLast();
    }
    
    private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToPrevious();
    }
    
    private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToNext();
    }
    
    private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToFirst();
    }
    
    private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        // If existing window is visible, delete the customer and all their orders.  
        // In a real application, you should add warnings and allow the user to cancel the operation.  
        var cur = custViewSource.View.CurrentItem as Customer;
    
        var cust = (from c in context.Customers
                    where c.CustomerID == cur.CustomerID
                    select c).FirstOrDefault();
    
        if (cust != null)
        {
            foreach (var ord in cust.Orders.ToList())
            {
                Delete_Order(ord);
            }
            context.Customers.Remove(cust);
        }
        context.SaveChanges();
        custViewSource.View.Refresh();
    }
    
    // Commit changes from the new customer form, the new order form,  
    // or edits made to the existing customer form.  
    private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        if (newCustomerGrid.IsVisible)
        {
            // Create a new object because the old one  
            // is being tracked by EF now.  
            Customer newCustomer = new Customer
            {
                Address = add_addressTextBox.Text,
                City = add_cityTextBox.Text,
                CompanyName = add_companyNameTextBox.Text,
                ContactName = add_contactNameTextBox.Text,
                ContactTitle = add_contactTitleTextBox.Text,
                Country = add_countryTextBox.Text,
                CustomerID = add_customerIDTextBox.Text,
                Fax = add_faxTextBox.Text,
                Phone = add_phoneTextBox.Text,
                PostalCode = add_postalCodeTextBox.Text,
                Region = add_regionTextBox.Text
            };
    
            // Perform very basic validation  
            if (newCustomer.CustomerID.Length == 5)
            {
                // Insert the new customer at correct position:  
                int len = context.Customers.Local.Count();
                int pos = len;
                for (int i = 0; i < len; ++i)
                {
                    if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0)
                    {
                        pos = i;
                        break;
                    }
                }
                context.Customers.Local.Insert(pos, newCustomer);
                custViewSource.View.Refresh();
                custViewSource.View.MoveCurrentTo(newCustomer);
            }
            else
            {
                MessageBox.Show("CustomerID must have 5 characters.");
            }
    
            newCustomerGrid.Visibility = Visibility.Collapsed;
            existingCustomerGrid.Visibility = Visibility.Visible;
        }
        else if (newOrderGrid.IsVisible)
        {
            // Order ID is auto-generated so we don't set it here.  
            // For CustomerID, address, etc we use the values from current customer.  
            // User can modify these in the datagrid after the order is entered.  
    
            Customer currentCustomer = (Customer)custViewSource.View.CurrentItem;
    
            Order newOrder = new Order()
            {
                OrderDate = add_orderDatePicker.SelectedDate,
                RequiredDate = add_requiredDatePicker.SelectedDate,
                ShippedDate = add_shippedDatePicker.SelectedDate,
                CustomerID = currentCustomer.CustomerID,
                ShipAddress = currentCustomer.Address,
                ShipCity = currentCustomer.City,
                ShipCountry = currentCustomer.Country,
                ShipName = currentCustomer.CompanyName,
                ShipPostalCode = currentCustomer.PostalCode,
                ShipRegion = currentCustomer.Region
            };
    
            try
            {
                newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text);
            }
            catch
            {
                MessageBox.Show("EmployeeID must be a valid integer value.");
                return;
            }
    
            try
            {
                // Exercise for the reader if you are using Northwind:  
                // Add the Northwind Shippers table to the model.
                
                // Acceptable ShipperID values are 1, 2, or 3.  
                if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2"
                    || add_ShipViaTextBox.Text == "3")
                {
                    newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text);
                }
                else
                {
                    MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind.");
                    return;
                }
            }
            catch
            {
                MessageBox.Show("Ship Via must be convertible to int");
                return;
            }
    
            try
            {
                newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text);
            }
            catch
            {
                MessageBox.Show("Freight must be convertible to decimal.");
                return;
            }
    
            // Add the order into the EF model  
            context.Orders.Add(newOrder);
            ordViewSource.View.Refresh();
        }
    
        // Save the changes, either for a new customer, a new order  
        // or an edit to an existing customer or order.
        context.SaveChanges();
    }
    
    // Sets up the form so that user can enter data. Data is later  
    // saved when user clicks Commit.  
    private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        existingCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.Visibility = Visibility.Collapsed;
        newCustomerGrid.Visibility = Visibility.Visible;
    
        // Clear all the text boxes before adding a new customer.  
        foreach (var child in newCustomerGrid.Children)
        {
            var tb = child as TextBox;
            if (tb != null)
            {
                tb.Text = "";
            }
        }
    }
    
    private void NewOrder_click(object sender, RoutedEventArgs e)
    {
        var cust = custViewSource.View.CurrentItem as Customer;
        if (cust == null)
        {
            MessageBox.Show("No customer selected.");
            return;
        }
    
        existingCustomerGrid.Visibility = Visibility.Collapsed;
        newCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.UpdateLayout();
        newOrderGrid.Visibility = Visibility.Visible;
    }
    
    // Cancels any input into the new customer form  
    private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        add_addressTextBox.Text = "";
        add_cityTextBox.Text = "";
        add_companyNameTextBox.Text = "";
        add_contactNameTextBox.Text = "";
        add_contactTitleTextBox.Text = "";
        add_countryTextBox.Text = "";
        add_customerIDTextBox.Text = "";
        add_faxTextBox.Text = "";
        add_phoneTextBox.Text = "";
        add_postalCodeTextBox.Text = "";
        add_regionTextBox.Text = "";
    
        existingCustomerGrid.Visibility = Visibility.Visible;
        newCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.Visibility = Visibility.Collapsed;
    }
    
    private void Delete_Order(Order order)
    {
        // Find the order in the EF model.  
        var ord = (from o in context.Orders.Local
                   where o.OrderID == order.OrderID
                   select o).FirstOrDefault();
    
        // Delete all the order_details that have  
        // this Order as a foreign key  
        foreach (var detail in ord.Order_Details.ToList())
        {
            context.Order_Details.Remove(detail);
        }
    
        // Now it's safe to delete the order.  
        context.Orders.Remove(ord);
        context.SaveChanges();
    
        // Update the data grid.  
        ordViewSource.View.Refresh();
    }
    
    private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        // Get the Order in the row in which the Delete button was clicked.  
        Order obj = e.Parameter as Order;
        Delete_Order(obj);
    }
    

Ejecución de la aplicación WPF

  1. Presione F5 para iniciar la depuración. Compruebe que los datos de cliente y pedido se rellenan en la cuadrícula y que los botones de navegación funcionan según lo previsto.

  2. Seleccione Confirmar para agregar un nuevo cliente o pedido al modelo después de escribir los datos.

  3. Seleccione Cancelar para volver a salir de un nuevo formulario de pedido o cliente sin guardar los datos.

  4. Para realizar modificaciones en clientes y pedidos existentes directamente, use los cuadros de texto, que escriben esos cambios en el modelo automáticamente.